четверг

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


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

Просмотр результатов


В нашем примере тестовое покрытие было плохим. Чтобы выяснить почему, мы попросим go test записать для нас в файл “профиль покрытия”, который содержит собранные статистические данные, которые мы можем изучить более детально. Это делается легко, используйте флаг -coverprofile и файл для выхода:
% go test -coverprofile=coverage.out 
PASS
coverage: 42.9% of statements
ok      size    0.030s
%
(Флаг -coverprofile автоматически устанавливает -cover, чтобы включить анализ покрытия.) Тестовые прогоны будет такие же как раньше, но результат сохранится в файл. Чтобы изучить результат, мы запустим инструмент тестового покрытия без go test. При запуске мы можем попросить cover сделать разбитие на функции, но в данном случае это не будет показано, так как используется только одна функция:
% go tool cover -func=coverage.out
size.go:    Size          42.9%
total:      (statements)  42.9%
%
Намного более интересный способ просмотра данных заключается в использовании HTML презентации исходного кода, украшенного информацией о покрытии. Она вызывается флагом -html:


$ go tool cover -html=coverage.out
Когда выполняется эта команда, раскрывается окно браузера, показывая покрытый (зеленый), непокрытый (красный) и неотслеживаемый (серый) исходный код. Вот скриншот:

go tool cover -html=coverage.out

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

Больше возможностей


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

Команда go test включает флаг -covermode, который устанавливает один из трех режимов настроек:

- set: каждый оператор был выполнен?

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

- atomic: как count, но в параллельных программах.

По умолчанию установлен “set”, который вы уже видели. atomic нужен для точного подсчета сколько раз выполнялся оператор в параллельных алгоритмов. Для atomic используются операции из пакета sync/atomic, который может быть довольно дорогим. Для большинства целей режим count работает хорошо и также не дорог как режим по умолчанию set.

Давайте попробуем сосчитать операторы в стандартном пакете форматирования fmt. Мы запускаем тест и записываем профиль покрытия, что позволит в дальнейшем представить информацию в удобном для восприятия виде.
% go test -covermode=count -coverprofile=count.out fmt 
ok      fmt    0.056s    coverage: 91.7% of statements
%
Тут намного лучший коэффициент тестовое покрытие, чем было в нашем прошлом примере. (На коэффициент покрытия не влияет режим покрытия.) Мы можем вывести с разбивкой по функциям:
% go tool cover -func=count.out
fmt/format.go: init              100.0%
fmt/format.go: clearflags        100.0%
fmt/format.go: init              100.0%
fmt/format.go: computePadding     84.6%
fmt/format.go: writePadding      100.0%
fmt/format.go: pad               100.0%
...
fmt/scan.go:   advance            96.2%
fmt/scan.go:   doScanf            96.8%
total:         (statements)       91.7%
Больший выигрыш происходит при выводе HTML:
% go tool cover -html=count.out
Вот как выглядит функция pad в таком представлении:

go tool cover -html=count.out с режимом тестового покрытия count

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

Мы написали количество вызовов в начале каждой строки:
2933    if !f.widPresent || f.wid == 0 {
2985        f.buf.Write(b)
2985        return
2985    }
  56    padding, left, right := f.computePadding(len(b))
  56    if left > 0 {
  37        f.writePadding(left, padding)
  37    }
  56    f.buf.Write(b)
  56    if right > 0 {
  13        f.writePadding(right, padding)
  13    }
Такое множество информации о выполнении функции может быть использовано для оптимизации кода.

Базисные блоки


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

Но всё же это стоит объяснить. Мы хотели бы, чтобы аннотации покрытия переходили в программе тем же способом как традиционные инструменты с двоичными файлами. Такое трудно сделать, переписывая исходный код, так как ответвления не появляются явно в исходном коде.

То что делает аннотация покрытия является инструментальными блоками, которые обычно граничат с фигурными скобками. Сделать это правильно обычно бывает сложно. Результатом использования такого алгоритма является, то что закрывающая фигурная скобка принадлежит блоку, который закрывает в то время как открывающая фигурная скобка кажется, что находится вне блока. Более интересным следствием является подобное выражение:
f() && g()
Нет никакой возможности отделить вызов f от g. Независимо от этого, они всегда будут выполняться одинаковое количество раз.

Чтобы быть справедливыми, у gcov тоже есть с этим проблемы. Этот инструмент работает правильно, но представление основано на строке и поэтому могут быть упущены некоторые нюансы.

Итог


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

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


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

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

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