Сколько бы «в прошлой жизни» я не пытался заставить себя писать тесты, получалось довольно плохо. Точнее, оно получалось, но как-то всё хромало, как у Винни-Пуха правильнописание. Казалось, что быстрее и проще потыкать в браузере, или клавиши понажимать, проверить, а дальше просто будет работать.
По мере усложнения создаваемых приложений, логика становилась всё замудрённее, времени на такое вот «протыкивание» стало уходить всё больше. Каким-то своим отдельным путём я пришёл к тому, что стал писать мелкие отдельные файлы, которые выполняли некоторые функции из проекта и проверяли вывод на корректность. Таким образом, я для себя открыл юнит тестирование. Проблема была в том, что тесты необходимо поддерживать в актуальном состоянии, модифицировать вместе с основным кодом, а ведь кажется, что есть гораздо более важные задачи на данный момент.
В мае 2016 года было принято решение начать писать тесты в принудительном порядке, тем более, что исходные тексты новой версии нашей платформы Blank мы открыли сразу, а в грязь лицом ударить не хотелось.
Node.js
С подсистемой на Node.js вопросов никаких не возникло. Есть популярное решения для тестирования — Mocha (по-русски лучше не читать :) с понятным синтаксисом и весьма приятным форматированием результатов тестирования. Ну и, конечно, стандартный модуль Assert для выполнения проверок.
Go
С Go ситуация несколько иная. Стандартная поставка уже имеет средства для оформления и запуска тестов. Я сейчас говорю о пакете testing, в котором есть даже средства для замера производительности кода. Всё, что требуется — это создать рядом с тестируемым кодом файл с именем, оканчивающимся на _test.go
, например, app_test.go
, а в нем должны быть функции, начинающиеся на Test
и получающие на вход указатель на структуру testing.T
, с помощью методов которой можно, например, «завалить» тест. Во время компилляции, такие файлы игнорируются.
Например, у нас есть пакет с единственным методом, складывающим два положительных целых числа. В случае, если одно из чисел окажется отрицательным, функция возвращает ошибку. Понятно, что в реальной жизни программы должны делать что-то более полезное, но для объяснения сути, так даже понятнее:
// adder.go
package adder
import "errors"
func AddPositive(a, b int) (int, error) {
if a < 0 || b < 0 {
return 0, errors.New("only positive integers allowed")
}
return a + b, nil
}
Напишем тесты:
// adder_test.go
package adder
import "testing"
func TestAddPositiveSuccess(t *testing.T) {
expected := 5
res, err := AddPositive(2, 3)
if err != nil {
t.Fatal(err)
}
if res != expected {
t.Fatal("Achtung! value != expected")
}
}
func TestAddPositiveFail(t *testing.T) {
_, err := AddPositive(2, -3)
if err == nil {
t.Fatal("must return error")
}
}
В общем случае, чтобы запустить тестирование, достаточно выполнить команду go test
. Если все работает правильно, мы увидим нечто вроде PASS в консоли, что означает, что тесты выполнились успешно.
bash-3.2$ go test
PASS
ok _/tmp/adder 0.018s
Не очень-то информативно.
Вроде бы, никаких проблем, тест читаемый — если произошла ошибка, или результат выполнения функции не тот, что ожидали, то всё плохо. Смысл теста можно вложить в название тест-функции. Если тест будет завален, то в консоль будет выведена функция, в которой не прошли проверки и строка с соответствующей инструкцией t.Fatal
.
bash-3.2$ go test
--- FAIL: TestAddPositiveFail (0.00s)
adder_test.go:19: must return error
FAIL
exit status 1
FAIL _/tmp/adder 0.009s
Но после Mocha хочется какого-то бо́льшего удовлетворения. Чтобы красиво вывести все проверки. Чтобы можно было подробнее описать что именно тестируем и почему такой-то результат ожидаем.
Goblin
Поиск инструмента я начал с ресурса Awesome Go, там вообще много чего интересного можно найти, а иногда даже и полезного. Перебрал кучу тестирующих фреймворков, пока не дошёл до Гоблина. Т.к. мы договорились не называть по-русски другой фреймворк, то скажу дословно, как рассказывают о Гоблине его содатели:
A Mocha like BDD testing framework for Go
То, что доктор прописал!
Перепишем наш тест с использованием Гоблина:
package adder
import (
"github.com/franela/goblin"
"testing"
)
func TestAddPositive(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("#AddPositive", func() {
g.It("should return nil error and expected result when positive integer passed", func() {
expected := 5
res, err := AddPositive(2, 3)
g.Assert(err == nil).IsTrue()
g.Assert(res).Equal(expected)
})
g.It("should return error when negative integer passed", func() {
_, err := AddPositive(2, -3)
g.Assert(err == nil).IsFalse()
})
})
}
А теперь посмотрим на вывод команды go test
в консоли:
bash-3.2$ go test
#AddPositive
✔ should return nil error and expected result when positiv integer passed
✔ should return error when negative integer passed
2 tests complete (0 ms)
PASS
ok _/tmp/adder 0.008s
На мой взгляд, гораздо приятнее. При этом ещё и красиво цветом выделяются пройдённые тесты. Что тест, что вывод в консоли говорит сам за себя — тестируем функцию AddPositive
, которая должна выполниться без ошибки и вернуть ожидаемый результат, если на вход подаются положительные значения, а так же должна вернуть ошибку, если на вход подали что-то отрицательное.
Попробовав один раз, использую теперь Гоблина во всех проектах, я им очень доволен. При этом, Гоблин полностью совместим со стандартной библиотекой testing.
GoConvey
А теперь ещё один секрет. Есть довольно интересная штука, которая называется GoConvey. Это тоже фреймворк для тестирования, но с несколько другой идеологией, которая мне не очень понравилась. Зато мне понравилось как GoConvey работает в связке с Гоблином. Установив программу, запускаем в корне проекта. GoConvey сам откроет браузер, где мы сможем наблюдать вот такую красоту:
Он сам запустит тесты во всех сабмодулях, выведет суммарный отчет, покажет степень покрытия кода тестами (у нас 100% покрытие, как мы видим :). При этом будет перезапускать тесты при каждом изменении исходного кода и тут же отображать результат в браузере. Это просто праздник какой-то!
С самостоятельным тестированием разобрались. В следующей статье расскажу, как тестировать проекты принудительно. Не переключайтесь!