Модульное тестирование - обязательный компонент написания хорошего кода. Каждый разработчик должен уметь писать хорошие модульные тесты. Поскольку все больше и больше организаций используют Golang, крайне важно, чтобы мы, как разработчики, научились писать красивые модульные тесты для наших функций Golang.
Это руководство является самым основным введением в модульное тестирование Golang. В нем не рассматриваются сложные темы, такие как насмешки, бенчмаркинг и идемпотентность. Я постараюсь осветить эти темы в будущем.
Итак, приступим.
Код
Функциональность, которую мы тестируем сегодня, довольно проста. У нас есть интерфейс Operator
, который имеет два метода.
Generate
принимает два целых числа и создает ключ на основе некоторого шаблона, а Degenerate
берет ключ и декодирует целые числа, из которых был создан ключ.
Если ключ недействителен, Degenerate
выдает ошибку.
Теперь давайте создадим реализацию этого интерфейса под названием keyOp
.
Логика здесь довольно проста.
Мы предоставляем функцию GetKeyOperator
, которая создает новый экземпляр keyOp
с шаблоном %v_%v
.
Generate()
объединяет два целых числа в шаблон.
Degenerate()
преобразует ключ в соответствующие два целых числа. Если ключ не соответствует структуре шаблона, которая определяется позицией символа _
, мы возвращаем ошибку.
Теперь функция main может использовать эти методы для просмотра функциональности.
Как и ожидалось, результат такой:
key=2_3, a=2, b=3
Теперь, когда мы установили функциональность. Приступим к тестированию.
Тестирование
Сначала мы создадим файл с именем operators_test.go
в том же каталоге, что и operators.go
. Go автоматически ищет файлы в каталоге с суффиксом _test
при выполнении команды go test
.
Теперь давайте напишем тестовые примеры для наших Generate()
и Degenerate()
функций.
Для тестовых функций начните имя функции с Test_
, а затем предоставьте структуру для этого теста. Затем вы захотите написать название метода. Например, наши тестовые методы будут называться Test_keyOp_Generate
и Test_keyOp_Degenerate
соответственно.
Все методы тестирования должны принимать параметр testing.T
. T
- это тип, передаваемый функциям тестирования для управления состоянием тестирования и поддержки форматированных журналов тестирования.
Итак, сигнатура нашего метода такова:
func Test_keyOp_Generate(t *testing.T)
Аргументы для Generate
- два целых числа. Итак, давайте определим args
для хранения этих двух целых чисел.
type args struct { x int y int }
Теперь давайте определимся с требованиями нашего теста.
Мы структурируем тесты по их имени, а также аргументам в пользу теста и желаемому результату. Мы также добавим несколько таких тестов:
tests := []struct { name string args args want string }{ { name: "success", args: args{ x: 5, y: 50, }, want: "5_50", }, { name: "success large integers", args: args{ x: 50000, y: 999999, }, want: "50000_999999", }, }
Как видите, мы добавили два тестовых случая. Один для общего успеха, а другой для тестирования больших размеров ввода. Мы также определили правильный результат для этих тестовых случаев.
Теперь мы можем просто перебрать test
срез и запустить тесты.
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { kp := GetKeyOperator() if got := kp.Generate(tt.args.x, tt.args.y); got != tt.want { t.Errorf("keyOp.Generate() = %v, want %v", got, tt.want) } }) }
Мы используем метод Run
, указанный в структуре testing.T
. Run
принимает имя теста и функцию (func (t *testing.T)
) в качестве входных параметров и возвращает bool
. Он запускает функцию, указанную во втором параметре, в отдельной горутине и блокируется, пока не вернется.
В условии if
мы создаем и экземпляр kp
из keyOp
и проверяем, получили ли мы ожидаемый результат, проверяя got!=want
. Если тестовый пример терпит неудачу, мы записываем сообщение об ошибке в объект testing.T
, используя t.Errorf()
и правильно структурируя наше сообщение об ошибке.
Наша полная функция тестирования для Generate
находится здесь.
Теперь давайте попробуем проделать то же самое с методом Degenerate()
. Изменения здесь заключаются в том, что args
будет принимать только один string
в качестве параметра.
Кроме того, поскольку мы возвращаем три результата из Degenerate()
, мы проверим их с помощью переменных wantX
, wantY
и wantErr
. wantErr
- это bool
, который определит, получили ли мы ненулевую ошибку от тестируемого метода.
type args struct { s string } tests := []struct { name string args args wantX int wantY int wantErr bool }{ { name: "success", args: args{ s: "40_99", }, wantX: 40, wantY: 99, }, { name: "failure", args: args{ s: "4099", }, wantErr:true, }, }
Как видите, мы добавили два простых тестовых случая: один для успешной проверки, а другой, имеющий недопустимые входные данные, для проверки ошибки. В тесте success
мы проверяем правильные значения x
и y
с помощью wantX
и wantY
, в то время как в тесте failure
мы проверяем ненулевую ошибку с помощью wantErr
.
Мы снова пишем цикл for, который перебирает часть тестов и проверяет совпадения на предмет ожидаемых результатов. На этот раз мы проверяем все три параметра. Полная функция тестирования для Degenerate
находится здесь:
Запуск тестовых случаев
Мы можем запустить наши тестовые примеры с помощью команды test
. go test
имеет множество функций, которые можно найти в его подробной документации здесь.
Самое простое использование go test
- запускать все тесты в текущем каталоге и во всех подкаталогах. Это можно сделать, выполнив следующую команду:
go test ./...
Заключение
Я надеюсь, что это руководство послужит простым введением в методы модульного тестирования Golang.