0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p><strong>Testify</strong>- фреймворк для написания тестов на go. Если открыть документацию, то сразу видно, что в нём два основных пакета asser и require. И в обоих одинаковый набор методов.</p>
1
<p><strong>Testify</strong>- фреймворк для написания тестов на go. Если открыть документацию, то сразу видно, что в нём два основных пакета asser и require. И в обоих одинаковый набор методов.</p>
2
<h3>Какой пакет использовать?</h3>
2
<h3>Какой пакет использовать?</h3>
3
<p>Я советую всем<strong>использовать require</strong>. Всегда, когда не знаете, нет времени или желания думать -<strong>используйте require</strong>.</p>
3
<p>Я советую всем<strong>использовать require</strong>. Всегда, когда не знаете, нет времени или желания думать -<strong>используйте require</strong>.</p>
4
<p>Обратите внимание на "нет времени или желания думать". При написании тестов и так хватает, о чём подумать. Поэтому лучше ставить require и не думать об этом.</p>
4
<p>Обратите внимание на "нет времени или желания думать". При написании тестов и так хватает, о чём подумать. Поэтому лучше ставить require и не думать об этом.</p>
5
<h4>В чём разница между двумя пакетами?</h4>
5
<h4>В чём разница между двумя пакетами?</h4>
6
<p>Require сразу останавливает выполнение теста. Assert пометит тест упавшим, но выполнение продолжится.</p>
6
<p>Require сразу останавливает выполнение теста. Assert пометит тест упавшим, но выполнение продолжится.</p>
7
<h4>Что случится, если сделать наоборот и всегда ставить assert?</h4>
7
<h4>Что случится, если сделать наоборот и всегда ставить assert?</h4>
8
resp, err := client.Do(req) assert.NoError(t, err) assert.Equal(t, len(expectedBody), resp.ContentLength)<p>Это код упадёт в панику, если client.Do вернёт ошибку<em>(resp будет nil, и при попытке обратиться к ContentLength произойдёт<strong>разыменовывание</strong>нулевого указателя)</em>:</p>
8
resp, err := client.Do(req) assert.NoError(t, err) assert.Equal(t, len(expectedBody), resp.ContentLength)<p>Это код упадёт в панику, если client.Do вернёт ошибку<em>(resp будет nil, и при попытке обратиться к ContentLength произойдёт<strong>разыменовывание</strong>нулевого указателя)</em>:</p>
9
--- 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.<p>Assert как бы говорит: "Я тут что-то проверил, но ронять тест не буду". Поэтому код теста должен быть готов, что что-то уже пошло не так. Это усложняет понимание теста. Чтобы не думать, что там случится после assert’a, я рекомендую всегда использовать require:</p>
9
--- 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.<p>Assert как бы говорит: "Я тут что-то проверил, но ронять тест не буду". Поэтому код теста должен быть готов, что что-то уже пошло не так. Это усложняет понимание теста. Чтобы не думать, что там случится после assert’a, я рекомендую всегда использовать require:</p>
10
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.<h3>Зачем же этот assert вообще нужен?</h3>
10
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.<h3>Зачем же этот assert вообще нужен?</h3>
11
<p>Рассмотрим пример. Есть табличный тест:</p>
11
<p>Рассмотрим пример. Есть табличный тест:</p>
12
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) }<p>Здесь require работает не очень хорошо, т. к. завалит тест сразу, как только провалится один из случаев. Хотелось бы проверить все случаи из cases.</p>
12
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) }<p>Здесь require работает не очень хорошо, т. к. завалит тест сразу, как только провалится один из случаев. Хотелось бы проверить все случаи из cases.</p>
13
<p>Assert не просто помечает, что надо в конце завалить тест, он возвращает bool, говорящий, прошла проверка или нет. Если это использовать, то можно пройтись по всем тест-кейcам из таблицы так:</p>
13
<p>Assert не просто помечает, что надо в конце завалить тест, он возвращает bool, говорящий, прошла проверка или нет. Если это использовать, то можно пройтись по всем тест-кейcам из таблицы так:</p>
14
for _, c := range cases { resp, err := fn(c.str) if assert.NoError(t, err) { assert.Equal(t, c.expected, resp.ContentLength) } }<p>Небольшой минус такого решения - это сообщения об ошибках:</p>
14
for _, c := range cases { resp, err := fn(c.str) if assert.NoError(t, err) { assert.Equal(t, c.expected, resp.ContentLength) } }<p>Небольшой минус такого решения - это сообщения об ошибках:</p>
15
--- 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.<p>Какой именно случай упал - не очень понятно. Можно доработать assert’ы, добавив к ним расширенное сообщение об ошибке:</p>
15
--- 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.<p>Какой именно случай упал - не очень понятно. Можно доработать assert’ы, добавив к ним расширенное сообщение об ошибке:</p>
16
assert.NoErrorf(t, err, “check: %v”, c) assert.Equalf(t, c.expected, resp.ContentLength, “check: %v”, c)<p>Правда придётся так проработать все assert’ы. В примере их два, а что, если быдл бы больше? Я рекомендую использовать subtest:</p>
16
assert.NoErrorf(t, err, “check: %v”, c) assert.Equalf(t, c.expected, resp.ContentLength, “check: %v”, c)<p>Правда придётся так проработать все assert’ы. В примере их два, а что, если быдл бы больше? Я рекомендую использовать subtest:</p>
17
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) }) }<p>Во-первых, тут описание случая находится сразу в названии теста:</p>
17
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) }) }<p>Во-первых, тут описание случая находится сразу в названии теста:</p>
18
--- 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<p>А во-вторых, можно спокойно использовать require, т.к. это приведёт к завершению одно сабтеста. Раз нет assert'ов - можно не волноваться, что что-то упадёт из-за непродуманных зависимостей, код проще читать и понимать, т.к. он развивается линейно, без ветвлений и скрытых состояний.</p>
18
--- 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<p>А во-вторых, можно спокойно использовать require, т.к. это приведёт к завершению одно сабтеста. Раз нет assert'ов - можно не волноваться, что что-то упадёт из-за непродуманных зависимостей, код проще читать и понимать, т.к. он развивается линейно, без ветвлений и скрытых состояний.</p>
19
<h3>Резюме</h3>
19
<h3>Резюме</h3>
20
<ol><li>Используйте require.</li>
20
<ol><li>Используйте require.</li>
21
<li>Если require мешается, попробуйте реорганизовать тест: использовать сабтесты, разбить на несколько тестов.</li>
21
<li>Если require мешается, попробуйте реорганизовать тест: использовать сабтесты, разбить на несколько тестов.</li>
22
<li>Assert усложняет читаемость теста - нужны очень веские причины, чтобы его использовать.</li>
22
<li>Assert усложняет читаемость теста - нужны очень веские причины, чтобы его использовать.</li>
23
</ol>
23
</ol>