Автор статьи: Рустем Галиев

IBM Senior DevOps Engineer & Integration Architect. Официальный DevOps ментор и коуч в IBM

Привет Хабр!

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

Тестирование — это первостепенная задача в Go. Пакет тестирования предоставляет API для написания тестового кода. Опишу упражнение, в котором мы реализуем и выполним базовые тестовые случаи с помощью стандартного пакета тестирования. Выполним их с помощью набора инструментов Go.

Шаг первый:

  • Внедрим тесты с помощью стандартного пакета тестирования.

  • Выполним тесты из командной строки с помощью команды go test.

Основы тестирования

Для многих языков программирования тестирование является дополнительной функцией. Это не относится к Go. Пакет тестирования встроен в среду выполнения Go и предлагает все необходимое для написания сложного тестового кода. API прост для понимания и в то же время позволяет избежать синтаксического сахара.

Тестовый код находится в файле исходного кода, отличном от рабочего исходного кода. По конвенции эти исходные файлы имеют файл, заканчивающийся на _test.go. Например, для файла Go http.go вы должны создать тестовый файл с именем http_test.go. Нет специальной директории для тестового кода. Файл просто живет вместе с производственным исходным кодом.

При написании тестового кода необходимо учитывать всего несколько шагов:

  • В список импорта необходимо добавить пакет тестирования.

  • Каждый тестовый пример представлен функцией.

  • Функция должна быть экспортирована, а ее имя должно начинаться с префикса Test.

  • Функция имеет единственный параметр, который ссылается на тип testing.T. Параметры предоставляют вам доступ к соответствующему API для реализации утверждений.

Приступим. Сперва создадим файл hello.go со следующим содержимым

package main

import "errors"

func Hello(name string, language string) (string, error) {
    if name == "" {
   	 name = "World"
    }

    prefix := ""

    switch language {
    case "english":
   	 prefix = "Hello"
    case "spanish":
   	 prefix = "Hola"
    case "german":
   	 prefix = "Hallo"
    default:
   	 return "", errors.New("need to provide a supported language")
    }

    return prefix + " " + name, nil
}

Экспортируемая функция Hello принимает два параметра. Параметра “name”  представляет имя человека, а параметр “language” определяет языковой префикс, используемый для приветствия человека. Например, для параметров «Ben» и «English» в возвращаемой строке будет «Hello, Ben». Логика может вернуть ошибку, если язык не поддерживается.

Давайте напишем тестовый пример для этого самого варианта использования в файле с именем hello_test.go. Как вы можете видеть в приведенном ниже примере кода, мы вызываем исходный код (в данном случае функцию Hello) и затем проверяем его результат. Если результат не соответствует вашим ожиданиям, вы можете отметить это с помощью вызова функции t.Error*. Вызов t.Error* сообщает об ошибке теста, но продолжает выполнение набора тестов до тех пор, пока не будут выполнены все тестовые кейсы:

package main

import "testing"

func TestEnglish(t *testing.T) {
	name := "Ben"
	language := "english"
	expected := "Hello Ben"
	actual, err := Hello(name, language)

	if err != nil {
    	t.Errorf("Should not produce an error")
	}

	if expected != actual {
    	t.Errorf("Result was incorrect, got: %s, want: %s.", actual, expected)
	}
}

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

Но теперь прогоним наши тесты

Возможность выполнять тесты связана с тулчейном Go. Чтобы выполнить тесты, просто запустим исполняемый файл go с помощью команды test. В качестве последнего параметра команды нам нужно будет указать пакеты, которые вы хотите протестировать. Вместо предоставления конкретного пакета я часто просто рекурсивно выполняю все тесты из корневой директории. Полная команда для такого поведения — go test ./….

Мы имеем дело только с основным пакетом, который содержит один тест. Выполним тесты с помощью следующей команды. Также мы используем параметр -v для отображения подробного вывода. Все тесты должны пройти:

go test -v

Отлично, идем дальше, задействуем Testify.

Стандартный пакет тестирования предоставляет API для написания тестового кода. Однако API не хватает, когда дело доходит до предоставления удобных методов и синтаксического сахара. Testify — это пакет с открытым исходным кодом, упрощающий написание логики утверждений. Сейчас мы реализуем и выполним базовые тестовые примеры с помощью testify.

Сперва добавим testify с помощью команды go get:

go get github.com/stretchr/testify

Следующий код (также hello_test.go) импортирует пакет assert из файла testify. Его API предлагает множество удобных методов, которые упрощают написание утверждений. Здесь мы проверяем нулевое значение ошибки с помощью assert.Nil и равенство фактического с ожидаемым возвращаемым значением через assert.Equal:

package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestEnglish(t *testing.T) {
	name := "Ben"
	language := "english"
	expected := "Hello Ben"
	actual, err := Hello(name, language)

	assert.Nil(t, err)
	assert.Equal(t, expected, actual)
}

И запустим тест: go test -v

Ну и перейдем к созданию метрик покрытия кода и репортов.

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

Немного теории

Показатели покрытия кода отвечают на вопрос: «Какая часть нашего кода была проверена тестами?» В большинстве случаев нереально стремиться к 100% охвату, поскольку вы потратите много усилий на написание тестов. Более практичный подход — стремиться к 70–75% охвата. Этот процент дает вам достаточную уверенность, чтобы вносить изменения в рабочий исходный код, не ломая его немедленно. Наиболее часто используемые критерии покрытия кода следующие:

  • Покрытие операторов (также известное как покрытие строк): какой оператор/строка в блоке кода был покрыт?

  • Покрытие ветвей: какой путь выполнения был охвачен?

  • Покрытие условий: оценивается ли каждое булево подвыражение как истинное, так и ложное?

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

Помня об этой теории, давайте рассмотрим поддержку покрытия кода в Go.

Тулчеин Go имеет встроенные возможности для создания показателей покрытия кода. Мы уже использовали команду go test раньше. Все, что вам нужно сделать, чтобы зафиксировать показатели покрытия кода, — указать параметр командной строки -cover. Метрики будут отображаться прямо на терминале:

go test ./... -cover

Вы также можете записать метрики в файл для дальнейшей обработки. Следующая команда записывает файлы в cover.txt:

go test ./... -coverprofile=coverage.txt

После выполнения команды вы должны найти файл метрик. Обычно не нужно смотреть непосредственно на текстовый файл, но вы можете заглянуть в его структуру:

Итак, каким критериям покрытия кода следует Go? Критерии команды Go называются режимами покрытия и могут быть установлены с помощью параметра командной строки -covermode. Вот общий обзор:

  • set: Выполнялся ли каждый оператор?

  • count: сколько раз выполнялся каждый оператор?

  • atomic: работает как count, но считает точно в параллельных программах

Режим покрытия по умолчанию — это покрытие операторов, фактически установленное значение. Это критерии, которые мы применяли с предыдущей командой go test. Не стесняйтесь исследовать другие режимы покрытия и определить, как меняются числа. На следующем шаге вы узнаете, как превратить метрики в визуальное представление.

Обычно удобнее видеть метрики в визуальном представлении. Тулчеин Go предлагает способ превратить метрики из текстового файла в отчет в формате HTML. Отчет в формате HTML отображает представление метрик с цветовой кодировкой в статическом файле HTML.

Давайте попробуем. Следующая команда создает файл HTML с именем index.html.

go tool cover -html coverage.txt -o index.html

Существуют и другие варианты визуализации метрик покрытия кода. IDE может отображать метрики непосредственно в коде. Хорошими вариантами являются JetBrains GoLand и Microsoft Visual Studio Code с установленным плагином Go. Кроме того, вы также можете отправить метрики на стороннюю платформу, которая предоставляет расширенные функции визуализации. Coveralls и Codecov — лишь некоторые из них.

Напоследок хочу порекомендовать бесплатный вебинар от моих коллег из OTUS, на котором вы познакомитесь с профессией Тестировщика ПО, узнаете, где работают тестировщики и увидите какие задачи стоят перед тестировщиком в течение рабочего дня.

Зарегистрироваться на бесплатный вебинар

Комментарии (3)


  1. Plesser
    02.06.2023 11:46
    +2

    поправьте меня если я не прав, но не только файл должен иметь суффикс _test но и сама функция должна иметь префикс с TestXXXX, причем с заглавной буквы (видимо это связано с правами доступа к ней).

    ps Я только изучаю Golang, и вчера как раз слегка касался темы тестирования


  1. sedyh
    02.06.2023 11:46

    Здесь мы проверяем нулевое значение ошибки с помощью assert.Nil и равенство фактического с ожидаемым возвращаемым значением через assert.Equal

    Советую прочитать про разницу между assert и require, первый не остановит тест и вы можете получить npe в следующих проверках.


  1. GradeBuilder
    02.06.2023 11:46

    Не пробовали ли вы прикручивать AllureGo для красивого вывода метрик, ну и вообще как готовый тестовый фреймворк?