4 библиотеки, о которых должен знать каждый Автоматизатор

Самая выразительная часть хорошо написанного теста - хорошо написанный assert (acсершин, ассерт далее). Assert подтверждает поведение системы, которое вы ожидаете. Хороший assert должен с первого взгляда показывать, что делает тест. Ни в коем случае в ваших тестах не должно быть циклов, в недрах которых будет спрятан assert, и вам нужно будет потратить уйму времени, чтобы разобраться с тем, что происходит. Более того, любая не тривиальная логика в тест кейсе повышает риск того, что тест сам по себе неправильный.

assert sign photoshopped

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

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

Существует два подхода для написания ассертов. Первый - используя ключевое слово "assert", второй же - используя такие слова как "should" или "expect". Первый берет свое начало из более традиционного стиля написания юнит тестов и фокусируется на тестировании и верификации. Второй - более BDD - ориентирован - слова "should" и "expect" описывают поведение, которое вы ожидаете от системы. В этой заметке я предлагаю рассмотреть несколько библиотек, которые помогут в написании понятных ассертов.

Аssertions in JavaScript.

JavaScript обладает большим количеством библиотек, которые помогают делать ассерты более выразительными. Jasmine имеет встроенную функцию expect(). Такие библиотеки как Should.js и Chai поддерживают схожие функции. Я покажу пару примеров из библиотеки Chai, так как она является самой гибкой и поддерживает оба формата: как expect() и should(), так и старый формат - assert. Chai фокусируется на использовании цепочек для того, чтобы сделать утверждения более читабельными и выразительными.

var expect = require('chai').expect

var medal = "Bronze";
...
expect(medal).to.equal('Bronze');

Chai поддерживает богатый набор ассертов для коллекций:

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

Ну и наконец assert в стиле BDD:

var expect = require('chai').should();

medal.should.equal('Bronze');
obtainableStatuses.should.have.length(3).and.include('Silver');

Следует отметить, что оба подхода одинаково выразительны. Таким образом выбор стиля написания зависит от персональных предпочтений. Лично мой выбор в пользу should.

Assertions in Java.

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

Я приведу пример нескольких библиотек, которые, по моему мнению, могут быть полезными.

1. Hamcrest

Пожалуй, самая известная библиотека из этой области. Основной метод, который в ней используется, называется assertThat().

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import junit.framework.TestCase;

public class BiscuitTest extends TestCase {
  public void testEquals() {
    Biscuit theBiscuit = new Biscuit("Ginger");
    Biscuit myBiscuit = new Biscuit("Ginger");
    assertThat(theBiscuit, equalTo(myBiscuit));
  }
}

Самое приятное, что она предоставляет большой выбор матчеров:

Core
    anything - always matches, useful if you don't care what the object under test is
    describedAs - decorator to adding custom failure description
    is - decorator to improve readability - see "Sugar", below
Logical
    allOf - matches if all matchers match, short circuits (like Java &&)
    anyOf - matches if any matchers match, short circuits (like Java ||)
    not - matches if the wrapped matcher doesn't match and vice versa
Object
    equalTo - test object equality using Object.equals
    hasToString - test Object.toString
    instanceOf, isCompatibleType - test type
    notNullValue, nullValue - test for null
    sameInstance - test object identity
Beans
    hasProperty - test JavaBeans properties
Collections
    array - test an array's elements against an array of matchers
    hasEntry, hasKey, hasValue - test a map contains an entry, key or value
    hasItem, hasItems - test a collection contains elements
    hasItemInArray - test an array contains an element
Number
    closeTo - test floating point values are close to a given value
    greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - test ordering
Text
    equalToIgnoringCase - test string equality ignoring case
    equalToIgnoringWhiteSpace - test string equality ignoring differences in runs of whitespace
    containsString, endsWith, startsWith - test string matching

Более подробно можно посмотреть на их сайте.

Лично я пользуюсь этой библиотекой.

2. FestAssert

Как утрверждают сами разработчики:

Note
FEST Assertions 2.0 is a Java library that provides a fluent interface for writing assertions. Its main goal is to improve test code readability and make maintenance of tests easier.

Парочка примеров использования:

import static org.fest.assertions.api.Assertions.*;

// common assertions
assertThat(yoda).isInstanceOf(Jedi.class);
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
assertThat(frodo).isIn(fellowshipOfTheRing);
assertThat(sauron).isNotIn(fellowshipOfTheRing);

// String specific assertions
assertThat(frodo.getName()).startsWith("Fro").endsWith("do")
                           .isEqualToIgnoringCase("frodo");

// collection specific assertions
assertThat(fellowshipOfTheRing).hasSize(9)
                               .contains(frodo, sam)
                               .excludes(sauron);
// throwable specific assertions
try {
  fellowshipOfTheRing.get(9); // argggl !
} catch (Exception e) {
  assertThat(e).isInstanceOf(IndexOutOfBoundsException.class)
               .hasMessage("Index: 9, Size: 9")
               .hasNoCause();
}

// map specific assertions (One ring and elves ring bearers initialized before)
assertThat(ringBearers).hasSize(4)
                       .includes(entry(oneRing, frodo), entry(nenya, galadriel))
                       .excludes(entry(oneRing, aragorn));

Лично я на проектах активно не использовал эту библиотеку, но меня она привлекла тем, что есть готовые ассершины для Joda Time, Guava. Также есть возможность использования компараттора для утверждений.

// frodo and sam are instances of Character with Hobbit race (obviously :), they are not equal ...
assertThat(frodo).isNotEqualTo(sam);
// ... but if we compare race only, they are (raceComparator implements Comparator<Character>)
assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam);

Более подробно можно посмотреть и скачать здесь

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

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

В FestAssert это будет выглядеть так:

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

К сожалению, FestAssert больше не активен, поэтому взамен этой библиотеки предлагаю посмотреть на следующую в списке.

3. AssertJ

AssertJ - форк библиотеки Fest Assert, предоставляет большой набор утверждений, сообщений об ошибках и позволяет улучшить читабельность тестового кода.

// unique entry point to get access to all assertThat methods and utility methods (e.g. entry)
import static org.assertj.core.api.Assertions.*;

// common assertions
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron)
                 .isIn(fellowshipOfTheRing);

// String specific assertions
assertThat(frodo.getName()).startsWith("Fro")
                           .endsWith("do")
                           .isEqualToIgnoringCase("frodo");

// collection specific assertions
assertThat(fellowshipOfTheRing).hasSize(9)
                               .contains(frodo, sam)
                               .doesNotContain(sauron);

// using extracting magical feature to check fellowshipOfTheRing characters name :)
assertThat(fellowshipOfTheRing).extracting("name").contains("Boromir", "Gandalf", "Frodo", "Legolas")
                                                  .doesNotContain("Sauron", "Elrond");

// map specific assertions, ringBearers initialized with the elves rings and the one ring bearers.
assertThat(ringBearers).hasSize(4)
                       .contains(entry(oneRing, frodo), entry(nenya, galadriel))
                       .doesNotContainEntry(oneRing, aragorn);

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

4.Google Truth

Еще одна библиотечка от одноименной компании с очень приятным именем.

Set<Foo> foo = ...;
assertTrue(foo.isEmpty()); // or, shudder, foo.size() == 0

Дает непонятное и нечитабельное исключение:

java.lang.AssertionError
    at org.junit.Assert.fail(Assert.java:92)
    at org.junit.Assert.assertTrue(Assert.java:43)

С применением Truth читабельность и понятность гораздо выше:

Set<Foo> foo = ...;
assertThat(foo).isEmpty()


org.truth0.FailureStrategy$ThrowableAssertionError: Not true that  is empty
    at org.truth0.FailureStrategy.fail(FailureStrategy.java:33)

Хотите узнать больше и попробовать, смотрите здесь cайт.

Ну вот собственно и все.

Небольшой итог об этих монстрах:

Hamcrest и FestAssert играют подобные роли в Java-based BDD. Hamcrest более гибкий и легко расширяемый, но FestAssert обладает более простым синтаксисом и более прост в использовании. AssertJ - улучшенная версия FestAssert, которая обладает уймой полезных фишек.

В целом все библиотеки предназначены для того, чтобы сделать юнит тесты проще и понятнее. Хороших вам assertов =)