В Ruby фактически монополистом в области анализа покрытия является гем SimpleCov, который является удобной обёрткой над модулем Coverage из стандартной библиотеки.
Для начала я приведу небольшой тестовый проект из трёх классов, проанализирую его покрытие, а напоследок немного поразмышляю о том, как анализ покрытия может приносить пользу проекту, и какие есть недостатки у Coverage в Ruby.
В качестве проекта для тестирования взята небольшая история о мальчике, который может спрашивать разрешения погулять у матери и у отца.
# Мама очень заботится о своём сыне, и не разрешает ему гулять, # если он не надел шарф. А ещё она заботится о его успеваемости, поэтому если # сын не сделал домашнюю работу, гулять ему она тоже не разрешит. class Mother def permit_walk?(child) child.scarf_put_on && child.homework_done end end
# Отец тоже следит за тем, чтобы шарф был надет, но не так трепетно относится к учёбе. class Father def permit_walk?(child) child.scarf_put_on end end
# Сын любит и уважает родителей, поэтому никогда не уходит гулять, # не спросив разрешения. Спрашивать он может и у мамы, и у папы. # Ну и, конечно, он может одеваться и делать ДЗ. class Child attr_reader :homework_done, :scarf_put_on def initialize(mother, father) @mother = mother @father = father @homework_done = false @scarf_put_on = false end def do_homework! @homework_done = true end def put_on_scarf! @scarf_put_on = true end def walk_permitted?(whom_to_ask) parent = if whom_to_ask == :mother @mother else @father end parent.permit_walk?(self) end end
Ну и потестируем немного (тесты намеренно покрывают не все сценарии):
require "simplecov" SimpleCov.start require "rspec" require_relative "../lib/mother" require_relative "../lib/father" require_relative "../lib/child" RSpec.describe Child do let(:child) { Child.new(Mother.new, Father.new) } context "when asking mother without scarf and without homework" do it "isn't permitted to walk" do expect( child.walk_permitted?(:mother) ).to be false end end context "when asking mother with scarf and with homework" do it "is permitted to walk" do child.put_on_scarf! child.do_homework! expect( child.walk_permitted?(:mother) ).to be true end end end
Подключение SimpleCov сводится к двум строкам в начале файла с тестами, при этом важно, чтобы инициализация SimpleCov проводилась до подключения файлов проекта. Запускаем тесты:
ruby tests/test_child.rb
Voilà! Сгенерировался файл отчёт coverage/index.html. Посмотреть его можно по ссылке, а здесь я оставлю пару скриншотов, чтобы далеко не ходить.
Общий отчёт
father.rb
Выдержка из child.rb
Бонусы от анализа coverage
Из отчёта сразу видно, что не протестирован путь, в котором разрешение спрашивается у отца. Отсюда
очевидная польза от анализа покрытия: в условиях неприменения TDD отчёт может показать, что мы забыли что-то протестировать. Если же проект достался в наследство и нелёгкий путь тестирования только начинается, отчёт поможет решить, куда эффективнее всего направить силы.
Второе возможное применение — автоматическое обеспечение "качества" коммитов. CI-сервер может отбраковывать коммиты, которые приводят к снижению total coverage, резко снижая вероятность появления в репозитории непротестированного кода.
Что анализ покрытия не даёт
Во-первых, стопроцентное покрытие не обеспечивает отсутствие багов. Простой пример: если изменить класс Mother таким образом:
class Mother def permit_walk?(child) # child.scarf_put_on && child.homework_done child.homework_done end end
покрытие класса останется 100%-ым, тесты будут по-прежнему зелёными, но логика будет очевидно неверной. Для автоматического определения "отсутствующих, но нужных" тестов можно использовать гем mutant. Я ещё не пробовал его в деле, но, судя по Readme и количеству звёзд на гитхабе, библиотека действительно полезна. Впрочем, это тема для отдельного поста, до которого я как-нибудь доберусь.
Во-вторых, в Ruby на данный момент возможен анализ покрытия только по строкам, branch- и condition-coverage не поддерживается. Имеется в виду, что в однострочниках вида
some_condition ? 1 : 2 some_condition || another_condition return 1 if some_condition
есть точки ветвления, но даже если тесты пройдут только по одной возможной ветви исполнения, coverage покажет 100%. Был pull request в Ruby на эту тему, но от мейнтейнеров уже два года ничего не слышно. А жаль.
Послесловие
Я предпочитаю писать тесты сразу же после написания кода, и coverage служит мне напоминалкой о ещё не протестированных методах (частенько забываю потестить обработчики исключений). В общем, анализ покрытия вполне может приносить определённую пользу, но 100%-е покрытие не обязательно говорит о том, что тестов достаточно.
Материалы, используемые в статье:
ссылка на оригинал статьи https://habrahabr.ru/post/317326/