1. Введение
Платформы тестирования спецификаций дополняют платформы модульного тестирования для тестирования наших приложений.
В этом руководстве мы познакомим вас с платформой Spek – платформой тестирования спецификаций для Java и Kotlin.
2. Что такое Техническое тестирование?
Проще говоря, при тестировании спецификации мы начинаем со спецификации и описываем назначение программного обеспечения, а не его механику.
Это часто используется при разработке, основанной на поведении, поскольку целью является проверка системы на соответствие предопределенным спецификациям нашего приложения.
Широко известные платформы тестирования спецификаций включают Spock, Cucumber, Jasmine и RSpec.
2.1. Что такое Spek?
Spek - это платформа для тестирования спецификаций на основе Kotlin для JVM. Она предназначена для работы в качестве тестового движка JUnit 5. Это означает, что мы можем легко подключить ее к любому проекту, который уже использует JUnit 5, для запуска вместе с любыми другими тестами, которые у нас могут быть.
Также можно запускать тесты с использованием более старой платформы JUnit 4, при необходимости используя зависимость JUnit Platform Runner.
2.2. Зависимости Maven
Чтобы использовать Speak, нам нужно добавить необходимые зависимости в нашу сборку Maven:
<dependency>
<groupId>org.jetbrains.spek</groupId>
<artifactId>spek-api</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.spek</groupId>
<artifactId>spek-junit-platform-engine</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
Зависимость spec-api - это фактический API, используемый для платформы тестирования. Он определяет все, с чем будут работать наши тесты. Зависимость speak-junit-platform-engine - это модульный движок тестирования 5, необходимый для выполнения наших тестов.
Обратите внимание, что все спецификации должны быть одинаковой версии. Последнюю версию можно найти здесь.
2.3. Первое испытание
Как только Speak настроен, написание тестов - это простой случай написания правильного класса в правильной структуре. Это немного необычно, чтобы сделать его более читабельным.
Spek требует, чтобы все наши тесты наследовались от соответствующего суперкласса – обычно Spek – и чтобы мы реализовывали наши тесты, передавая блок конструктору этого класса:
class FirstSpec : Spek({
// Implement the test here
})
3. Стили тестирования
При тестировании спецификаций особое внимание уделяется написанию тестов таким образом, чтобы они были максимально удобочитаемыми. Cucumber, например, пишет весь тест на понятном человеку языке, а затем связывает его с шагами, чтобы код хранился отдельно.
Speak работает с использованием специальных методов, которые действуют как строки для чтения, каждой из которых присваивается соответствующий блок для выполнения. Есть несколько вариантов того, какие функции мы используем, в зависимости от того, как мы хотим, чтобы тесты считывали данные.
3.1. given/on/it
Один из способов, которым мы можем писать наши тесты, - это стиль “given/on/it”.
Для написания наших тестов используются методы, называемые given, on и it, вложенные в эту структуру:
- given – устанавливает начальные условия для теста
- on – выполните тестовое действие
- it – подтвердите, что тестовое действие выполнено правильно
У нас может быть столько блоков, сколько нам нужно, но они должны располагаться в следующем порядке:
class CalculatorTest : Spek({
given("A calculator") {
val calculator = Calculator()
on("Adding 3 and 5") {
val result = calculator.add(3, 5)
it("Produces 8") {
assertEquals(8, result)
}
}
}
})
Этот тест читается очень легко. Сосредоточившись на шагах теста, мы можем прочитать его как “Учитывая калькулятор, при сложении 3 и 5 получается 8”.
3.2. describe/it
Другой способ, которым мы можем написать наши тесты, - это стиль “describe/it”. Вместо этого мы используем метод describe для всех птенцов и продолжаем использовать его для наших утверждений.
В этом случае мы можем использовать методы describe столько раз, сколько нам нужно для написания наших тестов:
class CalculatorTest : Spek({
describe("A calculator") {
val calculator = Calculator()
describe("Addition") {
val result = calculator.add(3, 5)
it("Produces the correct answer") {
assertEquals(8, result)
}
}
}
})
В тестах, использующих этот стиль, меньше структуры, что означает, что у нас гораздо больше гибкости в написании тестов.
К сожалению, недостатком этого является то, что тесты читаются не так естественно, как при использовании “given/on/it”.
3.3. Дополнительные стили
Spek не применяет эти стили и позволяет менять ключевые слова по своему усмотрению. Единственное требование заключается в том, чтобы внутри него существовали все утверждения и чтобы на этом уровне не было найдено других блоков.
Полный список доступных вложенных ключевых слов приведен ниже:
- given
- on
- describe
- context
Мы можем использовать их, чтобы придать нашим тестам наилучшую структуру, соответствующую тому, как мы хотим их написать.
3.4. Тесты, основанные на данных
Механизм, используемый для определения тестов, представляет собой не что иное, как простые вызовы функций. Это означает, что мы можем выполнять с ними другие действия, как с любым обычным кодом. В частности, при желании мы можем вызывать их на основе данных.
Самый простой способ сделать это - выполнить цикл по данным, которые мы хотим использовать, и вызвать соответствующий блок из этого цикла:
class DataDrivenTest : Spek({
describe("A data driven test") {
mapOf(
"hello" to "HELLO",
"world" to "WORLD"
).forEach { input, expected ->
describe("Capitalising $input") {
it("Correctly returns $expected") {
assertEquals(expected, input.toUpperCase())
}
}
}
}
})
При необходимости мы можем делать все, что угодно, но это, вероятно, самое полезное.
4. Утверждения
Spek не предписывает какой-либо конкретный способ использования утверждений. Вместо этого он позволяет нам использовать любую структуру утверждений, которая нам наиболее удобна.
Очевидным выбором будет класс org.junit.jupiter.api.Assertions, поскольку мы уже используем фреймворк JUnit 5 в качестве средства тестирования.
Однако мы также можем использовать любую другую библиотеку утверждений, которую захотим, если это улучшит наши тесты – например, Kluent, Expekt или HamKrest.
Преимущество использования этих библиотек вместо стандартного класса JUnit 5 Assertions заключается в удобочитаемости тестов.
Например, приведенный выше тест, переписанный с использованием Fluent, читается как:
class CalculatorTest : Spek({
describe("A calculator") {
val calculator = Calculator()
describe("Addition") {
val result = calculator.add(3, 5)
it("Produces the correct answer") {
result shouldEqual 8
}
}
}
})
5. Обработчики до/после обработки
Как и в большинстве тестовых фреймворков, Spek также может выполнять логику до/ после тестирования.
Как и следует из их названия, это блоки, которые выполняются до или после самого теста.
Здесь доступны следующие опции:
- beforeGroup
- afterGroup
- beforeEachTest
- afterEachTest
Они могут быть помещены в любое из ключевых слов вложенности и будут применяться ко всему, что находится внутри этой группы.
По принципу работы Spek, весь код внутри любого из ключевых слов вложенности выполняется немедленно при запуске теста, но управляющие блоки выполняются в определенном порядке, сосредоточенном вокруг блоков ит.
Работая извне, Spec будет выполнять каждый тестовый блок before непосредственно перед каждым ит-блоком, вложенным в ту же группу, и каждый тестовый блок afterEach сразу после каждого ит-блока. Аналогично, Speak будет выполнять каждый блок before Group непосредственно перед каждой группой, а каждый блок after Group - сразу после каждой группы в текущей вложенности.
Это сложно, и лучше всего объяснить на примере:
class GroupTest5 : Spek({
describe("Outer group") {
beforeEachTest {
System.out.println("BeforeEachTest 0")
}
beforeGroup {
System.out.println("BeforeGroup 0")
}
afterEachTest {
System.out.println("AfterEachTest 0")
}
afterGroup {
System.out.println("AfterGroup 0")
}
describe("Inner group 1") {
beforeEachTest {
System.out.println("BeforeEachTest 1")
}
beforeGroup {
System.out.println("BeforeGroup 1")
}
afterEachTest {
System.out.println("AfterEachTest 1")
}
afterGroup {
System.out.println("AfterGroup 1")
}
it("Test 1") {
System.out.println("Test 1")
}
}
}
})
Результатом выполнения описанного выше действия является:
BeforeGroup 0
BeforeGroup 1
BeforeEachTest 0
BeforeEachTest 1
Test 1
AfterEachTest 1
AfterEachTest 0
AfterGroup 1
AfterGroup 0
Сразу видно, что внешние блоки beforeGroup/afterGroup относятся ко всему набору тестов, в то время как внутренние блоки beforeGroup/afterGroup относятся только к тестам в том же контексте.
Мы также можем видеть, что все блоки before Group выполняются перед любыми блоками before Each Test, и наоборот для afterGroup/afterEachTest.
6. Испытуемые
Часто нам приходится писать одну спецификацию для одного тестируемого объекта. Speak предлагает удобный способ написания этой спецификации, который позволяет автоматически управлять тестируемым объектом. Для этого мы используем базовый класс Subject Speak вместо класса Spec.
Когда мы используем это, нам нужно объявить вызов блока subject на самом внешнем уровне. Это определяет объект тестирования. Затем мы можем ссылаться на него в любом из наших тестовых кодов как на объект.
Мы можем использовать это, чтобы переписать наш предыдущий тест на калькуляторе следующим образом:
class CalculatorTest : SubjectSpek<Calculator>({
subject { Calculator() }
describe("A calculator") {
describe("Addition") {
val result = subject.add(3, 5)
it("Produces the correct answer") {
assertEquals(8, result)
}
}
}
})
Может показаться, что это не так уж много, но это может помочь сделать тесты намного более удобочитаемыми, особенно когда необходимо рассмотреть большое количество тестовых примеров.
6.1. Зависимости Maven
Чтобы использовать расширение Subject, нам нужно добавить зависимость в нашу сборку Maven:
<dependency>
<groupId>org.jetbrains.spek</groupId>
<artifactId>spek-subject-extension</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
7. Итог
Speak - это мощный фреймворк, позволяющий выполнять некоторые легко читаемые тесты, что, в свою очередь, означает, что все подразделения организации могут их прочитать.
Это важно для того, чтобы все коллеги могли внести свой вклад в тестирование всего приложения.
Оригинал статьи: https://www.baeldung.com/kotlin/spek