четверг

Тестовое покрытие программного кода в Go. Часть Первая.


Тестовое покрытие программного кода в Go. Часть вторая.

Введение


С самого начала проект Go, разрабатывался с учетом инструментов (tools). В эти инструменты включается инструмент для представление документации godoc, инструмент для форматирования кода gofmt и gofix для автоматического переписывания кода при изменении API. Возможно самым важным из всех является инструмент go, который автоматически инсталлирует, собирает и тестирует Go программы, используя для этого ничто иное как исходный код.

В релиз Go 1.2 включен новый инструмент для тестового покрытия, который использует необычный подход к тому каким образом генерируется статистика покрытия, подход, который основывается на техники составленной godoc и друзьями.

Поддержка инструментов


Сначала некоторый фон: Что для языка означает поддержка хорошего инструментария? Это означает, что язык делает простым написание хороших инструментов и что его экосистема поддерживает все разнообразие инструментов.

У Go есть много свойств, которые делают его подходящим для создания инструментария. Для начинающих у Go есть правильный синтаксис, который делает простым анализ исходного кода. Грамматика языка стремится к тому, чтобы не было особых случаев, требующих сложного разбора.

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

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


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

Примером является инструмент gofix, который автоматически переписывает код, используя новые функции языка и обновленные библиотеки. Gofix позволяет нам делать фундаментальные изменения в языке и библиотеках с момента выхода версии Go 1.0 с уверенностью, что потребители могли бы запустить этот инструмент, чтобы обновить свой исходный код до последней версии.

Внутри Google мы использовали gofix для осуществления изменений в огромном репозитарии кода, что было бы почти невероятно для других языков программирования, которые мы используем. Теперь нет никакой необходимости поддерживать множество версий некоторых API. Мы можем использовать gofix, чтобы за один раз обновить код во всей компании.

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

Тестовое покрытие (test coverage)


Тестовое покрытие - это термин, который описывает для какого количества кода из пакета осуществляется тестирование. Если тестирование охватывает 80% операторов исходного кода пакета, то мы говорим, что тестовое покрытие 80%.

Программа, обеспечивающая тестовое покрытие в Go 1.2, использует самые последнею поддержку инструментария в экосистеме Go.

Обычным способом вычисления тестового покрытия является использование бинарного файла. Например, программа GNU gcov для установки контрольных точек при ответвлениях выполняет бинарный файл. Так как каждое ответвление выполняется, то контрольная точка очищается и целевой оператор ответвления отмечается как “покрыт”.

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

Это работает. Если вы используете gccgo, то инструмент gcov может дать вам информацию о тестовом покрытии. Однако, если вы пользуетесь gc, где обычно используется компилятор Go, до версии Go 1.2, то вам не повезло.

Тестовое покрытие для Go


Для нового инструмента тестового покрытия для Go, мы использовали другой подход, который избегает динамической отладки. Идея проста: Переписать исходный код пакета перед компиляцией, чтобы добавить измененный код в инструментарий, компилятор и запуск и вывести статистику. Переписать легко, потому что команда go контролирует поток от исходного кода к тестированию и к исполняемому файлу.

Вот пример. Скажем, что у нас есть простой пакет с одним файлом:

package size

func Size(a int) string {
    switch {
    case a < 0:
        return "negative"
    case a == 0:
        return "zero"
    case a < 10:
        return "small"
    case a < 100:
        return "big"
    case a < 1000:
        return "huge"
    }
    return "enormous"
}
а вот тест:
package size

import "testing"

type Test struct {
    in  int
    out string
}

var tests = []Test{
    {-1, "negative"},
    {5, "small"},
}

func TestSize(t *testing.T) {
    for i, test := range tests {
        size := Size(test.in)
        if size != test.out {
            t.Errorf("#%d: Size(%d)=%s; want %s", i, test.in, size, test.out)
        }
    }
}
Для того, чтобы получить тестовое покрытие для пакета, вы выполняете тест с включенным покрытием, что обеспечивает go test с флагом -cover:
% go test -cover
PASS
coverage: 42.9% of statements
ok      size    0.026s
%  
Обратите внимание на покрытие 42.9%, что не очень хорошо. Перед тем как вы спросите, как увеличить это число, давайте посмотрим как оно вычисляется. Когда включено тестовое покрытие, то go test запускает инструмент “покрытие”, который является отдельной программой, переписывающей исходный код перед компиляцией. Вот на что походит переписанная функция Size:
func Size(a int) string {
    GoCover.Count[0] = 1
    switch {
    case a < 0:
        GoCover.Count[2] = 1
        return "negative"
    case a == 0:
        GoCover.Count[3] = 1
        return "zero"
    case a < 10:
        GoCover.Count[4] = 1
        return "small"
    case a < 100:
        GoCover.Count[5] = 1
        return "big"
    case a < 1000:
        GoCover.Count[6] = 1
        return "huge"
    }
    GoCover.Count[1] = 1
    return "enormous"
}
Каждый раздел программы аннотируется оператором присвоения, который, когда выполняется, записывает запущенные разделы. Счетчик связывается с первоначальной позицией операторов в исходном коде и считает доступные только для чтения данные, которые также генерируются инструментом покрытия. Когда завершается тестовый прогон, счетчики собираются и вычисляется процент покрытия. Хотя аннотирование присвоения может показаться дорогостоящей, компилируется единственная инструкция “move”. Такие накладные расходы в рабочем режиме являются умеренными, добавляя всего 3% при обычном выполнении теста. Это делает приемлемым включение тестового покрытия в стандартный цикл разработки.


© Перевод: www.golangpro.ru

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

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