Разделы портала

Онлайн-тренинги

.
Юнит тесты. Первый шаг к качеству
12.12.2017 11:30

Автор: Фомин Владимир, Инфосеть-С, Senior Javascript Developer

Оригинальная публикация: https://habrahabr.ru/post/336030/

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

Несмотря на различные трактовки юнит тестирования, есть несколько вещей которые объединяют этот термин.

Но есть моменты, в определении юнит тестирования, которые до сих являются спорными. В частности, что рассматривается под юнитом (единицей тестирования)? Подход ООП рассматривает класс как юнит, процедурный (или функциональный) подход, рассматривает одну функцию как юнит. Некоторые разработчики берут несколько классов и считают это юнитом, или берут набор методов в качестве юнита. Но на самом деле это ситуационная вещь, команда сама решает, что должно быть единицей тестирования в их системе.

Преимущества юнит-тестирования очевидны:

  • Являются низкоуровневым и фокусируется на маленькой части ПО
  • Тесты пишут сами разработчики
  • Тесты выполняются очень быстро, можно выполнять тесты несколько раз в минуту
  • При разработке можно выполнять не все тесты, а только те, которые необходимы именно вам

Таким образом, при использовании юнит тестирования скорость разработки существенно не уменьшается, но при этом возрастает качество самого продукта.

Важное различия в юнит тестировании, это какой тип тестирования вы выберите: Solitary (одинокий) и Sociable (общительный) тест. Термины впервые ввел Jay Fields.
Sociable (общительный) тест — это тест который использует реальные методы (или классы), которые входят в тестируемую единицу. Например, вы тестируете метод «цена» из класса заказов. Методу «цена» необходимо вызвать методы из класса клиент и продукт. В данном виде тестов будут вызваны именно эти методы, и ошибка в этих методах приведет к ошибке теста. Методы из классов клиент и продукт называется партнеры (collaborators).

Solitary (одинокий) тест — это тест, который в качестве партнеров использует дубли (TestDouble). Тест-дубли — это общий термин для любого случая, в котором вы заменяете реальный объект, исключительно для целей тестирования.

Хорошую классификация дублей сделал Жерар Мезарос (Gerard Meszaros), более подробно об этом можно почитать здесь

Каждый из этих методов тестирования имеет свои достоинства и недостатки, и между сторонниками этих двух методов ведутся горячие споры. Сторонников Solitary (одинокий) тестов также условно называют Mock-исты (Mock — подделка), а сторонников Sociable (общительный) тестов условно называют Classicists (не смог найти аналогов в русском языке). Хочется отметить, что сторонники Sociable (общительного) тестирования, также используют дубль-тесты для доступа к внешним ресурсам, например, к БД. Отчасти, это делается по причине скорости доступа. Но использовать дубли для доступа к внешним ресурсам это не абсолютное правило, если доступ к ним стабилен и достаточно быстр, то можно обойтись и без дублей. В любом случае разработчик сам решает, когда ему лучше применить дубли.

Одно из достоинств техники тестирования Solitary (одинокий) в том, что разработчики фокусируются на поведении приложения, а не на состоянии. Недостаток в том, что подделки могут замаскировать ошибку, которая присутствует в методе-партнере. Поэтому, при использовании тестов-дублей необходимо выполнять интеграционное тестирование. Достоинство тестирования Sociable (общительный) в том, что это уже по сути начальное интеграционное тестирование, но недостаток в том, что если упадет один метод это приведет к падению всех тестов, связанных с этим методом, что затрудняет отладку.

Я не буду подробно останавливаться на достоинствах и недостатках того или иного подхода в тестировании, об этом можно почитать у Фаулера в статье Mocks Aren't Stubs

Основные свойства unit-тестирования – это небольшой объем, сделанный самим программистом, и скорость – что означает, что они могут выполняется часто во время программирования.

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

В конце 1990-х годов Кент Бек разработал технику «разработка через тестирование» (Test-Driven Development, TDD), как часть экстремального программирования. Эта техника для построения ПО, которая управляет процессом разработки через написание тестов. В сущности, повторяет три простых правила:

  • Сначала пишется тест
  • Затем пишется код под этот тест
  • Рефакторинг нового и старого кода, чтобы улучшить качество кода


Процесс начинается заново пока не получится желаемый результат.


Написание теста первым дает два преимущества:

1. Это способ получить само-тестируемый код
2. Думая сначала о тесте вы заставляете себя думать об интерфейсе самого кода. Эта фокусировка на интерфейсе и на том как вы используете класс помогает вам разделить интерфейс от реализации.

Самая большая ошибка при использовании данной методологии — это пренебрежение третьим шагом, рефакторинг. Это приводит к тому, что код будет “грязным” (но по крайней мере, будут тесты).

BDD (Behaviour Driven Development) или разработка на основе поведения, появилось в процессе эволюции unit-тестирования и разработана Дэном Нортом (Dan North) в 2006г. Как утверждает сам автор, методология должна помочь людям изучить TDD. Она появилось из agile практик и предназначена сделать их более доступными и эффективными для команд-новичков в Agile.
Со временем, BDD стало охватывать более широкую картину agile-анализа и автоматическое приемочное тестирование.

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

Считается, что разработка на основе поведения одно из ответвлений Mock-стилей (или Solitary-тест), т.е. тесты преимущественно строятся с использованием дублей.

Позднее, появился стиль написания тестов Given-When-Then, или, как его стали называть, спецификация поведения системы. Стиль был разработан Дэном Нортом (Dan North), совместно с Крисом Маттисом (Chris Matts). Идея заключается в том, чтобы разбить написание тестового сценария на три раздела:

  1. Дано (Given) — состояние, до того, как вы начнете описывать поведение. можно рассматривать как предварительное условие теста.
  2. Когда (When) — поведение, которое вы описываете.
  3. Тогда (Then) — изменения, которые вы ожидаете от поведения


Пример:

Описание: Пользователь продает акции.
Сценарий: Пользователь запрашивает продажу до закрытия торгов
Дано (Given): У меня есть 100 акций MSFT и 150 акций APPL и время до закрытия торгов.
Когда (When): Я прошу продать 20 акций MSFT
Тогда (Then): У меня должно остаться 80 акций MSFT и 150 акций APPL и заявка на продажу 20 акций должна быть выполнена.


Не взирая на то, что с момента появлений методологий TDD и BDD прошло довольно много времени, многие разработчики до сих пор спорят друг с другом о целесообразности их применения. Кто-то утверждает, что нет необходимости писать тесты перед кодом, другие заявляют, что написание тестов после кода бессмысленно. Но и та и другая стороны согласны в одном, что тесты нужно писать! Методология BDD с точки зрения программистов, как утверждает сам ее автор (BDD IS LIKE TDD IF…), не отличается от TDD. Там используются все те же правила, что и в TDD: тест, код, рефакторинг. Отличие заключается в том, что BDD охватывает более широкую публику. Спецификации становятся доступными не только программистам, но и людям, не разбирающимся в коде, но имеющим отношение к разработке ПО. Таким образом, в процесс создания тестов подключается вся команда: аналитики, тестеры, менеджеры.

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

Но самое большое преимущество не в том, чтобы просто избегать ошибок в продукте, а в уверенности в том, что вы можете вносить изменения в систему. Старый код часто является ужасной картиной, где разработчики боятся его менять. Даже исправление одной ошибки может быть опасно, т.к. вы можете создать больше ошибок, чем исправите. В таких случаях, добавление новых возможностей происходит очень медленно, вы также боитесь сделать рефакторинг системы, увеличивая тем самым технический долг (TechnicalDebt) и попадаете в плохую спираль, где каждое изменение заставляет людей боятся еще большего изменения.
С тестами другая картина. Здесь люди уверены, что фиксация ошибок, может быть сделана безопасно, потому что, если вы допустили оплошность, то детектор ошибок сработает, и вы можете быстро восстановить и продолжить. С помощью этой системы безопасности, вы можете всегда поддерживать код в хорошей форме и уже не окажетесь в плохой спирали.

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

Само тестируемая система – это часть Continuous Integration (непрерывная интеграция) и Continuous Delivery (непрерывная доставка), но это тема уже выходит за рамки данной статьи.

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

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

Высокоуровневые тесты — это вторая линия обороны. Если вы получили ошибку в высокоуровневом тестировании, то это не просто ошибка в коде, это отсутствующий или некорректный юнит тест!

Список источников:

  1. Martin Fowler UnitTest
  2. Martin Fowler TestPyramid
  3. Martin Fowler SelfTestingCode
  4. Martin Fowler TestDrivenDevelopment
  5. James Shore The Art of Agile Development: Test-Driven Development
  6. Введение в программирование через поведение (BDD)
  7. Martin Fowler GivenWhenThen

Обсудить в форуме