2020-03-19 12:46:57 +00:00
|
|
|
|
Тестирование
|
|
|
|
|
Лекция 5
|
|
|
|
|
|
|
|
|
|
Фёдор Короткий
|
|
|
|
|
|
|
|
|
|
* go test
|
|
|
|
|
|
|
|
|
|
- `*_test.go` файлы не являются частью пакета, а содержат тесты.
|
|
|
|
|
- `go`test` создаёт нужный main, компилирует исполняемый файл и запускает его.
|
|
|
|
|
|
|
|
|
|
* Тестовые функции
|
|
|
|
|
|
|
|
|
|
Тестовые функции должны иметь сигнатуру:
|
|
|
|
|
|
|
|
|
|
func TestName(t *testing.T) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Параметр `*testing.T` используется, чтобы сообщить о падении теста.
|
|
|
|
|
|
|
|
|
|
* Пример Palindrome
|
|
|
|
|
|
|
|
|
|
.play word1/word.go /func IsPa/,/^}/
|
|
|
|
|
|
|
|
|
|
.play word1/word_test.go /func TestPa/,/^}/
|
|
|
|
|
|
|
|
|
|
.play word1/word_test.go /func TestNonPa/,/^}/
|
|
|
|
|
|
|
|
|
|
* Пример Palindrome
|
|
|
|
|
|
|
|
|
|
.play word1/word_test.go /func TestFrench/,/OMIT/
|
|
|
|
|
|
|
|
|
|
$ go test -v -run="French|Canal"
|
|
|
|
|
=== RUN TestFrenchPalindrome
|
|
|
|
|
--- FAIL: TestFrenchPalindrome (0.00s)
|
|
|
|
|
word_test.go:28: IsPalindrome("été") = false
|
|
|
|
|
=== RUN TestCanalPalindrome
|
|
|
|
|
--- FAIL: TestCanalPalindrome (0.00s)
|
|
|
|
|
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false
|
|
|
|
|
FAIL
|
|
|
|
|
exit status 1
|
|
|
|
|
FAIL gopl.io/ch11/word1 0.014s
|
|
|
|
|
|
|
|
|
|
* Пример Palindrome
|
|
|
|
|
|
|
|
|
|
.play word2/word.go /func IsPa/,/^}/
|
|
|
|
|
|
|
|
|
|
* Table Driven Test
|
|
|
|
|
|
|
|
|
|
.play word2/word_test.go /func Test/,/^}/
|
|
|
|
|
|
|
|
|
|
* Пример Echo
|
|
|
|
|
|
|
|
|
|
.play echo/echo.go /var/,/OMIT/
|
|
|
|
|
|
|
|
|
|
* Пример Echo
|
|
|
|
|
|
|
|
|
|
.play echo/echo_test.go /func TestEcho/,/^}/
|
|
|
|
|
|
|
|
|
|
* External Tests
|
|
|
|
|
|
|
|
|
|
$ go list -f={{.GoFiles}} fmt
|
|
|
|
|
[doc.go format.go print.go scan.go]
|
|
|
|
|
|
|
|
|
|
$ go list -f={{.TestGoFiles}} fmt
|
|
|
|
|
[export_test.go]
|
|
|
|
|
|
|
|
|
|
$ go list -f={{.XTestGoFiles}} fmt
|
|
|
|
|
[fmt_test.go scan_test.go stringer_test.go]
|
|
|
|
|
|
|
|
|
|
Package test
|
|
|
|
|
|
|
|
|
|
package fmt
|
|
|
|
|
|
|
|
|
|
func TestXXX(t *testing.T) {}
|
|
|
|
|
|
|
|
|
|
External test
|
|
|
|
|
|
|
|
|
|
package fmt_test
|
|
|
|
|
|
|
|
|
|
func TestYYY(t *testing.T) {}
|
|
|
|
|
|
|
|
|
|
* fmt isSpace example
|
|
|
|
|
|
|
|
|
|
// export_test.go
|
|
|
|
|
package fmt
|
|
|
|
|
|
|
|
|
|
var IsSpace = isSpace
|
|
|
|
|
|
|
|
|
|
- fmt не зависит от unicode, и содержит упрощённую реализацию isSpace.
|
|
|
|
|
|
|
|
|
|
- External тесты fmt проверяют, что fmt.isSpace и unicode.IsSpace не
|
|
|
|
|
отличаются в поведении.
|
|
|
|
|
|
|
|
|
|
* Writing Effective tests
|
|
|
|
|
|
|
|
|
|
Bad example
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// A poor assertion function.
|
|
|
|
|
func assertEqual(x, y int) {
|
|
|
|
|
if x != y {
|
|
|
|
|
panic(fmt.Sprintf("%d != %d", x, y))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSplit(t *testing.T) {
|
|
|
|
|
words := strings.Split("a:b:c", ":")
|
|
|
|
|
assertEqual(len(words), 4)
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Тест упадёт с сообщением `3 != 4` после страниц стектрейсов.
|
|
|
|
|
|
|
|
|
|
* Writing Effective tests
|
|
|
|
|
|
|
|
|
|
Good example
|
|
|
|
|
|
|
|
|
|
func TestSplit(t *testing.T) {
|
|
|
|
|
s, sep := "a:b:c", ":"
|
|
|
|
|
words := strings.Split(s, sep)
|
|
|
|
|
if got, want := len(words), 3; got != want {
|
|
|
|
|
t.Errorf("Split(%q, %q) returned %d words, want %d",
|
|
|
|
|
s, sep, got, want)
|
|
|
|
|
}
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Завершение теста
|
|
|
|
|
|
|
|
|
|
type Banana struct {
|
|
|
|
|
Color string
|
|
|
|
|
Tasty bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBanana(t *testing.T) {
|
|
|
|
|
banana, err := GetBanana()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetBanana() failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if banana.Color != "yellow" {
|
|
|
|
|
t.Errorf("banana colors is %s, want yellow", banana.Color)
|
|
|
|
|
}
|
|
|
|
|
if !banana.Tasty {
|
|
|
|
|
t.Errorf("banana is not tasty")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Завершение теста
|
|
|
|
|
|
|
|
|
|
- Иногда тест нужно завершить преждевременно.
|
|
|
|
|
|
|
|
|
|
t.Fatal("gcc not found in PATH")
|
|
|
|
|
t.Fatalf("request failed: %v", err)
|
|
|
|
|
t.FailNow()
|
|
|
|
|
|
|
|
|
|
- Иногда тест нужно продолжать, чтобы узнать больше информации
|
|
|
|
|
|
|
|
|
|
t.Error("i got a bad feeling about this")
|
|
|
|
|
t.Errorf("%d documents found, want %d", 2, 3)
|
|
|
|
|
t.Fail()
|
|
|
|
|
|
|
|
|
|
- Иногда нужно просто залогировать информацию на будущее
|
|
|
|
|
|
|
|
|
|
t.Logf("using go from %s", path)
|
|
|
|
|
|
|
|
|
|
* Тестовые проверки в других горутинах
|
|
|
|
|
|
|
|
|
|
func TestGo(t *testing.T) {
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
wg.Add(2)
|
|
|
|
|
|
|
|
|
|
go func () {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
// This is OK
|
|
|
|
|
t.Error("1 != 2")
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
go func () {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
// This is INVALID
|
|
|
|
|
t.Fatal("1 != 2")
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Горутины и завершение теста
|
|
|
|
|
|
|
|
|
|
func TestGo(t *testing.T) {
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
t.Logf("tick") // This will panic
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Правильное завершение
|
|
|
|
|
|
|
|
|
|
func TestGo(t *testing.T) {
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
defer wg.Wait()
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.Logf("tick")
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* testify
|
|
|
|
|
|
|
|
|
|
func TestSum(t *testing.T) {
|
|
|
|
|
if got, want := Sum(1, 2), 4; got != want {
|
|
|
|
|
t.Errorf("Sum(%d, %d) = %d, want %d", 1, 2, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
=== RUN TestSum
|
|
|
|
|
--- FAIL: TestSum (0.00s)
|
|
|
|
|
example_test.go:11: Sum(1, 2) = 3, want 4
|
|
|
|
|
FAIL
|
|
|
|
|
|
|
|
|
|
* testify
|
|
|
|
|
|
2020-03-19 13:06:26 +00:00
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
)
|
|
|
|
|
|
2020-03-19 12:46:57 +00:00
|
|
|
|
func TestSum0(t *testing.T) {
|
|
|
|
|
assert.Equalf(t, 4, Sum(1, 2), "Sum(%d, %d)", 1, 2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
=== RUN TestSum0
|
|
|
|
|
--- FAIL: TestSum0 (0.00s)
|
|
|
|
|
example_test.go:20:
|
|
|
|
|
Error Trace: example_test.go:20
|
|
|
|
|
Error: Not equal:
|
|
|
|
|
expected: 4
|
|
|
|
|
actual : 3
|
|
|
|
|
Test: TestSum0
|
|
|
|
|
Messages: Sum(1, 2)
|
|
|
|
|
|
|
|
|
|
- Функции из пакета `assert` работают как `t.Errorf`.
|
|
|
|
|
- Функции из пакета `require` работают как `t.Fatalf`.
|
|
|
|
|
|
|
|
|
|
* testify
|
|
|
|
|
|
|
|
|
|
func TestCall(t *testing.T) {
|
|
|
|
|
res, err := Call()
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, 42, res)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- Для проверок ошибок используйте `require.Error` и `require.NoError`.
|
|
|
|
|
|
|
|
|
|
* Coverage
|
|
|
|
|
|
2021-03-11 14:35:16 +00:00
|
|
|
|
.play size/size.go /func/,/^}/
|
|
|
|
|
|
|
|
|
|
* Coverage
|
|
|
|
|
|
|
|
|
|
.play size/size_test.go /func/,/^}/
|
|
|
|
|
|
|
|
|
|
go test -cover
|
|
|
|
|
PASS
|
|
|
|
|
coverage: 42.9% of statements
|
2024-06-05 17:36:34 +00:00
|
|
|
|
ok gitlab.com/slon/shad-go/lectures/04-testing/size 0.001s
|
2021-03-11 14:35:16 +00:00
|
|
|
|
|
|
|
|
|
* Coverage
|
|
|
|
|
|
|
|
|
|
go test -coverprofile=coverage.out
|
|
|
|
|
go tool cover -html=coverage.out
|
|
|
|
|
|
|
|
|
|
.image size/coverage.png
|
2020-03-19 12:46:57 +00:00
|
|
|
|
|
|
|
|
|
* Benchmark Functions
|
|
|
|
|
|
|
|
|
|
func BenchmarkIsPalindrome(b *testing.B) {
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
IsPalindrome("A man, a plan, a canal: Panama")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ go test -bench=.
|
|
|
|
|
PASS
|
|
|
|
|
BenchmarkIsPalindrome-8 1000000 1035 ns/op
|
|
|
|
|
ok gopl.io/ch11/word2 2.179s
|
|
|
|
|
|
|
|
|
|
- `b.ReportAllocs()` включает подсчёт аллокаций
|
|
|
|
|
- `-benchmem` включает подсчёт аллокаций глобально
|
|
|
|
|
|
|
|
|
|
* Benchmark & Test Parameters
|
|
|
|
|
|
|
|
|
|
func benchmark(b *testing.B, size int) { /* ... */ }
|
|
|
|
|
func Benchmark10(b *testing.B) { benchmark(b, 10) }
|
|
|
|
|
func Benchmark100(b *testing.B) { benchmark(b, 100) }
|
|
|
|
|
func Benchmark1000(b *testing.B) { benchmark(b, 1000) }
|
|
|
|
|
|
|
|
|
|
Или через под-тесты
|
|
|
|
|
|
|
|
|
|
func benchmark(b *testing.B, size int) { /* ... */ }
|
|
|
|
|
|
|
|
|
|
func BenchmarkN(b *testing.B) {
|
|
|
|
|
for _, n := range []int{10, 100, 1000} {
|
|
|
|
|
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
|
|
|
|
benchmark(b, n)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-11 09:41:59 +00:00
|
|
|
|
* Fuzzing
|
|
|
|
|
|
|
|
|
|
func Reverse(s string) string {
|
|
|
|
|
b := []byte(s)
|
|
|
|
|
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
|
|
|
|
|
b[i], b[j] = b[j], b[i]
|
|
|
|
|
}
|
|
|
|
|
return string(b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Fuzzing
|
|
|
|
|
|
|
|
|
|
func FuzzReverse(f *testing.F) {
|
|
|
|
|
testcases := []string{"Hello, world", " ", "!12345"}
|
|
|
|
|
for _, tc := range testcases {
|
|
|
|
|
f.Add(tc) // Use f.Add to provide a seed corpus
|
|
|
|
|
}
|
|
|
|
|
f.Fuzz(func(t *testing.T, orig string) {
|
|
|
|
|
rev := Reverse(orig)
|
|
|
|
|
doubleRev := Reverse(rev)
|
|
|
|
|
if orig != doubleRev {
|
|
|
|
|
t.Errorf("Before: %q, after: %q", orig, doubleRev)
|
|
|
|
|
}
|
|
|
|
|
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
|
|
|
|
|
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Fuzzing
|
|
|
|
|
|
|
|
|
|
go test -fuzz=Fuzz
|
|
|
|
|
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
|
|
|
|
|
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
|
|
|
|
|
fuzz: minimizing 38-byte failing input file...
|
|
|
|
|
--- FAIL: FuzzReverse (0.01s)
|
|
|
|
|
--- FAIL: FuzzReverse (0.00s)
|
|
|
|
|
reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"
|
|
|
|
|
|
|
|
|
|
Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
|
|
|
|
|
To re-run:
|
|
|
|
|
go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
|
|
|
|
|
FAIL
|
|
|
|
|
exit status 1
|
|
|
|
|
FAIL example/fuzz 0.030s
|
|
|
|
|
|
|
|
|
|
Что сломало реализацию?
|
|
|
|
|
|
|
|
|
|
go test fuzz v1
|
|
|
|
|
string("泃")
|
|
|
|
|
|
2020-03-19 12:46:57 +00:00
|
|
|
|
* Parallel tests
|
|
|
|
|
|
|
|
|
|
func TestA(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestB(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestC(t *testing.T) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* Example Tests
|
|
|
|
|
|
|
|
|
|
func ExampleIsPalindrome() {
|
|
|
|
|
fmt.Println(IsPalindrome("A man, a plan, a canal: Panama"))
|
|
|
|
|
fmt.Println(IsPalindrome("palindrome"))
|
|
|
|
|
// Output:
|
|
|
|
|
// true
|
|
|
|
|
// false
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 13:06:26 +00:00
|
|
|
|
Если `Output` нет, то `Example` служит только для документации.
|
2020-03-19 12:46:57 +00:00
|
|
|
|
|
|
|
|
|
func ExampleAPI() {
|
|
|
|
|
var c *Client // skip initialization
|
|
|
|
|
|
|
|
|
|
rsp, err := c.Call(&Request{})
|
|
|
|
|
_ = err
|
|
|
|
|
_ = rsp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* TestMain
|
|
|
|
|
|
|
|
|
|
Иногда нужно сделать глобальную инициализацию.
|
|
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
|
if os.Getenv("INSIDE_DOCKER") == "" {
|
|
|
|
|
os.Exit(runSelfInDocker())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
os.Exit(m.Run())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* t.Helper()
|
|
|
|
|
|
|
|
|
|
func assertGood(t *testing.T, i int) {
|
|
|
|
|
if i != 0 {
|
|
|
|
|
t.Errorf("i (= %d) != 0", i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestA(t *testing.T) {
|
|
|
|
|
// which one failed?
|
|
|
|
|
assertGood(t, 0)
|
|
|
|
|
assertGood(t, 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
=== RUN TestA
|
|
|
|
|
--- FAIL: TestA (0.00s)
|
|
|
|
|
example_test.go:25: i (= 1) != 0
|
|
|
|
|
FAIL
|
|
|
|
|
|
|
|
|
|
* t.Helper()
|
|
|
|
|
|
|
|
|
|
func assertGood(t *testing.T, i int) {
|
2020-03-19 13:06:26 +00:00
|
|
|
|
t.Helper()
|
2020-03-19 12:46:57 +00:00
|
|
|
|
if i != 0 {
|
|
|
|
|
t.Errorf("i (= %d) != 0", i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestA(t *testing.T) {
|
|
|
|
|
assertGood(t, 0)
|
|
|
|
|
assertGood(t, 1) // line 32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
=== RUN TestA
|
|
|
|
|
--- FAIL: TestA (0.00s)
|
|
|
|
|
example_test.go:32: i (1) != 0
|
|
|
|
|
FAIL
|
|
|
|
|
|
|
|
|
|
* t.Skip()
|
|
|
|
|
|
|
|
|
|
func TestingDB(t *testing.T) {
|
|
|
|
|
dbConn := os.Getenv("DB")
|
|
|
|
|
if dbConn == "off" {
|
|
|
|
|
t.Skipf("DB=off is set; disabling tests relying on database")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Иногда полезно пропускать тесты, которые используют внешние зависимости.
|
|
|
|
|
|
|
|
|
|
* Test Fixtures
|
|
|
|
|
|
|
|
|
|
type env struct {
|
|
|
|
|
Client *s3.Client
|
|
|
|
|
DB *sql.Conn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newEnv(t *testing.T) (*env, func()) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestA(t *testing.T) {
|
|
|
|
|
env, stop := newEnv(t)
|
|
|
|
|
defer stop()
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestB(t *testing.T) {
|
|
|
|
|
env, stop := newEnv(t)
|
|
|
|
|
defer stop()
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 14:35:16 +00:00
|
|
|
|
* t.Cleanup()
|
|
|
|
|
|
|
|
|
|
func newEnv(t *testing.T) *env {
|
|
|
|
|
// ...
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
DB.Close()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestA(t *testing.T) {
|
|
|
|
|
env := newEnv(t)
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 12:46:57 +00:00
|
|
|
|
* Fixture Composition
|
|
|
|
|
|
|
|
|
|
type MyFixture struct {
|
|
|
|
|
other.Fixture
|
|
|
|
|
third.Fixture
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newFixture(t *testing.T) (*MyFixture, func()) {
|
|
|
|
|
other, stopOther := other.NewFixture(t)
|
|
|
|
|
third, stopThird := third.NewFixture(t)
|
|
|
|
|
|
|
|
|
|
return &MyFixture{other, third}, func() {
|
|
|
|
|
stopOther()
|
|
|
|
|
stopThird()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 13:06:26 +00:00
|
|
|
|
* Race detector
|
|
|
|
|
|
|
|
|
|
.play race/race_test.go
|
|
|
|
|
|
|
|
|
|
* Race detector
|
|
|
|
|
|
|
|
|
|
prime@bee ~/C/shad-go> go test -race ./lectures/04-testing/race
|
|
|
|
|
==================
|
|
|
|
|
WARNING: DATA RACE
|
|
|
|
|
Read at 0x00c000092090 by goroutine 8:
|
2024-06-05 17:36:34 +00:00
|
|
|
|
gitlab.com/slon/shad-go/lectures/04-testing/race.TestRace()
|
2020-03-19 13:06:26 +00:00
|
|
|
|
/home/prime/Code/shad-go/lectures/04-testing/race/race_test.go:25 +0x144
|
|
|
|
|
testing.tRunner()
|
|
|
|
|
/usr/local/go/src/testing/testing.go:909 +0x199
|
|
|
|
|
|
|
|
|
|
Previous write at 0x00c000092090 by goroutine 9:
|
2024-06-05 17:36:34 +00:00
|
|
|
|
gitlab.com/slon/shad-go/lectures/04-testing/race.TestRace.func1()
|
2020-03-19 13:06:26 +00:00
|
|
|
|
/home/prime/Code/shad-go/lectures/04-testing/race/race_test.go:17 +0x6c
|
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
==================
|
|
|
|
|
--- FAIL: TestRace (0.00s)
|
|
|
|
|
testing.go:853: race detected during execution of test
|
|
|
|
|
FAIL
|
2024-06-05 17:36:34 +00:00
|
|
|
|
FAIL gitlab.com/slon/shad-go/lectures/04-testing/race 0.007s
|
2020-03-19 13:06:26 +00:00
|
|
|
|
|
|
|
|
|
* White box testing
|
|
|
|
|
|
2021-03-11 14:35:16 +00:00
|
|
|
|
.play mocks/mocks.go /func CheckQuota/,/OMIT/
|
2020-03-19 13:06:26 +00:00
|
|
|
|
|
|
|
|
|
* White box testing
|
|
|
|
|
|
|
|
|
|
.play mocks/mocks_test.go /func/,/^}/
|
|
|
|
|
|
2020-03-19 13:23:32 +00:00
|
|
|
|
* gomock
|
2020-03-19 13:06:26 +00:00
|
|
|
|
|
2020-03-19 13:23:32 +00:00
|
|
|
|
.play gomock/example.go
|
|
|
|
|
|
|
|
|
|
- Запуск `go`generate`.` создаст файл `mock.go`
|
|
|
|
|
- Хорошая идея - класть `mock`-и в отдельный пакет.
|
|
|
|
|
|
|
|
|
|
* gomock
|
|
|
|
|
|
|
|
|
|
.play gomock/example_test.go
|
|
|
|
|
|
|
|
|
|
* httptest
|
|
|
|
|
|
|
|
|
|
.play httptest/main.go /func/,/^}/
|
2020-03-19 13:50:45 +00:00
|
|
|
|
|
|
|
|
|
* golden files
|
|
|
|
|
|
|
|
|
|
func TestExample(t *testing.T) {
|
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", "/example", nil)
|
2020-03-19 18:52:55 +00:00
|
|
|
|
require.NoError(t, err)
|
2020-03-19 13:50:45 +00:00
|
|
|
|
|
|
|
|
|
handler := http.HandlerFunc(ExampleHandler)
|
2020-03-19 18:52:55 +00:00
|
|
|
|
handler.ServeHTTP(req, recorder)
|
2020-03-19 13:50:45 +00:00
|
|
|
|
|
|
|
|
|
g := goldie.New(t)
|
|
|
|
|
g.Assert(t, "example", recorder.Body.Bytes())
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 18:52:55 +00:00
|
|
|
|
- `go`test`-update` сохраняет результат в файл
|
|
|
|
|
- `go`test` сравнивает вывод с сохранённым результатом
|