Перейти к основному содержимому

Spock — первая спецификация

Разработчику

Spock — первая спецификация

Spock — фреймворк тестов на Groovy, который хорошо стыкуется с Java-проектами: production-код на Java, тесты — в src/test/groovy. Spock компилируется в байт-код JVM и запускается через JUnit Platform (та же инфраструктура, что у JUnit 5).

Главная идея — спецификация читается как текст — блоки given (данные), when (действие), then (проверка), таблица where для многих входов.

База Groovy: первая программа · теория: справочник § Spock · Java-тесты: JUnit 5 · сборка: Gradle Groovy DSL · HTTP-модели из API-тестера — удобная цель для Spock-тестов parseHeaders.


Что получится

Проект с Java-классом Calc, Spock-спецификацией CalcSpec, запуск ./gradlew test и понимание каждого блока теста.

./gradlew test

Разбор:

  • Фрагмент на bash показывает рабочий пример: начинается с ./gradlew test и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Основная логика выражена последовательностью инструкций, которые рантайм выполняет сверху вниз.
  • Поток выполнения линейный: шаги идут последовательно, поэтому итог зависит от порядка операций в блоке.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Термины

ТерминПростыми словами
SpecificationКласс теста в Spock (наследник spock.lang.Specification)
Feature methodМетод def "описание на русском"() — один сценарий
Блок givenПодготовка: переменные, моки, фикстуры
Блок whenДействие: вызов тестируемого кода
Блок thenПроверки и ожидание исключений
Блок expectДействие и проверка в одном шаге
Блок whereТаблица параметров (как Excel: столбцы = переменные)
@UnrollКаждая строка where — отдельный тест в отчёте

Зачем Spock, если есть JUnit

JUnit 5Spock
@Test void shouldAdd()Имя метода = человекочитаемое предложение
@ParameterizedTest + источникиТаблица where: внизу метода
Mockito отдельноMock(), Stub() встроены в язык Spock
AssertJ / Hamcrest==, thrown(), матчеры в then

Spock не заменяет JUnit в экосистеме: он использует JUnit Platform как раннер. В CI остаётся привычный шаг test и XML-отчёты.


Минимальная спецификация (без Gradle)

Суть Spock в одном методе — блоки с двоеточием:


import spock.lang.Specification

class SumSpec extends Specification {
def "два плюс три равно пять"() {
expect:
2 + 3 == 5
}
}

Разбор:

  • extends Specification — базовый класс Spock; без него блоки expect: не распознаются.
  • Имя метода в кавычках — человекочитаемое описание сценария в отчёте тестов.
  • expect: — один блок "проверка выражения": выражение после блока должно быть истинным.
  • 2 + 3 == 5 — обычный Groovy; при ложном результате тест красный с diff (как JUnit assertion).
  • Файл кладут в src/test/groovy; для запуска в Gradle нужны зависимости ниже.

Что тестируем (Java)

Spock вызывает обычные Java-классы без обёрток:

// src/main/java/com/example/Calc.java
package com.example;

public class Calc {
public int add(int a, int b) { return a + b; }
public int divide(int a, int b) {
if (b == 0) throw new ArithmeticException("zero");
return a / b;
}
}

Разбор:

  • Фрагмент на java показывает рабочий пример: начинается с // src/main/java/com/example/Calc.java и задает контекст выполнения.
  • Конструкция class описывает структуру объекта: поля хранят состояние, а методы инкапсулируют поведение.
  • Вызовы методов и объявления функций задают основной шаг логики: входные значения передаются в метод и сразу обрабатываются.
  • Поток выполнения контролируется условиями: при разных состояниях кода выбирается соответствующая ветка и результат.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Пакет в тесте Groovy должен совпадать: package com.example.


Gradle (Groovy DSL)

В build.gradle:

Код ITЗагрузка примера кода…

Разбор:

  • Фрагмент на groovy показывает рабочий пример: начинается с plugins { и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Вызовы функций или команд выполняют полезное действие: чтение данных, вычисление результата или запуск задачи сборки.
  • Поток выполнения линейный: шаги идут последовательно, поэтому итог зависит от порядка операций в блоке.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.
ЗависимостьЗачем
spock-coreSpock + интеграция с JUnit Platform
groovyКомпилятор Groovy для тестовых исходников
useJUnitPlatform()Gradle запускает тесты через JUnit 5 / Platform

Папка: src/test/groovy (зеркало пакета com/example/CalcSpec.groovy).

Подробнее про блоки Gradle: Gradle Groovy DSL — первая сборка.


Первая спецификация

Код ITЗагрузка примера кода…

Разбор:

  • Фрагмент на groovy показывает рабочий пример: начинается с package com.example и задает контекст выполнения.
  • Строки import подключают нужные классы и модули, чтобы дальше вызывать API без полного имени пакета.
  • Вызовы методов и объявления функций задают основной шаг логики: входные значения передаются в метод и сразу обрабатываются.
  • Поток выполнения контролируется условиями: при разных состояниях кода выбирается соответствующая ветка и результат.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Разбор по блокам

Класс и поле:

  • extends Specification — подключает DSL Spock (блоки, моки, setup/cleanup).
  • def calc = new Calc() — один экземпляр на спецификацию (можно вынести в setup()).

Сценарий "сложение":

БлокРоль
given:Задаём a и b
when:Вызываем calc.add
then:Утверждение result == 5 (при ложном — провал с diff)

Параметризованный тест:

  • @Unroll — в IDE/CI будет "деление 10 / 2 = 5", "деление 9 / 3 = 3", а не одна строка.
  • expect: — когда нет отдельного "действия", только выражение-истина.
  • where: — таблица — столбцы a, b, expected разделены |.
  • В имени метода #a, #b — подстановка значений из строки where.

Исключение:

  • В when вызываем опасный код.
  • В then thrown(ArithmeticException) — тест зелёный только если исключение именно этого типа вылетело.

Запуск

./gradlew test

Разбор:

  • Фрагмент на bash показывает рабочий пример: начинается с ./gradlew test и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Основная логика выражена последовательностью инструкций, которые рантайм выполняет сверху вниз.
  • Поток выполнения линейный: шаги идут последовательно, поэтому итог зависит от порядка операций в блоке.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Windows: gradlew.bat test.

В IntelliJ IDEA: правый клик на CalcSpec → Run. Отчёт JUnit-совместимый — Jenkins/GitHub Actions подхватят без отдельной настройки Spock.

Успешный прогон в консоли: BUILD SUCCESSFUL, в build/reports/tests/test/index.html — HTML-отчёт.


Моки

Зависимость подменяют без Mockito:

def repo = Mock(UserRepository)
repo.findById(1) >> new User(name: "Ann")

when:
def user = service.load(1)

then:
1 * repo.findById(1)
user.name == "Ann"

Разбор:

  • Фрагмент на groovy показывает рабочий пример: начинается с def repo = Mock(UserRepository) и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Вызовы методов и объявления функций задают основной шаг логики: входные значения передаются в метод и сразу обрабатываются.
  • Поток выполнения контролируется условиями: при разных состояниях кода выбирается соответствующая ветка и результат.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.
КонструкцияСмысл
Mock(Class)Прокси: вызовы записываются
>> значениеStub: при вызове вернуть значение
1 * repo.method(...)Проверка: метод вызван ровно один раз

Подробнее — справочник.


Частые ошибки

СимптомПричина
Spec не находитсяФайл не в src/test/groovy
No tests foundНет useJUnitPlatform() в test { }
== для объектов "падает"У POJO нет корректного equals — используйте is() / поля
Groovy не компилируетсяНесовместимые версии spock-core и groovy (смотрите матрицу на spockframework.org)

Что попробовать

  1. Добавить строку в where с ожидаемым ArithmeticException для b == 0.
  2. Spock + @SpringBootTest в Spring-модуле.
  3. Тот же ./gradlew test в Jenkins.
./gradlew test

Разбор:

  • Фрагмент на bash показывает рабочий пример: начинается с ./gradlew test и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Основная логика выражена последовательностью инструкций, которые рантайм выполняет сверху вниз.
  • Поток выполнения линейный: шаги идут последовательно, поэтому итог зависит от порядка операций в блоке.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Дальше

Gradle Groovy DSL · Jenkins Pipeline · делегирование · о разделе


Рекомендованная структура тестов Spock

Для устойчивого тестового набора делите спецификации на три слоя:

  1. "Чистая логика" без Spring-контекста.
  2. Интеграционные тесты с БД и сетью.
  3. Контрактные тесты API на границах сервиса.

Именуйте feature-методы как короткие пользовательские сценарии, чтобы отчет читался как журнал поведения системы.

def "возвращает ошибку при пустом имени пользователя"() {
when:
service.createUser("")

then:
thrown(IllegalArgumentException)
}

Разбор:

  • Фрагмент на groovy показывает рабочий пример: начинается с def "возвращает ошибку при пустом имени пользователя"() { и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Вызовы методов и объявления функций задают основной шаг логики: входные значения передаются в метод и сразу обрабатываются.
  • Поток выполнения контролируется условиями: при разных состояниях кода выбирается соответствующая ветка и результат.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Практика по where-таблицам

where особенно полезен для граничных значений:

@Unroll
def "parseAge(#input) -> #expected"() {
expect:
parser.parseAge(input) == expected

where:
input | expected
"18" | 18
"0" | 0
"99" | 99
}

Разбор:

  • Фрагмент на groovy показывает рабочий пример: начинается с @Unroll и задает контекст выполнения.
  • Ключевые операторы и выражения (def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера.
  • Вызовы методов и объявления функций задают основной шаг логики: входные значения передаются в метод и сразу обрабатываются.
  • Поток выполнения линейный: шаги идут последовательно, поэтому итог зависит от порядка операций в блоке.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Так вы получаете компактный тест и отдельный отчет по каждой строке.

Связанные материалы — Операторы и выражения, Gradle Groovy DSL, Jenkins Pipeline.


Сквозной кейс — тест сервиса каталога

Проверим, что сервис возвращает только активные книги:

Код ITЗагрузка примера кода…

Разбор:

  • Фрагмент на groovy показывает рабочий пример: начинается с class CatalogServiceSpec extends Specification { и задает контекст выполнения.
  • Конструкция class описывает структуру объекта: поля хранят состояние, а методы инкапсулируют поведение.
  • Вызовы методов и объявления функций задают основной шаг логики: входные значения передаются в метод и сразу обрабатываются.
  • Поток выполнения контролируется условиями: при разных состояниях кода выбирается соответствующая ветка и результат.
  • Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.

Этот тест легко запускается в CI без отдельной инфраструктуры.

Следующий шаг: Jenkins Pipeline и Gradle Groovy DSL.