Testing
In Go, the testing
package provides support for automated testing
of Go packages. It is intended to be used in concert with the go test
command, which automates execution of any function of the form func TestXxx(*testing.T) where Xxx does not start with a lowercase letter
. The function name serves to identify the test routine. Within these functions, use the Error
, Fail
or related methods to signal failure.
To write a new test suite, create a file that contains the TestXxx functions and give that file a name ending in _test.go
. The file will be excluded from regular package builds but will be included when the "go test" command is run
. The test file can be in the same package as the one being tested, or in a corresponding package with the suffix _test
.
Example
package tax
func CalculateTax(amount float64) float64 {
if amount <= 0 {
return 0
}
if amount >= 1000 && amount < 20000 {
return 10.0
}
if amount >= 20000 {
return 20.0
}
return 5.0
}
package tax
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCalculateTax(t *testing.T) {
result := CalculateTax(500.0)
assert.Equal(t, result, 5.0)
}
func TestCalculateTaxBatch(t *testing.T) {
type calcTax struct {
amount, expect float64
}
table := []calcTax{
{500.0, 5.0},
{1000.0, 10.0},
{1500.0, 10.0},
}
for _, item := range table {
result := CalculateTax(item.amount)
if result != item.expect {
t.Errorf("Expected %f but got %f", item.expect, result)
}
}
}
Measure Test Coverage
In Go, you can measure test coverage using the command go test -coverprofile
flag. This flag instructs the command to write a coverage profile to a file after running the tests. The coverage profile includes information about which parts of your code were executed during the tests
.
Example
This command will run all tests in the current directory and write the coverage profile to a file named coverage.out
.
Analyze the coverage profile using the go tool cover
command. This command can generate a report in different formats, such as text or HTML
.
Example
This command will generate an HTML report
and write it to a file named coverage.html
. You can then open this file in a web browser to view the coverage report.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>tax: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">tax/tax.go (80.0%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">package tax
func CalculateTax(amount float64) float64 <span class="cov8" title="1">{
if amount == 0 </span><span class="cov0" title="0">{
return 0
}</span>
<span class="cov8" title="1">if amount >= 1000 </span><span class="cov8" title="1">{
return 10.0
}</span>
<span class="cov8" title="1">return 5.0</span>
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
Benchmarking
You can also write benchmark tests
in Go, which measure the performance of a function or program
. It allows you to identify potential areas for optimization
and understand the impact of the changes you make to your code
. The testing
package in Go provides support for benchmarking, which is done by creating functions with a specific signature that takes a pointer to testing.B as its only argument
.
To run
the benchmark, use the go test
command with the -bench
flag.
Example
This command will generate an HTML report
and write it to a file named coverage.html
. You can then open this file in a web browser to view the coverage report.
package tax
import "time"
func CalculateTaxB(amount float64) float64 {
if amount == 0 {
return 0
}
if amount >= 1000 {
return 10.0
}
return 5.0
}
func CalculateTaxB2(amount float64) float64 {
if amount == 0 {
return 0
}
time.Sleep(time.Millisecond)
if amount >= 1000 {
return 10.0
}
return 5.0
}
Fuzzing
Fuzzing is a type of automated testing which continuously manipulates inputs to a program to find bugs
. Fuzzing uses coverage guidance to intelligently walk through the code being fuzzed to find and report failures to the user
. It can reach edge cases
which humans often miss, making fuzz testing particularly valuable for finding security exploits and vulnerabilities
.
```bash
go test -fuzz=. -fuzztime=5s
```
```go
package tax
func CalculateTax(amount float64) float64 {
if amount <= 0 {
return 0
}
if amount >= 1000 && amount < 20000 {
return 10.0
}
if amount >= 20000 {
return 20.0
}
return 5.0
}
```
```go
package tax
import "testing"
func FuzzCalculateTax(f *testing.F) {
seed := []float64{-1, -2, -2.5, 500.0, 1000.0, 1501.0}
for _, amount := range seed {
f.Add(amount)
}
f.Fuzz(func(t *testing.T, amount float64) {
result := CalculateTax(amount)
if amount <= 0 && result != 0 {
t.Errorf("Reveived %f but expected 0", result)
}
if amount > 20000 && result != 20 {
t.Errorf("Reveived %f but expected 20", result)
}
})
}
```
```bash title="output"
fuzz: elapsed: 0s, gathering baseline coverage: 0/8 completed
fuzz: elapsed: 0s, gathering baseline coverage: 8/8 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 89077 (29688/sec), new interesting: 0 (total: 8)
fuzz: elapsed: 5s, execs: 150973 (29952/sec), new interesting: 0 (total: 8)
PASS
ok tax 5.073s
```