четверг, 4 июня 2020 г.

Пакет testing в Golang

Пакет testing обеспечивает поддержку автоматического тестирования пакетов Go. Он предназначен для использования совместно с командой "go test", которая автоматизирует выполнение любой функции формы

func TestXxx(*testing.T)

где Xxx не начинается со строчной буквы. Имя функции служит для идентификации процедуры тестирования.

В этих функциях используйте Error, Fail или связанные методы, чтобы сигнализировать о сбое.

Чтобы написать новый набор тестов, создайте файл, имя которого заканчивается _test.go и содержит функции TestXxx, как описано здесь. Поместите файл в тот же пакет, что и тестируемый. Файл будет исключен из обычных сборок пакетов, но будет включен при запуске команды "go test".

Простая тестовая функция выглядит так:

func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}

Бенчмарки

Функции формы

func BenchmarkXxx(*testing.B)

считаются бенчмарками и выполняются командой "go test", если указан флаг -bench. Тесты запускаются последовательно.

Пример бенчмарк функции выглядит следующим образом:

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

Бенчмарк функция должна запускать целевой код b.N раз. Во время выполнения бенчмарка b.N корректируется до тех пор, пока функция бенчмарка не продержится достаточно долго, чтобы быть надежно синхронизированной. Вывод

BenchmarkHello    10000000    282 ns/op

означает, что цикл выполнялся 10000000 раз со скоростью 282 нс на цикл.

Если перед запуском бенчмарка требуется дорогостоящая настройка, таймер может быть сброшен:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

Если бенчмарку нужно тестировать производительность в параллельном режиме, он может использовать вспомогательную функцию RunParallel; такие тесты предназначены для использования с флагом go test -cpu:

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

Примеры (Examples)

Пакет также запускается и проверяет пример кода. Функции example могут включать заключительную строку комментария, которая начинается с "Output:" и сравнивается со стандартным выводом функции при выполнении тестов. (Сравнение игнорирует начальные и конечные пробелы.) Это примеры example:

func ExampleHello() {
    fmt.Println("hello")
    // Output: hello
}

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

Префикс комментария "Unordered output:" похож на "Output:", но соответствует любому порядку строк:

func ExamplePerm() {
    for _, value := range Perm(5) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

Example функции без выходных комментариев компилируются, но не выполняются.

Соглашение об именах для объявления примеров для пакета, функции F, типа T и метода M для типа T:

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

Несколько примеров функций для пакета/типа/функции/метода могут быть предоставлены путем добавления отдельного суффикса к имени. Суффикс должен начинаться со строчной буквы.

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

Весь тестовый файл представлен в качестве примера (example), когда он содержит одну example функцию, по крайней мере, одну другую функцию, тип, переменную или объявление константы, и не содержит тестовых или бенчмарк функций.

Пропуски

Тесты или бенчмарки могут быть пропущены во время выполнения с помощью вызова Skip метода *T или *B:

func TestTimeConsuming(t *testing.T) {
    if testing.Short() {
        t.Skip("пропуск теста в сокращенном режиме.")
    }
    ...
}

Субтесты и суб-бенчмарки

Методы Run для T и B позволяют определять субтесты и суб-бенчмарки без необходимости определять отдельные функции для каждого. Это позволяет использовать табличные бенчмарки и создавать иерархические тесты. Это также предоставляет возможность поделиться общим кодом установки и удаления (очистки):

func TestFoo(t *testing.T) {
    // установочный код
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // код очистки
}

Каждый субтест и суб-бенчмарк имеет уникальное имя: сочетание имени теста верхнего уровня и последовательности имен, передаваемых в Run, разделенных косой чертой, с необязательным конечным порядковым номером для устранения неоднозначности.

Аргумент для флагов командной строки -run и -bench - это нефиксированное регулярное выражение, которое соответствует имени теста. Для тестов с несколькими элементами, разделенными слешем, такими как субтесты, аргумент сам разделен слэшем, а выражения по очереди соответствуют каждому элементу имени. Поскольку оно не занято, пустое выражение соответствует любой строке. Например, использование "соответствия" ("matching") означает "чье имя содержит":

go test -run ''      # запускает все тесты
go test -run Foo     # запускает верхнеуровневые тесты соотвествующие "Foo", такие как "TestFooBar".
go test -run Foo/A=  # запускает верхнеуровневые тесты соотвествующие "Foo", запускает субтесты соотвествующие "A=".
go test -run /A=1    # запускает все верхнеуровневые тесты, запускает субтесты соотвествующие "A=1".

Субтесты также могут быть использованы для контроля параллелизма. Родительский тест будет завершен только после завершения всех его субтестов. В этом примере все тесты выполняются параллельно друг другу и только друг с другом, независимо от других тестов верхнего уровня, которые могут быть определены:

func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // захватить переменную диапазона
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            ...
        })
    }
}

Детектор гонки убивает программу, если она превышает 8192 конкурентных goroutine, поэтому будьте осторожны при выполнении параллельных тестов с установленным флагом -race.

Выполнение не возвращается до тех пор, пока параллельные субтесты не будут завершены, предоставляя способ очистки после группы параллельных тестов:

func TestTeardownParallel(t *testing.T) {
    // Этот Run не вернется, пока не завершатся параллельные тесты.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // код очистки
}

Main

Иногда тестовой программе необходимо выполнить дополнительную настройку или демонтаж до или после тестирования. Иногда тесту также необходимо контролировать, какой код выполняется в основном (main) потоке. Для поддержки этих и других случаев, если тестовый файл содержит функцию:

func TestMain(m *testing.M)

тогда сгенерированный тест вызовет TestMain(m) вместо непосредственного запуска тестов. TestMain работает в основной программе и может выполнять любые настройки и разборки, необходимые для вызова m.Run. Затем он должен вызвать os.Exit с результатом m.Run. Когда вызывается TestMain, flag.Parse не был запущен. Если TestMain зависит от флагов командной строки, включая флаги пакета тестирования, он должен явно вызывать flag.Parse.

Простая реализация TestMain:

func TestMain(m *testing.M) {
    // вызываем flag.Parse() здесь, 
    // если TestMain использует флаги
    os.Exit(m.Run())
}


Читайте также:


Комментариев нет:

Отправить комментарий