Мой подход к тестированию. Часть первая

Сколько бы «в прошлой жизни» я не пытался заставить себя писать тесты, получалось довольно плохо. Точнее, оно получалось, но как-то всё хромало, как у Винни-Пуха правильнописание. Казалось, что быстрее и проще потыкать в браузере, или клавиши понажимать, проверить, а дальше просто будет работать.

По мере усложнения создаваемых приложений, логика становилась всё замудрённее, времени на такое вот «протыкивание» стало уходить всё больше. Каким-то своим отдельным путём я пришёл к тому, что стал писать мелкие отдельные файлы, которые выполняли некоторые функции из проекта и проверяли вывод на корректность. Таким образом, я для себя открыл юнит тестирование. Проблема была в том, что тесты необходимо поддерживать в актуальном состоянии, модифицировать вместе с основным кодом, а ведь кажется, что есть гораздо более важные задачи на данный момент.

В мае 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 сам откроет браузер, где мы сможем наблюдать вот такую красоту:

output

Он сам запустит тесты во всех сабмодулях, выведет суммарный отчет, покажет степень покрытия кода тестами (у нас 100% покрытие, как мы видим :). При этом будет перезапускать тесты при каждом изменении исходного кода и тут же отображать результат в браузере. Это просто праздник какой-то!

С самостоятельным тестированием разобрались. В следующей статье расскажу, как тестировать проекты принудительно. Не переключайтесь!