Болевые точки одиночного теста
- Выполнение кейса занимает слишком много времени (слишком много зависимостей в тестовом контексте | слишком сильная связь в дизайне)
- Слишком много тестов в одном методе (объект данных сложный, тестируемый метод делает слишком много)
- Слишком много настроек/разборок (указывает на то, что тестируемый класс слишком связан)
- Данные находятся в базе данных, слишком много членов с полномочиями на выполнение операций, их легко изменить и легко вызвать путаницу (база данных H2).
- Измените одно место, затронуты несколько тестов, возможно, это проблема дизайна теста, возможно, в коде реализации слишком много зависимостей.
Введение в среду тестирования Spock
Spock — это среда тестирования со следующими основными функциями:
- Платформа модульного тестирования, которую можно применять к приложениям Java или Groovy.
- В тестовом коде используется язык спецификаций, расширенный на основе языка groovy.
- Тест запускается через средство выполнения junit, которое совместимо с большинством сценариев выполнения junit (ide, средство сборки, непрерывная интеграция и т. д.).
- Идеи дизайна фреймворка относятся к JUnit, jMock, RSpec, Groovy, Scala, Vulcans...
Spock in 5 minutes
требуемые зависимости
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Mandatory dependencies for using Spock -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.1-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.1-groovy-2.4</version>
<scope>test</scope>
</dependency>
<!-- enables mocking of classes (in addition to interfaces) -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
<!-- enables mocking of classes without default constructor -->
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.6</version>
<scope>test</scope>
</dependency>
Базовая структура
Тестовые классы Спока получены изSpecificationКласс, названный в соответствии со спецификацией Java. Каждый метод тестирования может напрямую использовать текст в качестве имени метода, и метод внутренне определяетсяgiven-when-thenТрехсегментная блочная (блочная) композиция. Кроме того, естьand、where、expectи так далее для нескольких разных блоков.
@Title("测试的标题")
@Narrative("""关于测试的大段文本描述""")
// 标明被测试的类是Adder
@Subject(TestedClassName)
// 当测试方法间存在依赖关系时
// 标明测试方法将严格按照其在源代码中声明的顺序执行
@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: 清理每个测试类的环境
}
}
утверждение
- В блоке then нет необходимости
assertEquals("断言提示", left, right)Таким образом, напишите прямоleft == rightТакого логического выражения достаточно. - Используя синтаксис Groovy, Spock использует N * method(), чтобы определить, вызывался ли метод N раз. и
N * method() >> trueЭто означает, что метод метода вызывается N раз, и каждый раз возвращаемое значение метода истинно.
Параметрический тест
Спок использует блоки where для предоставления тестовых методов табличными тестовыми данными. Заголовок — это имя переменной или выражение, которое будет использоваться в утверждении в методе тестирования.|Отдельные входные параметры с||Отдельный ввод и вывод. Эти параметры можно использовать#参数名путь в@UnrollОпределяется в описании или имени метода тестирования или в списке параметров метода тестирования, а затем используется в блоке where.
// where 块中的每行参数都转换为一个独立的测试用例
@Unroll("test #para0, #para1")
def "测试方法3"(int first, int second) {
// ... ...
where: "parameterized sample"
para0 | para1 | para2 || para3 | first | second
10 | 2 | 3 || 7 | 2 | 5
}
Простой пример
Тестируемый класс (src/main/java/SumUtils.java)
public class SumUtils {
public static int sum(int a, int b) {
return a + b;
}
}
тестовый класс (src/test/groovy/SumUtilsTest.groovy)
@Title("测试加法工具类")
@Subject(SumUtils)
class SumUtilsTest extends Specification {
@Unroll
def "test Sum"() {
expect:
res == SumUtils.sum(a , b)
where:
a | b | res
1 | 1 | 2
0 | 0 | 0
-1 | -1 | -2
0 | -1 | 0
}
}
Общий синтаксис Спока
Mocking
Насмешка — это поведение описания (обязательных) взаимодействий между объектом в соответствии со спецификацией и его сотрудниками.
1 * subscriber.receive("hello")
| | | |
| | | argument constraint
| | method constraint
| target constraint
cardinality
Создать фиктивный объект
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)
Stubing
Заглушка — это действие, при котором соавтор отвечает на вызов метода определенным образом. При заглушке метода вам все равно, сколько раз этот метод вызывается, вы просто хотите, чтобы он возвращал какое-то значение при вызове или выполнял какой-то побочный эффект.
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"
Полный пример одного теста
@Title("招行业务类测试")
@Subject(CmbPayLocalServiceImpl)
class CmbPayLocalServiceImplTest extends Specification {
CmbPayLocalServiceImpl cmbPayLocalService = new CmbPayLocalServiceImpl()
ConfigBaseService configBaseService = Mock()
WeChatPayHelper helper = Mock()
CodeUrlMapLocalService codeUrlMapLocalService = Mock()
void setup() {
cmbPayLocalService.configBaseService = configBaseService
cmbPayLocalService.weChatPayHelper = helper
cmbPayLocalService.codeUrlMapLocalService = codeUrlMapLocalService
}
def "GetCodeUrl"() {
given: "定义一些变量"
def enterpriseNum = "AC310008"
def mchId = "308999170120019"
def fgTradeNo = System.currentTimeMillis().toString()
def start = "20190813130000"
def end = "20190815130000"
def param = new WeChatQrCodePayParam(
enterpriseNum: enterpriseNum,
mchId: mchId,
outTradeNo: System.currentTimeMillis().toString(),
body: "Groovy 测试",
totalFee: "1",
notifyUrl: ""
)
def record = new TransRecord<>(
enterpriseNum: enterpriseNum,
mchId: mchId,
fgTradeNo: fgTradeNo,
state: "1"
)
configBaseService.getConfig(_) >> ""
codeUrlMapLocalService.saveCodeUrlMapping(*_) >> true
when:
Map<String, Object> result = cmbPayLocalService.getCodeUrl(param, record)
then:
def url = result.get("url")
}
@Unroll
def "GetCodeUrlWithWheretime"() {
when:
def enterpriseNum = "AC310008"
def mchId = "308999170120019"
def fgTradeNo = System.currentTimeMillis().toString()
def param = new WeChatQrCodePayParam(
enterpriseNum: enterpriseNum,
mchId: mchId,
outTradeNo: System.currentTimeMillis().toString(),
body: "Groovy 测试",
totalFee: "1",
notifyUrl: ""
)
def record = new TransRecord<>(
enterpriseNum: enterpriseNum,
mchId: mchId,
fgTradeNo: fgTradeNo,
state: "1"
)
configBaseService.getConfig(_) >> "308999170120019"
codeUrlMapLocalService.saveCodeUrlMapping(*_) >> true
then:
StringUtils.isNotBlank((String)cmbPayLocalService.getCodeUrl(param, record).get("CodeUrl"))
where:
start | end | url
"20190813130000" | "20190815130000" | true
"20190813130000" | "20191015130000" | true
"20190813130000" | "20191015130000" | true
"20190813130000" | "20191015130000" | true
}
}
Компонент: заводной
Groovy — это объектно-ориентированный язык программирования, разработанный на платформе Java. Этот динамический язык имеет функции, аналогичные функциям Python, Ruby и Smalltalk, и может использоваться в качестве языка сценариев для платформы Java.
Синтаксис Groovy настолько похож на Java, что большая часть кода Java также является правильным кодом Groovy. Код Groovy динамически преобразуется компилятором в байт-код Java. Благодаря своему характеру работы на JVM Groovy может использовать библиотеки, написанные на других языках Java.
- точка с запятой в конце оператора; не является обязательным
- пройти через
defКлючевые слова определяют переменные и методы - Обычно пара двойных кавычек используется для обозначения строковой константы, «это строковая константа», используйте
\как escape-символ - Groovy обрабатывает 0, null, пустые массивы или пустые строки как ложные, а ненулевые значения, допустимые ссылки, непустые массивы и непустые строки — как истинные.
- использовать ключевые слова
defПредставляет динамический тип, как в JavaScriptvar - Модификатор доступа класса по умолчанию является общедоступным, а поле по умолчанию является закрытым (но к нему можно получить доступ напрямую с экземпляром класса).
- использовать
name, ${age > 18}"`
- Метод инициализации объекта аналогичен JSON, все выражение заключено в круглые скобки, поле и значение представлены парами, разделенными двоеточиями, массив или последовательность заключены в квадратные скобки, индекс массива начинается с 0, а пустая карта представлена [:]
- использовать
_в качестве заполнителей параметров. Его можно использовать для ссылки на параметры, методы, возвращаемые значения илиwhereПараметры теста в блоках
// 定义字符串
String str = ""
// 定义列表
List list = [1, 2, 3, 4, 5]
// 定义 map
Map map = [key1:val1, key2:val2]
Спецификация платежа Fusion
Должен придерживаться принципов AIR
Объяснение: Когда модульный тест запускается на линии, создается впечатление, что воздуха (AIR) не существует, но это очень важно для гарантии качества теста. На макроуровне хороший модульный тест обладает характеристиками автоматизации, независимости и повторяемости.
- А: автоматический
Модульные тесты должны быть полностью автоматизированными и неинтерактивными. Тестовые случаи обычно выполняются на регулярной основе, и процесс выполнения должен быть полностью автоматизирован, чтобы иметь смысл. Тест, вывод которого требует ручной проверки, не является хорошим модульным тестом. System.out не разрешается использовать для проверки человеческого тела в модульных тестах, и для проверки необходимо использовать assert.
- Я: Независимый
Держите модульные тесты независимыми. Чтобы убедиться, что модульные тесты стабильны, надежны и просты в обслуживании, сценарии модульных тестов не должны вызывать друг друга и зависеть от порядка выполнения. Пример счетчика: метод2 должен полагаться на выполнение метода1, а результат выполнения используется в качестве входных данных для метода2.
- R: Повторяемый (повторяемый)
Модульные тесты могут выполняться многократно, и на них не может влиять внешняя среда. Примечание. Модульные тесты обычно включаются в непрерывную интеграцию, и модульные тесты выполняются каждый раз при возврате кода. Если модульный тест зависит от внешней среды (сети, службы, промежуточного ПО и т. д.), это легко приведет к недоступности механизма непрерывной интеграции. Положительный пример: чтобы не подвергаться влиянию внешней среды, требуется изменить зависимость SUT на инъекцию при проектировании кода и использовать DI-фреймворк, такой как spring, для внедрения локальной реализации (памяти) или реализации Mock во время тестирование.
Убедитесь, что степень детализации теста достаточно мала
Для модульного тестирования убедитесь, что степень детализации теста достаточно мала, чтобы помочь выявить проблему. Детализация одного теста находится не более чем на уровне класса и, как правило, на уровне метода.
Описание: Только при небольшой степени детализации теста место ошибки может быть обнаружено как можно скорее при возникновении ошибки. Модульные тесты не несут ответственности за проверку логики межклассового или межсистемного взаимодействия, что является областью интеграционного тестирования.
Также есть юнит-тесты, написанные по сценарию, и один метод соответствует нескольким сценариям.
Ссылаться на:blog.CSDN.net/Закон также является игрой для всех людей/…
Инкрементальный код основного бизнеса, основного приложения и основного модуля гарантирует, что единый тест пройдет успешно.
Примечание. Новый код своевременно дополняет модульный тест. Если новый код влияет на исходный модульный тест, своевременно исправьте его.
Основная цель одиночного теста
Уровень покрытия операторов достигает 70%, уровень покрытия операторов и уровень покрытия ветвей основного модуля должны достигать 100%;
Примечание. Уровень DAO, уровень менеджера и служба с высокой степенью повторного использования, упомянутые в уровне приложения технической спецификации, должны быть протестированы в модульном режиме.
Единый тестовый код соответствует принципам BCDE
Обеспечить качество поставки протестированных модулей
- B: граница, проверка граничных значений, включая цикл, специальную выборку, проверку граничных значений, включая цикл, специальную выборку, особый момент времени, последовательность данных и т. д.
- C: Исправьте, исправьте ввод и получите ожидаемый результат. , исправьте ввод и получите ожидаемый результат.
- D: Дизайн в сочетании с проектной документацией для написания модульных тестов. , в сочетании с проектной документацией для написания модульных тестов.
- E: Ошибка, принудительный ввод информации об ошибке (например, недопустимые данные, ненормальное разрешение бизнес-процесса и т. д.) и get, принудительный ввод информации об ошибке (например, недопустимые данные, ненормальное бизнес-разрешение процесса и т. д.), и получить ожидаемый результат.
Взаимодействие со спецификацией базы данных
Для запросов, обновлений, удалений и других операций, связанных с базой данных,Вы не можете предположить, что данные в базе данных существуют, или непосредственно работать с базой данных для вставки данных, пожалуйста, используйте программу для вставки или импорта данных для подготовки данных.
Пример счетчика: в модульном тесте удаления строки данных в базе данных сначала вручную добавьте строку в качестве цели удаления, но вновь добавленные данные в этой строке не соответствуют бизнес-правилам вставки, что приводит к ненормальным результатам теста. .
Вы можете использовать базу данных h2 в памяти, чтобы убедиться, что модульные тесты не загрязняют тестовую базу данных.
Непроверяемый код предполагает необходимость рефакторинга
Для нетестируемого кода рекомендуется выполнить необходимый рефакторинг, чтобы сделать код тестируемым, и избегать написания нестандартного тестового кода, чтобы соответствовать требованиям теста.
Определить единичный диапазон измерения
Разработчики должны работать с тестировщиками, чтобы определить объем модульных тестов, а модульные тесты предпочтительно охватывают все тестовые случаи (UC).
изменение менталитета
- Так делают тестовые одноклассники.
- Код модульного теста является избыточным. Общая функция автомобиля сильно зависит от того, проходит ли проверка каждого компонента нормально или нет.
- Код модульного тестирования не требует обслуживания. По прошествии полутора лет он находится практически в состоянии заброшенности.
— Юнит-тесты диалектически не связаны с онлайн-сбоями. Хорошие модульные тесты могут свести к минимуму онлайн-сбои.