предисловие
Недавно команда представила Spock, фреймворк для тестирования. После того, как я на самом деле использовал его, у меня был очень хороший опыт. Эта статья предназначена для закрепления входных знаний, а вторая - для распространения их среди всех.
Прежде чем изучать среду тестирования Spock, мы должны сначала сосредоточиться на самом модульном тесте и понять наши общие болевые точки модульного тестирования, чтобы мы могли лучше понять, что такое среда тестирования Spock, почему мы ее используем и какие болевые точки она может решить. решать.
Теперь давайте начнем.
О модульном тестировании
Когда мы пишем код, нам неизбежно нужно его тестировать. Существует множество видов тестов. Для Javaers самый простой тест — написать основную функцию для запуска результата функции или запустить систему и имитировать запрос, чтобы увидеть, ввод и вывод соответствуют ожиданиям.Для тестирования системы используются более продвинутые различные наборы тестов. У каждого теста есть свои проблемы, такие как правильность работы теста, узкие места в производительности системы и т. д.
Тогда что мы часто говорим?
модульный тест(английский: Unit Testing), также известный кактест модуля, направлен напрограммный модуль(дизайн программного обеспеченияНаименьшая единица) для проведения тестовой работы по проверке правильности. Программный модуль — это наименьшая тестируемая часть приложения. существуетпроцедурное программированиеВ объектно-ориентированном программировании единицей является отдельная программа, функция, процедура и т. д.; в объектно-ориентированном программировании наименьшая единица — это метод, включая методы базового класса (суперкласса), абстрактного класса или производного класса ( подкласс).
-- из Википедии
Выше описание из Википедии.
Модульное тестирование, безусловно, не является необходимой вещью, ни один тест вашей программы после того, как команда QA сквозное тестирование и интеграционное тестирование, но и для обеспечения правильности. Но с другой точки зрения надо было еще и протестировать агрегат. Например, одним из обязательных условий является обеспечение дальнейшего развертывания модульного тестирования, так как во время реконструкции кода модульное тестирование не сдвинется ни на дюйм.
1.1 Преимущества модульного тестирования
Преимущества модульного тестирования включают, но не ограничиваются:
-
Улучшить качество программного обеспечения
Высококачественное модульное тестирование может гарантировать качество разработки и надежность программы.Чем раньше обнаружен дефект, тем дешевле его устранение.
-
Содействовать оптимизации кода
Модульное тестирование писателей и защитников - это инженеры-разработчики, разработчики будут продолжать проверять свой код в этом процессе, чтобы (неосознанно) оптимизировать свой код.
-
Улучшить эффективность НИОКР
Написание юнит-тестов якобы отнимает время разработки проекта, но на последующих этапах совместной отладки, интеграции и регрессионного тестирования код с высоким покрытием юнит-тестами имеет меньше дефектов, а проблемы исправлены, что помогает повысить общую эффективность НИОКР. .
-
Повышение достоверности реконструкции
Рефакторинг кода обычно включает относительно низкоуровневые изменения, такие как изменение базовой структуры данных и т. д., и часто затрагиваются службы верхнего уровня; под защитой модульного тестирования мы будем больше доверять рефакторинговому коду.
1.2 Основные принципы модульного тестирования
Макроскопически, тесты единиц должны соответствоватьAIRв общем:
- А: автоматический
- Я: независимый
- Р: Повторяемый
Микроскопически уровень кода юнит-теста должен соответствоватьBCDEв общем:
- B: Граница, граничный тест, включая границу контура, специальное значение, особый момент времени, последовательность данных и т. д.
- C: Исправьте, исправьте ввод и получите ожидаемый результат**
- D: Дизайн, в соответствии с проектной документацией, для написания модульных тестов
- **E: Ошибка, цель модульного тестирования — доказать, что программа неверна, а не доказать, что программа не содержит ошибок. **Чтобы найти скрытые ошибки в коде, нам нужно иметь некоторый обязательный неправильный ввод (например, недопустимые данные, ненормальный процесс, ввод, не разрешенный для бизнеса и т. д.) при написании тестовых примеров, чтобы получить ожидаемые неправильные результаты.
1.3 Общие сценарии модульного тестирования
- Пишите модульные тесты перед разработкой, описывайте требования с помощью тестов и разработки через тестирование. (Если вы не знакомы с TDD, можете погуглить)
- Получайте своевременную обратную связь в процессе разработки и заранее выявляйте проблемы.
- Применяется к автоматизированным сборкам или процессам непрерывной интеграции для выполнения регрессионного тестирования каждой модификации кода. (Обеспечение качества CI/CD)
- В качестве основы для реконструкции надежна реконструкция проверки.
1.4 Общие болевые точки модульного тестирования
В ежедневном развитии могут встречаться следующие болевые точки:
- Контекст теста зависит от внешних служб (таких как службы базы данных)
- В контексте теста есть зависимости кода (например, фреймворки и т. д.).
- Модульные тесты сложно поддерживать и понимать (отсутствие семантики)
- Для многосценового разного ввода и вывода кода юнит-теста будет много.
- ...
Небольшое пояснение к вышеуказанным пунктам.
Во-первых, размер кода тестового кода никогда не будет меньше бизнес-кода (при условии наличия индикатора покрытия и отсутствия читерства), иногда функция имеет несколько входов и выходов. размер будет только увеличиваться. Кода много, и однотестовый код не такой интуитивный, как бизнес-код (это зависит от способа написания комментариев, что грязно и утомительно), а некоторые кодеры не обращают внимания на читабельность кода, что будет в конечном итоге привести к тому, что код с модульным тестированием будет труднее читать и сложнее поддерживать. В то же время большинство фреймворков модульного тестирования очень навязчивы к коду, и если вы хотите понять модульное тестирование, вы должны сначала узнать, какой фреймворк модульного тестирования. С этой точки зрения сложность обслуживания возросла.
Кроме того, в юнит-тестах есть внешние зависимости, то есть первый и второй пункты, часто сложно написать чистый юнит-тест без зависимостей, например, он зависит от базы данных и других модулей, поэтому многие пишут юнит-тесты. При тестировании решите полагаться на некоторые ресурсы, например на локальный запуск базы данных. Эти так называемые «модульные тесты», как правило, популярны, но они часто сбивают с толку проекты, в которых участвуют несколько человек. Например, если вы хотите прочитать файл локально или подключиться к базе данных, у других людей, которые модифицируют код (или в системе непрерывной интеграции), нет этих вещей, поэтому тесты не могут пройти. В конце концов, большинство этих тестовых кодов оказались непригодными для использования, и их не хотелось удалять, поэтому их пришлось закомментировать и кинуть туда. С постепенным развитием проектов с открытым исходным кодом проблема зависимости от внешних ресурсов может быть решена с помощью некоторых средств тестирования, таких как использование базы данных H2 в памяти вместо подключения к реальной тестовой базе данных, но типы ресурсов, которые могут быть заменены всегда ограничены.
В реальном рабочем процессе возникает еще один тип проблем с зависимостями, с которыми трудно справиться: зависимость от кода. Например, методы одного объекта вызывают методы других объектов, а другие объекты вызывают больше объектов, в итоге образуя чрезвычайно огромное дерево вызовов. Позже появились некоторые фиктивные фреймворки, такие как Java JMockit, EasyMock или Mockito. Используя этот тип структуры, можно относительно легко делать предположения и проверки с помощью фиктивного метода, что является качественным скачком по сравнению с предыдущим методом.
Однако здесь необходимо подчеркнуть один момент, т.Простота написания модульных тестов больше всего связана с качеством кода и имеет решающее значение. Независимо от того, какой фреймворк для тестирования используется в проекте, он не может решить проблему сложности тестирования самого кода.
Чтобы поставить его просто, иногда вы чувствуете, что ваш код трудно написать тесты подразделения, что означает, что код не совсем написан. Вам необходимо обратить внимание, является ли логический абстрактный дизайн кода, и рефакторинг вашего шага кода за шагом, чтобы сделать изменение вашего кода. Легко тестировать. Но они относятся к знаниям рефакторинга кода и включают в себя множество принципов проектирования. Рекомендуемое чтение «рефакторинг - улучшение конструкции существующего кода», «Искусство модификации кода» и «Развитие Agile Software: принципы, шаблоны и практики».
1.5 Изменения в менталитете
Многие разработчики относятся к модульному тестированию с умственным барьером.
-
Это то, что делают тестовые одноклассники. (Разработчикам нужно хорошо поработать над модульным тестированием.
-
Код модульного теста является избыточным. (Общая функция автомобиля сильно зависит от того, является ли тест каждого компонента блока нормальным или нет.
-
Код модульного тестирования не требует обслуживания. Через полтора года практически в заброшенном состоянии (код юнит-тестов нужно поддерживать вместе с разработкой проекта
-
Юнит-тесты диалектически не связаны с онлайн-сбоями. (Хорошие модульные тесты могут свести к минимуму онлайн-сбои
О Споке
Spock предоставляет вам все инструменты тестирования, которые могут вам понадобиться на протяжении всего жизненного цикла тестирования. Он поставляется со встроенной фиктивной заглушкой и некоторыми дополнительными тестовыми аннотациями, созданными специально для интеграционного тестирования. В то же время, поскольку Spock является более новой средой тестирования, у нее есть время обнаружить распространенные ловушки существующих сред и либо устранить их, либо предложить более элегантное обходное решение.
- Spock — это среда тестирования и спецификации для приложений Java и Groovy.
- В тестовом коде используется язык спецификации, расширенный на основе языка groovy.
- Вызов тестов через junit runner, совместимый с большинством сценариев запуска junit (ide, инструменты сборки, непрерывная интеграция и т. д.)
Groovy
- Язык JVM, предназначенный для «расширения JAVA».
- Дружелюбный к JAVA-разработчикам
- Вы можете использовать синтаксис Java и API
- Лаконичная и выразительная грамматика
- Типичные приложения: jenkins, elasticsearch, gradle
specification language
спецификация исходит из недавней популярности написанияBDD (поведенческое тестирование разработки, ориентированной на поведение). На основе TDD поведение кода выражается через тесты. Используйте некоторый язык спецификации, чтобы описать, что программа «должна» делать, а затем тестовая среда читает это описание и проверяет, что приложение работает должным образом. Преобразуйте требования в три абзаца «Дано/Когда/Тогда», чтобы вы увидели, что тестовая среда имеет эту грамматику из трех абзацев «Дано/Когда/Тогда». Вообще говоря, за этим стоят идеи BDD, такие как Cucumber и Cucumber. на рисунке выше.
Спок быстро использовать
Теперь давайте используем Спока как можно быстрее.
3.0 Создайте пустой проект
Создайте пустой проект:spock-example
, выберите проект maven.
3.1 Зависимости
<dependencies>
<!-- Mandatory dependencies for using Spock -->
<!-- 使用Spock必须的依赖 -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.5</version>
<scope>test</scope>
</dependency>
<!-- Optional dependencies for using Spock -->
<!-- 选择性使用的Spock相关依赖 -->
<dependency> <!-- use a specific Groovy version rather than the one specified by spock-core -->
<!-- 不使用Spock-core中定义的Groovy版本,而是自己定义 -->
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.7</version>
<type>pom</type>
</dependency>
<dependency> <!-- enables mocking of classes (in addition to interfaces) -->
<!-- mock 接口和类时要用 -->
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
<dependency> <!-- enables mocking of classes without default constructor (together with CGLIB) -->
<!-- mock 类要用 -->
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.6</version>
<scope>test</scope>
</dependency>
<dependency> <!-- only required if Hamcrest matchers are used -->
<!-- Hamcrest 是一个用于编写匹配对象的框架,如果用到了Hamcrest matchers,需要加这个依赖 -->
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<!-- Dependencies used by examples in this project (not required for using Spock) -->
<!-- 使用h2base做测试数据库-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 Плагины
<plugins>
<!-- Mandatory plugins for using Spock -->
<!--使用Spock的强制性插件 -->
<plugin>
<!-- The gmavenplus plugin is used to compile Groovy code. To learn more about this plugin,visit https://github.com/groovy/GMavenPlus/wiki -->
<!-- 这个 gmavenplus 插件是用于编译Groovy代码的 . 想获取更多此插件相关信息,visit https://github.com/groovy/GMavenPlus/wiki -->
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Optional plugins for using Spock -->
<!-- 选择性使用的Spock相关插件-->
<!-- Only required if names of spec classes don't match default Surefire patterns (`*Test` etc.) -->
<!--只有当测试类不匹配默认的 Surefire patterns (`*Test` 等等.)-->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
<configuration>
<useFile>false</useFile>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
...
</plugins>
3.3 Проектирование исходного каталога тестов
Поскольку spock основан на языке groovy, вам необходимо создать исходный каталог groovy: сначала создайте каталог с именем groovy в тестовом каталоге, а затем установите его в качестве исходного каталога теста.
3.4 Напишите тестируемый класс
/**
* @author Richard_yyf
* @version 1.0 2019/10/1
*/
public class Calculator {
public int size(String str){
return str.length();
}
public int sum(int a, int b) {
return a + b;
}
}
3.5 Создать тестовый класс
Ctrl + Shift + T
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Title
import spock.lang.Unroll
/**
*
* @author Richard_yyf
* @version 1.0 2019/10/1
*/
@Title("测试计算器类")
@Subject(Calculator)
class CalculatorSpec extends Specification {
def calculator = new Calculator()
void setup() {
}
void cleanup() {
}
def "should return the real size of the input string"() {
expect:
str.size() == length
where:
str | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
// 测试不通过
def "should return a+b value"() {
expect:
calculator.sum(1,1) == 1
}
// 不建议用中文哦
@Unroll
def "返回值为输入值之和"() {
expect:
c == calculator.sum(a, b)
where:
a | b | c
1 | 2 | 3
2 | 3 | 5
10 | 2 | 12
}
}
3.6 Запуск теста
3.7 Мок-зависимости
Вот симуляция службы кеша в качестве примера
/**
* @author Richard_yyf
* @version 1.0 2019/10/2
*/
public interface CacheService {
String getUserName();
}
public class Calculator {
private CacheService cacheService;
public Calculator(CacheService cacheService) {
this.cacheService = cacheService;
}
public boolean isLoggedInUser(String userName) {
return Objects.equals(userName, cacheService.getUserName());
}
...
}
тестовый класс
class CalculatorSpec extends Specification {
// mock对象
// CacheService cacheService = Mock()
def cacheService = Mock(CacheService)
def calculator
void setup() {
calculator = new Calculator(cacheService)
}
def "is username equal to logged in username"() {
// stub 打桩
cacheService.getUserName(*_) >> "Richard"
when:
def result = calculator.isLoggedInUser("Richard")
then:
result
}
...
}
запустить тест
Спок уходит глубоко
В Spock поведение тестируемой системы (SUT) определяется спецификацией. При написании тестов с использованием инфраструктуры Spock класс теста должен наследоваться от класса Specification. Именование соответствует спецификации Java.
Спок инфраструктура
Каждый метод тестирования может напрямую использовать текст в качестве имени метода, и метод внутренне определяетсяgiven-when-then
Трехсегментная блочная (блочная) композиция. Кроме этого естьand
,where
,expect
Несколько разных блоков.
@Title("测试的标题")
@Narrative("""关于测试的大段文本描述""")
@Subject(Adder) //标明被测试的类是Adder
@Stepwise //当测试方法间存在依赖关系时,标明测试方法将严格按照其在源代码中声明的顺序执行
class TestCaseClass extends Specification {
@Shared //在测试方法之间共享的数据
SomeClass sharedObj
def setupSpec() {
//TODO: 设置每个测试类的环境
}
def setup() {
//TODO: 设置每个测试方法的环境,每个测试方法执行一次
}
@Ignore("忽略这个测试方法")
@Issue(["问题#23","问题#34"])
def "测试方法1" () {
given: "给定一个前置条件"
//TODO: code here
and: "其他前置条件"
expect: "随处可用的断言"
//TODO: code here
when: "当发生一个特定的事件"
//TODO: code here
and: "其他的触发条件"
then: "产生的后置结果"
//TODO: code here
and: "同时产生的其他结果"
where: "不是必需的测试数据"
input1 | input2 || output
... | ... || ...
}
@IgnoreRest //只测试这个方法,而忽略所有其他方法
@Timeout(value = 50, unit = TimeUnit.MILLISECONDS) // 设置测试方法的超时时间,默认单位为秒
def "测试方法2"() {
//TODO: code here
}
def cleanup() {
//TODO: 清理每个测试方法的环境,每个测试方法执行一次
}
def cleanupSepc() {
//TODO: 清理每个测试类的环境
}
Feature methods
Это ядро спецификации Spock, описывающей поведение, которое должна иметь ТУС. Каждая спецификация будет содержать набор связанных методов Feature:
def "should return a+b value"() {
expect:
calculator.sum(1,1) == 1
}
blocks
Каждый метод функции разделен на разные блоки, и разные блоки находятся на разных этапах выполнения теста.Когда тест выполняется, каждый блок выполняется в соответствии с разными порядками и правилами, как показано на следующем рисунке:
-
Setup Blocks
setup
также можно записать какgiven
, в этом блоке будут размещены инициализаторы, относящиеся к этой тестовой функции, например:def "is username equal to logged in username"() { setup: def str = "Richard" // stub 打桩 cacheService.getUserName(*_) >> str when: def result = calculator.isLoggedInUser("Richard") then: result }
-
When and Then Blocks
when
иthen
необходимо использовать вместе сwhen
Выполнение тестируемой функции вthen
определить, соответствует ли оно ожиданиям -
Expect Blocks
expect можно рассматривать как упрощенную версию when+then, например
when: def x = Math.max(1, 2) then: x == 2
упрощено до
expect: Math.max(1, 2) == 2
утверждение
Условие аналогично утверждению в junit, как в приведенном выше примере,В then или expect все операторы верхнего уровня, возвращаемое значение которых является логическим, будут утверждены по умолчанию..Если вы хотите добавить утверждения в другом месте, вам нужно явно добавить ключевое слово assert
утверждение об исключении
Если вы хотите убедиться, что было выбрано исключение, вы можете использовать throw()
def "peek"() {
when: stack.peek()
then: thrown(EmptyStackException)
}
Если вы хотите убедиться, что какое-то исключение не было вызвано, вы можете использовать notThrown()
Mock
Макеты — это поведения, которые описывают (обязательные) взаимодействия между объектами в соответствии со спецификацией и их соавторами.
1 * subscriber.receive("hello")
| | | |
| | | argument constraint
| | method constraint
| target constraint
cardinality
Создать объект Mock
def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
Инъекционный макет объекта
class PublisherSpec extends Specification {
Publisher publisher = new Publisher()
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
def setup() {
publisher.subscribers << subscriber // << is a Groovy shorthand for List.add()
publisher.subscribers << subscriber2
}
Ограничения частоты вызовов (мощность)
1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive)
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls, including zero
// (rarely needed; see 'Strict Mocking')
целевое ограничение
1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello") // a call to any mock object
ограничение метода
1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello") // a method whose name matches the given regular expression (here: method name starts with 'r' and ends in 'e')
ограничение аргумента
1 * subscriber.receive("hello") // an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument that is unequal to the String "hello"
1 * subscriber.receive() // the empty argument list (would never match in our example)
1 * subscriber.receive(_) // any single argument (including null)
1 * subscriber.receive(*_) // any argument list (including the empty argument list)
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive(endsWith("lo")) // any non-null argument that is-a String
1 * subscriber.receive({ it.size() > 3 && it.contains('a') })
// an argument that satisfies the given predicate, meaning that
// code argument constraints need to return true of false
// depending on whether they match or not
// (here: message length is greater than 3 and contains the character a)
Корневая свая
Заглушка — это действие, при котором соавтор отвечает на вызов метода определенным образом. При заглушке метода вам все равно, сколько раз этот метод вызывается, вы просто хотите, чтобы он возвращал какое-то значение при вызове или выполнял какой-то побочный эффект.
subscriber.receive(_) >> "ok"
| | | |
| | | response generator
| | argument constraint
| method constraint
target constraint
как:subscriber.receive(_) >> "ok"
Это означает, что независимо от того, какой экземпляр или параметр, вызов метода приема возвращает строку ok.
вернуть фиксированное значение
использовать>>
оператор, возвращающий фиксированное значение
subscriber.receive(_) >> "ok"
последовательность возвращаемых значений
Возвращает последовательность, которая повторяется и возвращает указанные значения по порядку. Как показано ниже, первый вызов возвращает ok, второй вызов возвращает ошибку и так далее.
subscriber.receive(_) >>> ["ok", "error", "error", "ok"]
Возвращаемое значение динамического расчета
subscriber.receive(_) >> { args -> args[0].size() > 3 ? "ok" : "fail" }
subscriber.receive(_) >> { String message -> message.size() > 3 ? "ok" : "fail" }
производить побочные эффекты
subscriber.receive(_) >> { throw new InternalError("ouch") }
цепная реакция
subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"
Эпилог
В этой статье представлены основы модульного тестирования и некоторые примеры использования Spock. Используя Spock, вы можете наслаждаться удобным и универсальным набором тестов языка сценариев groovy, а написанный тестовый код становится более элегантным и читабельным.
Но это только первый шаг. Изучение того, как использовать тестовый фреймворк, — это всего лишь предварительное изучение «навыков». Рефакторинг кода и написание более тестируемого кода, как заставить команду практиковать TDD и т. д.
Надеюсь поделиться более соответствующими знаниями в будущем.