«1. Обзор

Среды запуска тестов, такие как JUnit и TestNG, предоставляют некоторые базовые методы утверждения (assertTrue, assertNotNull и т. д.).

Кроме того, существуют фреймворки утверждений, такие как Hamcrest, AssertJ и Truth, которые предоставляют плавные и богатые методы утверждений с именами, которые обычно начинаются с «assertThat».

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

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

2. Зависимости Maven

Давайте импортируем зависимость javalite-common, содержащую JSpec:

<dependency>
    <groupId>org.javalite</groupId>
    <artifactId>javalite-common</artifactId>
    <version>1.4.13</version>
</dependency>

Для получения последней версии проверьте репозиторий Maven Central.

3. Сравнение стилей утверждения

Вместо типичного способа утверждения на основе правил мы просто пишем спецификацию поведения. Давайте рассмотрим быстрый пример подтверждения равенства в JUnit, AssertJ и JSpec.

В JUnit мы бы написали:

assertEquals(1 + 1, 2);

А в AssertJ мы бы написали:

assertThat(1 + 1).isEqualTo(2);

Вот как мы напишем тот же тест в JSpec:

$(1 + 1).shouldEqual(2);

JSpec использует тот же стиль, что и фреймворки Fluent Assert, но опускает начальное ключевое слово assert/assertThat и использует вместо него should.

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

Посмотрите, как этот пример очень близок к нашему естественному написанию спецификаций:

String message = "Welcome to JSpec demo";
the(message).shouldNotBe("empty");
the(message).shouldContain("JSpec");

4. Структура спецификаций

Спецификация состоит из двух частей: создателя ожидания и метода ожидания.

4.1. Создатель ожиданий

Создатель ожиданий создает объект Expectation, используя один из следующих статически импортированных методов: a(), the(), it(), $():

$(1 + 2).shouldEqual(3);
a(1 + 2).shouldEqual(3);
the(1 + 2).shouldEqual(3);
it(1 + 2).shouldEqual(3);

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

Единственное отличие состоит в том, что метод it() является типобезопасным, позволяя сравнивать только объекты одного типа:

it(1 + 2).shouldEqual("3");

Сравнение объектов разных типов с использованием it() приведет к компиляции ошибка.

4.2. Метод ожидания

Вторая часть оператора спецификации — это метод ожидания, который сообщает о требуемой спецификации, такой как shouldEqual, shouldContain.

Когда тест не пройден, исключение типа javalite.test.jspec.TestException отображает выразительное сообщение. Мы увидим примеры таких сообщений об ошибках в следующих разделах.

5. Встроенные ожидания

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

5.1. Ожидание равенства

shouldEqual(), shouldBeEqual(), shouldNotBeEqual()

Они указывают, что два объекта должны/не должны быть равны, используя метод java.lang.Object.equals() для проверки на равенство: ~ ~~

$(1 + 2).shouldEqual(3);

Сценарий отказа:

$(1 + 2).shouldEqual(4);

выдаст следующее сообщение:

Test object:java.lang.Integer == <3>
and expected java.lang.Integer == <4>
are not equal, but they should be.

5.2. Логическое ожидание свойства

shouldHave(), shouldNotHave()

Мы используем эти методы, чтобы указать, должно ли именованное логическое свойство объекта возвращать значение true:

Cage cage = new Cage();
cage.put(tomCat, boltDog);
the(cage).shouldHave("animals");

Это требует, чтобы класс Cage содержат метод с сигнатурой:

boolean hasAnimals() {...}

Сценарий отказа:

the(cage).shouldNotHave("animals");

выдаст следующее сообщение:

Method: hasAnimals should return false, but returned true

shouldBe(), shouldNotBe()

Мы используем их, чтобы указать, что тестируемый объект должен/не должен быть чем-то:

the(cage).shouldNotBe("empty");

Это требует, чтобы класс Cage содержал метод с сигнатурой «boolean isEmpty()».

Сценарий отказа:

the(cage).shouldBe("empty");

выдаст следующее сообщение:

Method: isEmpty should return true, but returned false

5.3. Ожидание типа

shouldBeType(), shouldBeA()

Мы можем использовать эти методы, чтобы указать, что объект должен быть определенного типа:

cage.put(boltDog);
Animal releasedAnimal = cage.release(boltDog);
the(releasedAnimal).shouldBeA(Dog.class);

Сценарий отказа:

the(releasedAnimal).shouldBeA(Cat.class);

приведет к следующее сообщение:

class com.baeldung.jspec.Dog is not class com.baeldung.jspec.Cat

5.4. Ожидание обнуляемости

«shouldBeNull(), shouldNotBeNull()

Мы используем их, чтобы указать, что тестируемый объект должен/не должен быть нулевым:

cage.put(boltDog);
Animal releasedAnimal = cage.release(dogY);
the(releasedAnimal).shouldBeNull();

Сценарий отказа:

the(releasedAnimal).shouldNotBeNull();

выдаст следующее сообщение: ~~ ~

Object is null, while it is not expected

5.5. Ожидание ссылки

shouldBeTheSameAs(), shouldNotBeTheSameAs()

Эти методы используются для указания того, что ссылка на объект должна совпадать с ожидаемой:

Dog firstDog = new Dog("Rex");
Dog secondDog = new Dog("Rex");
$(firstDog).shouldEqual(secondDog);
$(firstDog).shouldNotBeTheSameAs(secondDog);

Сценарий отказа:

$(firstDog).shouldBeTheSameAs(secondDog);

будет вывести следующее сообщение:

references are not the same, but they should be

5.6. Ожидание содержимого коллекции и строки

shouldContain(), shouldNotContain() Мы используем их, чтобы указать, что тестируемая коллекция или карта должны/не должны содержать данный элемент:

cage.put(tomCat, felixCat);
the(cage.getAnimals()).shouldContain(tomCat);
the(cage.getAnimals()).shouldNotContain(boltDog);

Сценарий отказа:

the(animals).shouldContain(boltDog);

~~ ~ выдаст следующее сообщение:

tested value does not contain expected value: Dog [name=Bolt]

Мы также можем использовать эти методы, чтобы указать, что строка должна/не должна содержать заданную подстроку:

$("Welcome to JSpec demo").shouldContain("JSpec");

И хотя это может показаться странным, мы можем расширить это поведение с другими типами объектов, которые сравниваются с использованием их методов toString():

cage.put(tomCat, felixCat);
the(cage).shouldContain(tomCat);
the(cage).shouldNotContain(boltDog);

Для пояснения, метод toString() объекта Cat tomCat выдаст: вывод toString() объекта клетки:

Cat [name=Tom]

6. Пользовательские ожидания

Cage [animals=[Cat [name=Tom], Cat[name=Felix]]]

Помимо встроенных ожиданий, JSpec позволяет нам записывать пользовательские ожидания.

6.1. DifferenceExpectation

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

В этом простом примере мы убеждаемся, что операция (2 + 3) не даст нам результата (4):

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

expect(new DifferenceExpectation<Integer>(4) {
    @Override
    public Integer exec() {
        return 2 + 3;
    }
});

Например, при выпуске животного из клетки, содержащей двух животных, размеры должны быть разными:

Сценарий отказа:

cage.put(tomCat, boltDog);
expect(new DifferenceExpectation<Integer>(cage.size()) {
    @Override
    public Integer exec() {
        cage.release(tomCat);
        return cage.size();
    }
});

Здесь мы пытаемся выпустить несуществующее животное. внутри клетки:

Размер не изменится, и мы получим следующее сообщение:

cage.release(felixCat);

6.2. Ожидание исключения

Objects: '2' and '2' are equal, but they should not be

Мы можем написать ExceptionExpectation, чтобы указать, что тестируемый код должен вызывать исключение.

Мы просто передадим ожидаемый тип исключения в конструктор и предоставим его как общий тип:

Сценарий ошибки №1:

expect(new ExceptionExpectation<ArithmeticException>(ArithmeticException.class) {
    @Override
    public void exec() throws ArithmeticException {
        System.out.println(1 / 0);
    }
});

Так как эта строка не приведет ни к какому исключению , его выполнение выдаст следующее сообщение:

System.out.println(1 / 1);

Сценарий сбоя #2:

Expected exception: class java.lang.ArithmeticException, but instead got nothing

Это приведет к исключению, отличному от ожидаемого исключения:

Integer.parseInt("x");

7. Заключение

class java.lang.ArithmeticException,
but instead got: java.lang.NumberFormatException: For input string: "x"

Другие фреймворки fluent-утверждений предоставляют лучшие методы для утверждений коллекций, утверждений исключений и интеграции с Java 8, но JSpec предоставляет уникальный способ написания утверждений в форме спецификаций.

У него есть простой API, который позволяет нам писать наши утверждения на естественном языке, и он предоставляет описательные сообщения об ошибках теста.

Полный исходный код всех этих примеров можно найти на GitHub — в пакете com.baeldung.jspec.

«