Статьи

BDD, модульные тесты и сила беглых утверждений

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

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

Есть два основных вкуса к свободным утверждениям. Первый тип обычно использует слово «утверждать», тогда как второй использует такие термины, как «должен» или «ожидать». Первый подход основан на более традиционном модульном тестировании и фокусируется на тестировании и верификации. Действительно, вы обычно делаете утверждение о том, что уже произошло, или о результате, который уже был рассчитан. Второй — больше ориентирован на BDD: слова «должны» и «ожидают» описывают то, что, по вашему мнению, должно делать приложение, независимо от того, что оно делает в настоящее время или даже существует.

Давайте посмотрим на несколько.

Свободные утверждения в JavaScript

JavaScript имеет ряд библиотек, которые могут помочь сделать ваши утверждения более выразительными. В Jasmine есть встроенная функция wait (), а Should.js и Chai также поддерживают аналогичные функции. Chai, вероятно, является наиболее гибким из них, поддерживая как ожидаемые, так и ожидаемые форматы, а также устаревший стиль assert. Chai сосредотачивается на использовании цепочки методов, чтобы сделать утверждения гибкими и удобочитаемыми. Например, представьте, что мы создаем веб-сайт для часто летающих пассажиров для крупной авиакомпании с клиентом Javascript. Нам нужно проверить, что статус для часто летающих пассажиров бронзовый. Используя Чай, мы могли бы написать:

var expect = require('chai').expect 
...
expect(frequentFlyer.getStatus()).to.equal('Bronze'); 

Как и следовало ожидать, Chai поддерживает богатую коллекцию утверждений и может связывать несколько утверждений вместе. Например:

var obtainableStatuses = ['Silver','Gold','Platinum']
...
expect(obtainableStatuses).to.have.length(3).and.to.include('Gold')

Chai также поддерживает утверждение BDD-стиля, как показано здесь:

var expect = require('chai').should(); 
frequentFlyer.getStatus().should.equal('Bronze'); 
obtainableStatuses.should.have.length(3).and.include('Silver'); 

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

Свободные утверждения в Java и .NET

Свободные библиотеки утверждений также существуют для статических языков, таких как Java и .Net, хотя они, как правило, немного менее выразительны, чем их динамические эквиваленты. Java, например, имеет несколько свободно распространяемых библиотек утверждений. Двумя наиболее известными являются Hamcrest и FestAssert. Более поздние версии NUnit поставляются с аналогичной моделью утверждений на основе ограничений.

Все эти инструменты отходят от более старых методов Assert и позволяют вам выразить свои ожидания более бегло и кратко. В частности, эти библиотеки предлагают ряд высокоуровневых утверждений для коллекций и могут быть расширены для работы с объектами домена, что позволяет избежать необходимости использовать слишком много логики в ваших модульных тестах. Представьте, что мы хотим сказать, что для часто летающих пассажиров изначально должен быть статус Бронзовый. В традиционном JUnit мы бы написали что-то вроде этого:

assertEquals(BRONZE, member.getStatus());

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

FrequentFlyer member = FrequentFlyer.withFrequentFlyerNumber("12345678")
.named("Joe", "Bloggs");
assertThat(member.getStatus(), is(FrequentFlyerStatus.BRONZE)); 

FestAssert делает что-то похожее, но использует другую синтаксическую структуру:

assertThat(member.getStatus()).isEqualTo(FrequentFlyerStatus.BRONZE);

А в NUnit мы могли бы написать что-то вроде этого:

Assert.That(member.getStatus(), Is.EqualTo(FrequentFlyerStatus.BRONZE));

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

assertThat(member.getUnachievedStatuses(), hasItems(GOLD,PLATINUM));

В FestAssert эквивалент будет аналогичным:

assertThat(member.getUnachievedStatuses()).contains(GOLD,PLATINUM);

Эти библиотеки также допускают более сложные выражения. Например, предположим, что мы хотим получить возраст всех часто летающих пассажиров в возрасте до 18 лет. Мы можем выразить это довольно элегантно в Хэмкресте так:

List memberAges = …;
assertThat(memberAges, everyItem(lessThan(18)));

Мы также можем сделать что-то подобное в NUnit:

Assert.That(memberAges, Has.All.LessThan(18));

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

Hamcrest и FestAssert играют одинаковую роль в BDD на основе Java. Hamcrest более гибок и проще в расширении, но FestAssert имеет более простой синтаксис и немного проще в использовании. Модель утверждений на основе ограничений в NUnit похожа и имеет особенно богатую библиотеку утверждений. Все это огромное улучшение традиционных утверждений Assert.

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

Это сокращенный отрывок из  BDD в действии , который будет выпущен летом 2014 года и в настоящее время доступен через MEAP (Программа раннего доступа Manning).