Статьи

Табличные тесты в Go

Я начал работать в  Go  как профессионально, так и лично, и получил удовольствие от этого опыта. Одна из причин заключается в том, что в некоторых областях Go применяет совершенно другой подход к Java, что делает переключение между ними расширяющим кругозор.

Одна такая область находится в модульном тестировании. Go придает достаточное значение модульным тестам, чтобы сделать тестирование частью стандартной библиотеки (включая покрытие кода, которое я собираюсь обсудить в будущем). Но подход отличается от библиотеки, такой как JUnit, в том, что нет никаких утверждений. Вместо этого есть только функции, такие как  Errorf провал теста с сообщением журнала.

Чтобы увидеть полный пример из этой статьи, посмотрите этот небольшой  репозиторий GitHub . Он содержит функцию, которая возвращает числовое значение для римской цифры в виде строки. 

Модульные тесты в Go хранятся в файлах, оканчивающихся на  _test.go. Эти файлы не будут создаваться с нормальным кодом, но будут проверены на предметные тесты. В этих файлах мы пишем функции, начинающиеся с того,  Test что принимают один параметр  типа *testing.T . Например:

import "testing"

...

func TestValid(t *testing.T) {
   // Run tests
}

Функция не нуждается в возвращаемом типе; если функция завершается, как и ожидалось, тест проходит. Чтобы не пройти тест, мы используем функции  *testing.T типа. Как упомянуто выше, этот тип не предоставляет функции стиля «утверждения»; вместо этого есть функции, которые регистрируют ошибки и не проходят тест. По этой причине в тестах Go используются регулярные выражения if / else, такие как:

if err != nil {
    t.Errorf("Unexpected error for input %v: %v", tt.input, err)
}

Это более многословно, чем утверждение, но преимущество в том, что оно более явное. Код понятен любому, кто понимает язык, и не требует изучения отдельной библиотеки. Он также выравнивает тестовый код с обычным кодом Go, поскольку проверка на наличие ошибок, отличных от nil, является стандартной практикой в ​​Go (вместо обработки исключений, наблюдаемой в других языках). Дополнительную информацию о встроенной поддержке Go для модульного тестирования см. В этой статье , также на DZone.

Чтобы восполнить дополнительную детализацию при проверке ошибок, Go поощряет  тестирование на основе таблиц . Идея состоит в том, чтобы создать структуру данных, обычно срез, которая содержит входные данные теста и ожидаемые выходные данные, а затем выполнить итерацию по срезу, проверяя каждый случай.

Несмотря на то, что этот вид табличного теста легко выполнять на других языках, таких как Java, в Go он очень прост благодаря возможности объявлять и заполнять структуры данных в одном выражении.

Например, чтобы протестировать функцию, которая принимает строку и возвращает int, мы можем объявить следующий  фрагмент :

var validTests = []struct {
    input    string
    expected int
}{
    {"", 0},
    {"I", 1},
}

Эквивалент в Java, вероятно, можно сделать, объявив класс, а затем создав статический инициализатор для создания коллекции экземпляров этого класса. Не сложно, но более многословно.

Код для перебора таблицы может быть базовым; конечно, он должен быть адаптирован, чтобы отражать, как тестируемый код должен быть инициализирован и вызван.

for _, tt := range validTests {
    res, err := RomanToInt(tt.input)
    if err != nil {
        t.Errorf("Unexpected error for input %v: %v", tt.input, err)
    }
    if res != tt.expected {
        t.Errorf("Unexpected value for input %v: %v", tt.input, res)
    }
}

Этот код использует преимущества простой итерации Go  над слайсами . Использование  for _, tt позволяет нам игнорировать индекс каждого элемента и просто использовать сам элемент.

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

До сих пор я только что показал табличный тест на допустимые входы и выходы. В следующей статье я покажу тест на основе таблиц для проверки ошибок.