Testify: выбор пакета для тестирования (require vs assert)
Testify — фреймворк для написания тестов на go. Если открыть документацию, то сразу видно, что в нём два основных пакета asser и require. И в обоих одинаковый набор методов.
Какой пакет использовать?
Я советую всем использовать require. Всегда, когда не знаете, нет времени или желания думать — используйте require.
Обратите внимание на «нет времени или желания думать». При написании тестов и так хватает, о чём подумать. Поэтому лучше ставить require и не думать об этом.
В чём разница между двумя пакетами?
Require сразу останавливает выполнение теста. Assert пометит тест упавшим, но выполнение продолжится.
Что случится, если сделать наоборот и всегда ставить assert?
resp, err := client.Do(req) assert.NoError(t, err) assert.Equal(t, len(expectedBody), resp.ContentLength)
Это код упадёт в панику, если
--- FAIL: TestFoo (0.00s) /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:27 Error: Expected nil, but got: &errors.errorString{s:"test"} panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1240743] goroutine 6 [running]: testing.tRunner.func1(0xc42010c0f0) /usr/local/Cellar/go/1.10.1/libexec/src/testing/testing.go:742 +0x29d panic(0x1284320, 0x1450180) /usr/local/Cellar/go/1.10.1/libexec/src/runtime/panic.go:502 +0x229 _/Users/kulti/projects/blog_testify.TestFoo(0xc42010c0f0) /Users/kulti/projects/blog_testify/foo_test.go:28 +0xc3 testing.tRunner(0xc42010c0f0, 0x12ea138) /usr/local/Cellar/go/1.10.1/libexec/src/testing/testing.go:777 +0xd0 created by testing.(*T).Run /usr/local/Cellar/go/1.10.1/libexec/src/testing/testing.go:824 +0x2e0 exit status 2 FAIL _/Users/kulti/projects/blog_testify 0.021s Error: Tests failed.
Assert как бы говорит: «Я тут что-то проверил, но ронять тест не буду». Поэтому код теста должен быть готов, что что-то уже пошло не так. Это усложняет понимание теста. Чтобы не думать, что там случится после assert’a, я рекомендую всегда использовать require:
resp, err := client.Do(req) require.Nil(t, err) require.Equal(t, len(expectedBody), resp.ContentLength) --- FAIL: TestFoo (0.00s) /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:26 Error: Expected nil, but got: &errors.errorString{s:"test"} FAIL exit status 1 FAIL _/Users/kulti/projects/blog_testify 0.030s Error: Tests failed.
Зачем же этот assert вообще нужен?
Рассмотрим пример. Есть табличный тест:
cases := []testCase{ {"", 0}, {"v1", len("v1")}, {"V1", len("v12")}, } for _, c := range cases { resp, err := fn(c.str) require.NoError(t, err) { require.Equal(t, c.expected, resp.ContentLength) }
Здесь require работает не очень хорошо, т. к. завалит тест сразу, как только провалится один из случаев. Хотелось бы проверить все случаи из
Assert не просто помечает, что надо в конце завалить тест, он возвращает
for _, c := range cases { resp, err := fn(c.str) if assert.NoError(t, err) { assert.Equal(t, c.expected, resp.ContentLength) } }
Небольшой минус такого решения — это сообщения об ошибках:
--- FAIL: TestFoo (0.00s) /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:37 Error: Expected nil, but got: &errors.errorString{s:"test"} /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:38 Error: Not equal: expected: 2 actual: 0 /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:38 Error: Not equal: expected: 3 actual: 0 FAIL exit status 1 FAIL _/Users/kulti/projects/blog_testify 0.018s Error: Tests failed.
Какой именно случай упал — не очень понятно. Можно доработать assert’ы, добавив к ним расширенное сообщение об ошибке:
assert.NoErrorf(t, err, “check: %v”, c) assert.Equalf(t, c.expected, resp.ContentLength, “check: %v”, c)
Правда придётся так проработать все assert’ы. В примере их два, а что, если быдл бы больше? Я рекомендую использовать subtest:
for _, c := range cases { t.Run("check "+c.str, func(t *testing.T) { resp, err := fn(c.str) require.Nil(t, err) require.Equal(t, c.expected, resp.ContentLength) }) }
Во-первых, тут описание случая находится сразу в названии теста:
--- FAIL: TestFoo (0.00s) --- FAIL: TestFoo/check_ (0.00s) /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:38 Error: Expected nil, but got: &errors.errorString{s:"test"} --- FAIL: TestFoo/check_v1 (0.00s) /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:39 Error: Not equal: expected: 2 actual: 0 --- FAIL: TestFoo/check_V1 (0.00s) /Users/kulti/projects/blog_testify/assertions.go:239: Error Trace: foo_test.go:39 Error: Not equal: expected: 3 actual: 0
А во-вторых, можно спокойно использовать require, т.к. это приведёт к завершению одно сабтеста. Раз нет assert'ов — можно не волноваться, что что-то упадёт из-за непродуманных зависимостей, код проще читать и понимать, т.к. он развивается линейно, без ветвлений и скрытых состояний.
Резюме
- Используйте require.
- Если require мешается, попробуйте реорганизовать тест: использовать сабтесты, разбить на несколько тестов.
- Assert усложняет читаемость теста — нужны очень веские причины, чтобы его использовать.