От мощных тестовых флагов до покрытия кода и тестирования производительности!

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

В этой статье мы подробно остановимся на следующем:

  • Запуск базовых тестов в Go
  • Использование тестовых флагов
  • Бенчмаркинг и производительность
  • Создание покрытия кода

Давайте научимся Go!

Базовый набор тестов

Начнем с нуля. Чтобы запустить наши тесты, нам нужно будет перейти в нашу исходную папку Go. Оттуда мы можем создать новую папку, которую будем использовать в качестве песочницы для наших тестов. Давайте также создадим два каталога parent с подкаталогом child. Наконец, мы добавим два тестовых файла parent_test.go и child_test.go. Добавление «_test» в конец файла Go сигнализирует компилятору, что мы хотим запускать модульные тесты из этого источника.

$ go env GOPATH
/Users/israelmiles/go
$ cd /Users/israelmiles/go/src
$ mkdir testing-go
$ cd testing-go
$ mkdir parent
$ mkdir parent/child
$ touch parent/parent_test.go
$ touch parent/child/child_test.go

Если вам нужна помощь в настройке GOPATH, посмотрите здесь:



Как установить переменную среды GOPATH в Ubuntu? Какой файл мне нужно отредактировать?
Вы можете использовать решение «экспорт
точно так же, как то, что предлагали другие ребята. Я хотел бы предоставить вам еще один… stackoverflow.com »



У нас будут каталоги parent и child для демонстрации различных методов тестирования. Сначала давайте добавим следующий код в наш parent_test.go файл.

parent_test.go

В нашем parent пакете наш первый шаг - импортировать testing пакет, который дает нам доступ к различным методам тестирования. Как видите, мы берем ссылку на объект testing.T для нашей тестовой функции TestMath(). Затем эта функция запускает два разных теста на сложение и вычитание на ходу. Структура t.Run() должна включать имя (добавление на ходу) и анонимную функцию, которая принимает ту же ссылку на testing.T. Если мы не выполняем некоторые установленные нами условия, мы вызываем t.Fail() в качестве опции, чтобы продемонстрировать результаты наших тестов.

Метод t.Run() может быть вызван одновременно из нескольких горутин, но все такие вызовы должны возвращаться до того, как функция внешнего тестирования для объекта тестирования t вернется.

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

пройти тест

Если мы войдем в каталог parent и запустим go test, компилятор автоматически запустит все файлы, заканчивающиеся на _test.go.

$ pwd
/Users/israelmiles/go/src/testing-go/parent
$ go test
PASS
ok   testing-go/parent 0.220s

Круто, наши тесты прошли! Но что, если нам нужна дополнительная информация? Мы можем включить флаг -v для подробного описания, чтобы лучше понять, как выполнялись наши тесты.

$ go test -v
=== RUN   TestMath
=== RUN   TestMath/Addition_in_Go
=== RUN   TestMath/Subtraction_in_Go
--- PASS: TestMath (0.00s)
    --- PASS: TestMath/Addition_in_Go (0.00s)
    --- PASS: TestMath/Subtraction_in_Go (0.00s)
=== RUN   TestStrings
=== RUN   TestStrings/Concatenation_in_Go
--- PASS: TestStrings (0.00s)
    --- PASS: TestStrings/Concatenation_in_Go (0.00s)
PASS
ok   testing-go/parent 0.085s

Теперь мы можем увидеть, какие функции были запущены с каждым отдельным тестом внутри. Мы также можем запустить набор рекурсивных тестов, если у нас многоуровневая структура каталогов.

Если вы хотите запустить одиночный тест, вы можете использовать флаг -run.

$ pwd
/Users/israelmiles/go/src/testing-go/parent
$ go test -v -run TestMath
=== RUN   TestMath
=== RUN   TestMath/Addition_in_Go
=== RUN   TestMath/Subtraction_in_Go
--- PASS: TestMath (0.00s)
    --- PASS: TestMath/Addition_in_Go (0.00s)
    --- PASS: TestMath/Subtraction_in_Go (0.00s)
PASS
ok   testing-go/parent 0.243s

Вы также можете указать пакет для поиска тестов для запуска. Структура тогда go test -run <Test expression> <packages to search>.

Если вы хотите запустить тест несколько раз, вы можете использовать флаг -count. Это может быть полезно, если вы, например, проводите испытания на выносливость.

$ go test -count 1 testing-go/parent
ok   testing-go/parent 0.079s
$ go test -count 10 testing-go/parent
ok   testing-go/parent 0.310s
Israels-MacBook-Pro:parent israelmiles$

child_test.go

В нашем каталоге parent находится каталог child, включая child_test.go. Вот пример теста, который мы можем добавить в файл:

Здесь у нас есть еще более простая настройка, когда мы просто проверяем условие, а если оно ложно, мы вызываем метод Error() из нашего объекта тестирования.

Рекурсивное тестирование

Мы можем запустить рекурсивный тест с go test, добавив /... в конце пакета для тестирования. Например:

$ pwd
/Users/israelmiles/go/src/testing-go
$ go test -v testing-go/parent/...
=== RUN   TestMath
=== RUN   TestMath/Addition_in_Go
=== RUN   TestMath/Subtraction_in_Go
--- PASS: TestMath (0.00s)
    --- PASS: TestMath/Addition_in_Go (0.00s)
    --- PASS: TestMath/Subtraction_in_Go (0.00s)
=== RUN   TestStrings
=== RUN   TestStrings/Concatenation_in_Go
--- PASS: TestStrings (0.00s)
    --- PASS: TestStrings/Concatenation_in_Go (0.00s)
PASS
ok   testing-go/parent (cached)
=== RUN   TestMultiplication
--- PASS: TestMultiplication (0.00s)
PASS
ok   testing-go/parent/child 0.295s

Теперь вы знаете, как:

  • Запускайте тесты с go test
  • Выполните тесты несколько раз с флагом -count
  • Создавайте тесты с testing.T.Run()
  • Распечатайте тестовую информацию с флагом -v
  • Выполните одиночный тест с флагом -run
  • Выполните набор рекурсивных тестов, добавив /... к вашему пакету

Сравнительный анализ ваших тестов

Давайте расширим структуру нашего проекта. Мы добавим три файла в корневую папку нашего проекта, чтобы вычислить и проверить последовательность Фибоначчи, используя структуру цикла и рекурсивный формат.

fibonacci.go

Два способа найти последовательность Фибоначчи с помощью срезов и рекурсии.

fibonacci_test.go

Здесь у нас есть первые 12 значений последовательности Фибоначчи для проверки. Затем мы вызываем два теста для цикла и рекурсивные методы вычисления последовательности.

main.go

Файл драйвера main.go simple вызывает методы фибоначчи по 10 раз каждый, используя strconv.Itoa() для преобразования из int в строку. Вы можете запустить это, чтобы увидеть вывод каждой функции Фибоначчи.

Таким образом, выполнение наших тестов с go test -v даст:

=== RUN   TestFib
=== RUN   TestFib/Fibonnaci_Loop
=== RUN   TestFib/Fibonnaci_Recursion
--- PASS: TestFib (0.00s)
    --- PASS: TestFib/Fibonnaci_Loop (0.00s)
    --- PASS: TestFib/Fibonnaci_Recursion (0.00s)
PASS
ok   testing-go 0.092s

Бенчмаркинг

Если вы хотите получить представление о том, насколько эффективны ваши тесты, вы можете использовать флаг -bench в дополнение к созданию метода тестирования. Сначала добавьте приведенный ниже код в fibonacci_test.go. Чтобы создать тестовый метод, просто начните имя функции с Benchmark и возьмите ссылку на testing.B в качестве параметра. Значение b.N назначается компилятором и представляет собой количество запусков FibonacciLoop.

Выше наш первый тест выполняется с помощью метода FibonacciLoop() для первого значения последовательности. Второй тестовый метод в строке 7 предназначен для 100-го значения последовательности Фибоначчи. Если мы проведем сравнительный анализ тестов, мы увидим, как становится все труднее вычислять последовательность Фибоначчи.

$ go test -v -bench Fib
=== RUN   TestFib
=== RUN   TestFib/Fibonnaci_Loop
=== RUN   TestFib/Fibonnaci_Recursion
--- PASS: TestFib (0.01s)
    --- PASS: TestFib/Fibonnaci_Loop (0.00s)
    --- PASS: TestFib/Fibonnaci_Recursion (0.01s)
goos: darwin
goarch: amd64
pkg: testing-go
BenchmarkFibonacciLoop1
BenchmarkFibonacciLoop1-8      43454248         25.2 ns/op
BenchmarkFibonacciLoop100
BenchmarkFibonacciLoop100-8     4285092        277 ns/op
PASS
ok   testing-go 2.694s

Посмотрите, как наше регулярное выражение Fib выполняет оба FibonacciLoop метода? Не только это, но мы можем видеть, что вычисление 1-го значения последовательности Фибоначчи занимает около 25,2 наносекунды на вызов, в то время как 100-е значение последовательности Фибоначчи занимает около 277 наносекунд на вызов!

Кроме того, мы можем увидеть, сколько памяти используют наши функции, включив флаг -benchmem.

$ go test -v -bench . -benchmem
=== RUN   TestFib
=== RUN   TestFib/Fibonnaci_Loop
=== RUN   TestFib/Fibonnaci_Recursion
--- PASS: TestFib (0.01s)
    --- PASS: TestFib/Fibonnaci_Loop (0.00s)
    --- PASS: TestFib/Fibonnaci_Recursion (0.01s)
goos: darwin
goarch: amd64
pkg: testing-go
BenchmarkFibonacciLoop1
BenchmarkFibonacciLoop1-8   42072594 24.9 ns/op 32 B/op  1 allocs/op
BenchmarkFibonacciLoop100
BenchmarkFibonacciLoop100-8 4268587  280 ns/op  896 B/op 1 allocs/op
PASS
ok   testing-go 2.780s

Теперь мы видим, что 1-е значение последовательности занимает 32 байта на вызов, а 100-е значение требует 896 байтов. Как вы думаете, сколько времени занимает рекурсивный метод?

Создание покрытия кода

Хорошо, вы уже многому научились! Давайте остановимся на последнем полезном инструменте для тестирования на Go. Если мы хотим увидеть, какая часть нашего кода покрыта тестовыми примерами, мы можем использовать флаг -cover.

$ go test -v -cover testing-go
=== RUN   TestFib
=== RUN   TestFib/Fibonnaci_Loop
=== RUN   TestFib/Fibonnaci_Recursion
--- PASS: TestFib (0.01s)
    --- PASS: TestFib/Fibonnaci_Loop (0.00s)
    --- PASS: TestFib/Fibonnaci_Recursion (0.01s)
PASS
coverage: 64.7% of statements
ok   testing-go 0.112s

Таким образом, мы покрываем 64,7% всего кода в fibonacci.go и main.go. Если бы вы избавились от операторов печати в main.go, вы бы увеличили охват до 73,3%. Дальнейшие целевые тестовые примеры против методов Фибоначчи еще больше увеличат ваш охват.

Примечание. Если у вас произошел сбой теста, флаг -cover фактически перезапишет ваш исходный код перед его компиляцией (не спрашивайте меня, как). Поэтому не используйте флаг -cover вместе с какими-либо тестами производительности.

Но какие части моего кода покрыты?

Если вы хотите увидеть, какие из операторов вашего кода охватываются тестами, вы можете включить флаг -coverprofile. Для этого требуется файл для добавления информации, который мы назовем cover.out. Если бы вы попытались сразу прочитать cover.out, вы бы не получили много полезной информации.

$ go test -coverprofile cover.out
$ cat cover.out
mode: set
testing-go/fibonnaci.go:4.31,6.14 2 1
testing-go/fibonnaci.go:9.5,11.29 3 1
testing-go/fibonnaci.go:14.5,14.16 1 1
testing-go/fibonnaci.go:6.14,8.6 1 1
testing-go/fibonnaci.go:11.29,13.6 1 1
testing-go/fibonnaci.go:18.36,19.15 1 1
testing-go/fibonnaci.go:22.5,22.61 1 1
testing-go/fibonnaci.go:19.15,21.6 1 1
testing-go/main.go:3.13,4.29 1 0
testing-go/main.go:7.5,7.29 1 0
testing-go/main.go:4.29,6.6 1 0
testing-go/main.go:7.29,9.6 1 0

Чтобы увидеть наше освещение в более удобном формате, используйте команду go tool cover. Если вы вызываете флаг -html, вы можете назначить его нашему новому выходному файлу cover.out, чтобы сгенерировать исчерпывающий отчет.

$ go tool cover -html=cover.out

Затем откроется сводка покрытия в вашем веб-браузере по умолчанию:

Как мы видим, методы Фибоначчи полностью покрыты! Но если мы посмотрим на основной файл Go ...

Мы видим, что это на самом деле источник нашей недостаточной освещенности.

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