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 5 | Spock |
|---|---|
@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-core | Spock + интеграция с 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вызываем опасный код. - В
thenthrown(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) |
Что попробовать
- Добавить строку в
whereс ожидаемымArithmeticExceptionдляb == 0. - Spock +
@SpringBootTestв Spring-модуле. - Тот же
./gradlew testв Jenkins.
./gradlew test
Разбор:
- Фрагмент на
bashпоказывает рабочий пример: начинается с./gradlew testи задает контекст выполнения. - Ключевые операторы и выражения (
def, литералы, вызовы) формируют данные, с которыми работает остальная часть примера. - Основная логика выражена последовательностью инструкций, которые рантайм выполняет сверху вниз.
- Поток выполнения линейный: шаги идут последовательно, поэтому итог зависит от порядка операций в блоке.
- Итог фрагмента — воспроизводимый результат — вывод в консоль, изменение данных или артефакт, который можно сразу проверить.
Дальше
Gradle Groovy DSL · Jenkins Pipeline · делегирование · о разделе
Рекомендованная структура тестов Spock
Для устойчивого тестового набора делите спецификации на три слоя:
- "Чистая логика" без Spring-контекста.
- Интеграционные тесты с БД и сетью.
- Контрактные тесты 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.