От мощных тестовых флагов до покрытия кода и тестирования производительности!
Каждому инженеру-программисту необходимо знать, как проводить тестирование на выбранном им языке. Даже простой уровень модульных тестов может значительно повысить устойчивость программы и гарантировать, что она работает так, как ожидает инженер. 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, есть еще много информации, но в этой статье рассматриваются основные основы, которые вам нужны для начала работы. Если вы увидели что-нибудь примечательное или хотели бы узнать больше о разделе статьи, я рекомендую вам оставить комментарий ниже! Спасибо за прочтение.