Go
тестирование
Поиск…
Вступление
Go поставляется с собственными средствами тестирования, в которых есть все необходимое для запуска тестов и тестов. В отличие от большинства других языков программирования, часто нет необходимости в отдельной тестовой структуре, хотя некоторые из них существуют.
Базовый тест
main.go
:
package main
import (
"fmt"
)
func main() {
fmt.Println(Sum(4,5))
}
func Sum(a, b int) int {
return a + b
}
main_test.go
:
package main
import (
"testing"
)
// Test methods start with `Test`
func TestSum(t *testing.T) {
got := Sum(1, 2)
want := 3
if got != want {
t.Errorf("Sum(1, 2) == %d, want %d", got, want)
}
}
Для запуска теста просто используйте команду go test
:
$ go test
ok test_app 0.005s
Используйте флаг -v
для просмотра результатов каждого теста:
$ go test -v
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
ok _/tmp 0.000s
Используйте путь ./...
для рекурсивного тестирования подкаталогов:
$ go test -v ./...
ok github.com/me/project/dir1 0.008s
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
ok github.com/me/project/dir2 0.008s
=== RUN TestDiff
--- PASS: TestDiff (0.00s)
PASS
Запустите специальный тест:
Если есть несколько тестов, и вы хотите запустить конкретный тест, это можно сделать следующим образом:
go test -v -run=<TestName> // will execute only test with this name
Пример:
go test -v run=TestSum
Контрольные тесты
Если вы хотите измерить тесты, добавьте метод тестирования следующим образом:
sum.go
:
package sum
// Sum calculates the sum of two integers
func Sum(a, b int) int {
return a + b
}
sum_test.go
:
package sum
import "testing"
func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Sum(2, 3)
}
}
Затем, чтобы запустить простой тест:
$ go test -bench=.
BenchmarkSum-8 2000000000 0.49 ns/op
ok so/sum 1.027s
Настольные модульные тесты
Этот тип тестирования является популярным методом тестирования с предопределенными входными и выходными значениями.
Создайте файл main.go
с контентом:
package main
import (
"fmt"
)
func main() {
fmt.Println(Sum(4, 5))
}
func Sum(a, b int) int {
return a + b
}
После запуска вы увидите, что на выходе будет 9
. Хотя функция Sum
выглядит довольно просто, рекомендуется протестировать ваш код. Чтобы сделать это, мы создаем другой файл с именем main_test.go
в той же папке, что и main.go
, содержащий следующий код:
package main
import (
"testing"
)
// Test methods start with Test
func TestSum(t *testing.T) {
// Note that the data variable is of type array of anonymous struct,
// which is very handy for writing table-driven unit tests.
data := []struct {
a, b, res int
}{
{1, 2, 3},
{0, 0, 0},
{1, -1, 0},
{2, 3, 5},
{1000, 234, 1234},
}
for _, d := range data {
if got := Sum(d.a, d.b); got != d.res {
t.Errorf("Sum(%d, %d) == %d, want %d", d.a, d.b, got, d.res)
}
}
}
Как вы можете видеть, создается кусок анонимных структур, каждый из которых имеет набор входов и ожидаемый результат. Это позволяет создавать большое количество тестовых примеров вместе в одном месте, а затем выполнять в цикле, уменьшая повторение кода и улучшая ясность.
Примеры тестов (самодокументирующие тесты)
Этот тип тестов гарантирует, что ваш код будет правильно компилироваться и появиться в сгенерированной документации для вашего проекта. В дополнение к этому, примеры тестов могут утверждать, что ваш тест производит правильный вывод.
sum.go
:
package sum
// Sum calculates the sum of two integers
func Sum(a, b int) int {
return a + b
}
sum_test.go
:
package sum
import "fmt"
func ExampleSum() {
x := Sum(1, 2)
fmt.Println(x)
fmt.Println(Sum(-1, -1))
fmt.Println(Sum(0, 0))
// Output:
// 3
// -2
// 0
}
Чтобы выполнить тест, запустите go test
в папке, содержащей эти файлы, или поместите эти два файла в подпапку с именем sum
а затем из родительской папки выполните go test ./sum
. В обоих случаях вы получите аналогичный результат:
ok so/sum 0.005s
Если вам интересно, как это проверяет ваш код, вот еще одна примерная функция, которая фактически не проходит тест:
func ExampleSum_fail() {
x := Sum(1, 2)
fmt.Println(x)
// Output:
// 5
}
Когда вы запускаете go test
, вы получаете следующий результат:
$ go test
--- FAIL: ExampleSum_fail (0.00s)
got:
3
want:
5
FAIL
exit status 1
FAIL so/sum 0.006s
Если вы хотите увидеть документацию для своего пакета sum
- просто запустите:
go doc -http=:6060
и перейдите к http: // localhost: 6060 / pkg / FOLDER / sum / , где FOLDER - это папка, содержащая пакет sum
(в этом примере so
). Документация для метода sum выглядит следующим образом:
Тестирование HTTP-запросов
main.go:
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func fetchContent(url string) (string, error) {
res, err := http.Get(url)
if err != nil {
return "", nil
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func main() {
url := "https://example.com/"
content, err := fetchContent(url)
if err != nil {
log.Fatal(err)
}
fmt.Println("Content:", content)
}
main_test.go:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func Test_fetchContent(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello world")
}))
defer ts.Close()
content, err := fetchContent(ts.URL)
if err != nil {
t.Error(err)
}
want := "hello world"
if content != want {
t.Errorf("Got %q, want %q", content, want)
}
}
Устанавливать / Сбрасывать макет функции в тестах
В этом примере показано, как измакать вызов функции, который не имеет отношения к нашему модульному тесту, а затем использовать оператор defer
чтобы повторно назначить обращенный вызов функции обратно к его исходной функции.
var validate = validateDTD
// ParseXML parses b for XML elements and values, and returns them as a map of
// string key/value pairs.
func ParseXML(b []byte) (map[string]string, error) {
// we don't care about validating against DTD in our unit test
if err := validate(b); err != nil {
return err
}
// code to parse b etc.
}
func validateDTD(b []byte) error {
// get the DTD from some external storage, use it to validate b etc.
}
В нашем модульном тесте,
func TestParseXML(t *testing.T) {
// assign the original validate function to a variable.
originalValidate = validate
// use the mockValidate function in this test.
validate = mockValidate
// defer the re-assignment back to the original validate function.
defer func() {
validate = originalValidate
}()
var input []byte
actual, err := ParseXML(input)
// assertion etc.
}
func mockValidate(b []byte) error {
return nil // always return nil since we don't care
}
Тестирование с использованием функции setUp и tearDown
Вы можете установить функцию setUp и tearDown.
- Функция setUp подготавливает среду к испытаниям.
- Функция tearDown выполняет откат.
Это хороший вариант, когда вы не можете изменить свою базу данных, и вам нужно создать объект, который имитирует объект, созданный в базе данных, или же нужно будет инициализировать конфигурацию в каждом тесте.
Глупым примером может быть:
// Standard numbers map
var numbers map[string]int = map[string]int{"zero": 0, "three": 3}
// TestMain will exec each test, one by one
func TestMain(m *testing.M) {
// exec setUp function
setUp("one", 1)
// exec test and this returns an exit code to pass to os
retCode := m.Run()
// exec tearDown function
tearDown("one")
// If exit code is distinct of zero,
// the test will be failed (red)
os.Exit(retCode)
}
// setUp function, add a number to numbers slice
func setUp(key string, value int) {
numbers[key] = value
}
// tearDown function, delete a number to numbers slice
func tearDown(key string) {
delete(numbers, key)
}
// First test
func TestOnePlusOne(t *testing.T) {
numbers["one"] = numbers["one"] + 1
if numbers["one"] != 2 {
t.Error("1 plus 1 = 2, not %v", value)
}
}
// Second test
func TestOnePlusTwo(t *testing.T) {
numbers["one"] = numbers["one"] + 2
if numbers["one"] != 3 {
t.Error("1 plus 2 = 3, not %v", value)
}
}
Другим примером может быть подготовка базы данных для тестирования и выполнения отката
// ID of Person will be saved in database
personID := 12345
// Name of Person will be saved in database
personName := "Toni"
func TestMain(m *testing.M) {
// You create an Person and you save in database
setUp(&Person{
ID: personID,
Name: personName,
Age: 19,
})
retCode := m.Run()
// When you have executed the test, the Person is deleted from database
tearDown(personID)
os.Exit(retCode)
}
func setUp(P *Person) {
// ...
db.add(P)
// ...
}
func tearDown(id int) {
// ...
db.delete(id)
// ...
}
func getPerson(t *testing.T) {
P := Get(personID)
if P.Name != personName {
t.Error("P.Name is %s and it must be Toni", P.Name)
}
}
Просмотр содержимого кода в формате HTML
Run go test
как обычно, но с флагом coverprofile
. Затем используйте go tool
чтобы просмотреть результаты как HTML.
go test -coverprofile=c.out
go tool cover -html=c.out