Означает ли хорошее покрытие юнит-тестами, что система работает так, как ожидалось? Не может быть. Несколько независимых единиц кода совместно выполняют данную задачу. Когда мы смотрим на более широкую картину всего приложения, мы должны думать о поведении системы и о том, покрываются ли все линии как часть тестового покрытия, а не только единичное покрытие.

Что такое модульное тестирование?

Модульные тесты обычно представляют собой автоматизированные тесты, написанные и запущенные разработчиками программного обеспечения, чтобы убедиться, что часть приложения (известная как модуль) соответствует своему дизайну и ведет себя так, как предполагалось. В процедурном программировании единицей может быть целый модуль, но чаще это отдельная функция или процедура. В объектно-ориентированном программировании модуль часто представляет собой целый интерфейс, например класс, но может быть и отдельным методом. [1]

Что такое системное тестирование?

Системное тестирование принимает в качестве входных данных все интегрированные компоненты, прошедшие интеграционное тестирование. Целью интеграционного тестирования является обнаружение любых несоответствий между единицами, которые объединены вместе (называемые сборками). Системное тестирование направлено на выявление дефектов как внутри «межсборок», так и внутри системы в целом. Фактический результат — это поведение, возникающее или наблюдаемое при тестировании компонента или системы.

Системное тестирование выполняется для всей системы в контексте либо спецификаций функциональных требований (FRS), либо спецификаций системных требований (SRS), либо обоих. Системное тестирование проверяет не только дизайн, но и поведение и даже предполагаемые ожидания клиента. Он также предназначен для тестирования за пределами, определенными в спецификациях требований к программному или аппаратному обеспечению. [2]

Отчет о покрытии тестами системы

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

Как мы можем знать, что рассмотрели все случаи и достигли каждого бита кода, который может способствовать выполнению приложения? Чтобы обеспечить это здравомыслие, нам нужен отчет о покрытии, который может быть сгенерирован, когда приложение запускается в промежуточной/функциональной среде.

Как мы можем это сделать?

Несколько вещей, которые мы должны знать, прежде чем начать

История с обложки

Пакетный тест

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

1. Инициализация приложения для запуска в качестве теста

Отчет о покрытии генерируется только для тестов в golang, и тестовый двоичный файл также будет соответствовать тому же. Чтобы обеспечить выполнение тестового двоичного файла, как и ожидалось, мы должны сделать следующее. Чтобы запустить тест пакета, он должен содержать хотя бы один файл *_test.go. Допустим, у нас есть main_test.go для нашего варианта использования. Чтобы запустить тест, метод должен иметь префикс Test*. Допустим, мы определили TestMain.

Например, рассмотрите файл main.go, как показано ниже.

Чтобы создать тестовую сборку для main.go, main_test.goможет быть

Таким образом, в тестовом файле определено имя метода TestMain. Это будет вызвано, когда тестовый двоичный файл будет выполнен, это означает вызов main() и запуск приложения, как обычный двоичный файл, а также все виды отчетов, которые может предоставить любой модульный тест.

В: Если покрытие доступно при запуске только тестового двоичного файла, зачем нам нужен флаг SystemTest?

Что ж, может показаться, что все работает отлично, но проблема в том, что когда вы бежите

go test ./...

При этом выполняются все методы, соответствующие Test, которые доступны в _test.go файлах. Это означает, что он также выполнит TestMain, в результате чего инициирующее приложение запустит приложение, а не все остальные тестовые наборы.

2. Сгенерируйте исполняемый код для тестового файла.

Для запуска приложения в любой среде нам требуется скомпилированный двоичный файл.

Test files that declare a package with the suffix "_test" will be compiled as a separate package, and then linked and run with the main test binary.

Из Пакетного теста

Юнит-тесты также создадут двоичный файл внутри и будут удалены после завершения выполнения. Golang также предоставляет возможность создать тестовый двоичный файл для всего приложения. Как упоминалось в Пакетном тесте, мы можем добиться этого, используя опцию -c

go test -o <destination binary> <main_test.go> \
        -timeout=0 \
        -c -covermode=atomic \
        -coverpkg ./...
here:
-c 
        Used to generate the test binary.
-o file
        Compile the test binary to the named file. If not specified then the output file will be of format  `package.test`.
-timeout d
        If a test binary runs longer than duration d, panic. 
        If d is 0, the timeout is disabled. The default is 10 minutes (10m).
-covermode set,count,atomic
        Set the mode for coverage analysis for the package[s]being tested. The default is "set" unless -race is enabled,in which case it is "atomic".
        The values:
        set: bool: does this statement run?
        count: int: how many times does this statement run?
        atomic: int: count, but correct in multithreaded tests; 
                     significantly more expensive.
./...   
        ./... at the end of the command makes sure the coverage   binary is generated for all sub packages under the same path, except imported packages. We can also list the specific packages separated by comma to get coverage reports of specific packages. For more details try running `go test -help`.

3. Запуск тестового двоичного файла

Как только двоичный файл сгенерирован, это всего лишь команда

package.test -systemTest -test.coverprofile coverage.cov
here:
-systemTest (custom)
          Indicates its a system test 
-test.coverprofile file (inbuilt)
          Write a coverage profile to file

Здесь -systemTest важен флаг. В противном случае он не будет инициализировать приложение. При предоставлении этого аргумента приложение инициируется как обычный двоичный файл и собирает отчет о покрытии.

В: Что еще мы можем получить из этого тестового двоичного файла?

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

package.test -help и изучите варианты

4. Отчет о покрытии

Теперь мы смогли выполнить тестовый двоичный файл и собрать отчет о покрытии, но он доступен в памяти и недоступен в файле. Это связано с тем, что отчет о покрытии будет сгенерирован только тогда, когда приложение получит сигнал SIGINT или при использовании Ctrl+C.

Если вы не видите сгенерированный файл покрытия, проверьте, правильно ли передан -systemTest флаг или отсутствуют какие-либо конфигурации, связанные с приложением.

Вывод

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

  • Тестирование системы с широким охватом помогает построить надежную систему (меньше ошибок)
  • Определите устаревший код и помогите поддерживать код приложения
  • Уменьшено количество ошибок и сюрпризов на производстве

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