Автор: Мо Юань, технический эксперт Alibaba.
Введение
Модульное тестирование предназначено не только для проверки того, есть ли проблемы с кодом, который вы сейчас пишете, но, что более важно, оно может в значительной степени гарантировать будущие изменения и исправления в бизнесе.Bug
Риски, вызванные (или добавленные) изменениями кода, вызванными рефакторингом или рефакторингом.
В то же время модульное тестирование продвигается к написанию формального кода (разработка через тестирование), что может значительно улучшить дизайн структуры кода. Приоритизируя написание тестовых примеров, декомпозиция функций, процесс использования и интерфейс могут быть хорошо спроектированы с точки зрения пользователя, тем самым улучшая характеристики высокой связности и низкой связанности структуры кода. Делает будущие изменения требований или рефакторинг кода более эффективным и лаконичным.
Поэтому написание модульных тестов имеет большое значение для разработки и обслуживания продукта, технического улучшения и накопления!
2. Первый модульный тест
Сначала напишите модульный тест, который поможет понять и попрактиковаться в следующем содержании.
2.1 Среда разработки
IntelliJ IDEA
IntelliJ IDEA
Встроенный и включенный по умолчаниюTestNG
и плагин покрытия:
- TestNG
- покрытие
- мутационное тестирование
Проверьте, установлен ли и включен ли плагин TestNG в окне настроек:
Аналогично, плагин View Coverage может искать «Coverage». В IntelliJ IDEA есть три инструмента статистики покрытия, JaCoCo, Emma и IntelliJ IDEA поставляются с ними.
Точно так же при просмотре и установке плагина для тестирования мутаций можно выполнить поиск по запросу «тестирование мутаций PIT».
Eclipse
Eclipse
Вам необходимо самостоятельно установить плагины, связанные с модульным тестированием:
- TestNG
- покрытие
- мутационное тестирование
Плагин для выполнения модульных тестов TestNG. Вы можете найти установку «TestNG» в Eclipse Marketplace:
Плагин для получения покрытия юнит-тестами. Вы можете найти «EclEmma» в Eclipse Marketplace, чтобы установить:
Точно так же при просмотре и установке плагина для тестирования мутаций можно найти «Pitclipse».
2.2 Зависимости Maven
это здесьпоискJAR
новая версия пакета
- TestNG
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
- JMockit
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit-coverage</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
- Spring Test
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.kubek2k</groupId>
<artifactId>springockito</artifactId>
<version>${springockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.kubek2k</groupId>
<artifactId>springockito-annotations</artifactId>
<version>${springockito.version}</version>
<scope>test</scope>
</dependency>
- Другое (может понадобиться)
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.servlet.api.version}</version>
<scope>test</scope>
</dependency>
2.3 Создание модульных тестов
Далее вводится черезIDE
Как автоматически создавать юнит-тесты (можно и вручную):
IntelliJ IDEA
- 1. Нажмите сочетание клавиш Alt + Enter на строке кода имени класса тестируемого класса (строка кода ключевого слова «класс») (или удерживайте на ней указатель мыши, и когда появится значок желтой лампочки, щелкните раскрывающийся список меню с помощью мыши. ), выберите опцию «Создать тест» во всплывающем меню:
2. Во всплывающем окне выберите «TestNG» и выберите метод модульного теста, который необходимо создать, затем нажмите кнопку «ОК», чтобы создать модульный тест. (Имя среднего класса, имя пакета и необходимость создания методов «setUP» и «tearDown» можно выбрать в соответствии с вашей собственной ситуацией.)
3. Созданный модульный тест генерирует тестовый класс в тестовом каталоге проекта Maven:
Затмение:
- 1. Щелкните правой кнопкой мыши файл тестируемого класса «Создать -> Другое»:
2. Найдите «Test» во всплывающем окне, выберите «TestNG class» и нажмите кнопку «Next»:
3. Выберите в окне метод тестирования, который необходимо создать, и нажмите кнопку «Далее»:
4. Установите имя пакета, имя класса и аннотации в соответствии с вашей ситуацией:
образец кода
Вы можете обратиться к следующему примеру кода для написания модульных тестов:
package org.light4j.unit.test;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Tested;
import org.testng.Assert;
import org.testng.annotations.Test;
import wow.unit.test.remote.UserService;
import java.util.List;
/**
* 单元测试demo
*
* @author jiazuo.ljz
*/
public class BookServiceTest {
/**
* 图书持久化类,远程接口
*/
@Injectable
private BookDAO bookDAO;
/**
* 用户服务,远程接口
*/
@Injectable
private UserService userService;
/**
* 图书服务,本地接口
*/
@Tested(availableDuringSetup = true)
private BookService bookService;
/**
* 测试根据用户的Nick查询用户的图书列表方法
* 其中“getUserBooksByUserNick”方法最终需要通过UserID查询DB,
* 所以在调用此方法之前需要先对UserService类的getUserIDByNick方法进行Mock。
*/
@Test
public void testGetUserBooksByUserNick() throws Exception {
new Expectations() {
{
userService.getUserIDByNick(anyString); // Mock接口
result = 1234567; // Mock接口的返回值
times = 1; // 此接口会被调用一次
}
};
List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc");
Assert.assertNotNull(bookList);
}
}
2.4 Запуск модульных тестов
IntelliJ IDEA
- 1. Щелкните правой кнопкой мыши «Выполнить 'testMethod ()'» на методе тестирования, и в меню того же уровня есть «Отладка» и «Покрытие»:
Примечание. Вы также можете щелкнуть параметр панели инструментов для запуска слева направо: запуск, отладка, запуск покрытия, вы можете выбрать в соответствии с вашими потребностями:
2. Нажмите «Выполнить»:
Левая рамка: область класса модульного теста
Нижнее боковое поле: содержимое распечаток модульного теста и результаты выполнения.
Eclipse
- 1. Щелкните правой кнопкой мыши метод тестирования «Запуск от имени -> TestNG Test», и в меню того же уровня есть «Отладка как» и «Покрытие как»:
Примечание. Вы также можете щелкнуть параметр панели инструментов для запуска слева направо: покрытие, отладка, выполнение.
2. Нажмите «Выполнить»:
Левая рамка: результаты запуска модульного теста.
Нижнее боковое поле: что выводит модульный тест
Maven
- Выполните все модульные тесты в каталоге, войдите в каталог проекта и выполните: mvn test
- Выполнять определенные классы модульных тестов. Несколько тестовых классов можно разделить запятыми: mvn test -Dtest=Test1,Test2
- Выполнить метод определенного класса модульного теста: mvn test -Dtest=Test1#testMethod
- Выполнение модульных тестов в пакете: mvn test -Dtest=com/alibaba/biz/*
- Выполнение модульных тестов с использованием выражений пути в стиле ANT: mvn test -Dtest=**/*Test или mvn test -Dtest=**/???Test
- Игнорировать модульные тесты: mvn -Dmaven.test.skip=true
2.5 Покрытие юнит-тестами
IntelliJ IDEA
- 1. Беги
Нажмите «Выполнить покрытие»:
Левая ячейка 1: протестированные классы и покрытие
Левое поле 2: Область класса модульного тестирования
Среднее поле: код тестируемого класса. Зеленые линии закрыты, красные линии не закрыты
Правое поле: охватываются все классы. Дважды щелкните имя пакета, чтобы просмотреть подробное покрытие классов.
Нижнее боковое поле: содержимое распечаток модульного теста и результаты выполнения.
2. Выходной отчет
В окне запущенного процесса и вывода результатов есть строка «JMockit: Отчет о покрытии, записанный в» — это каталог файла отчета о покрытии, созданный JMocit:
Eclipse
- 1. Беги
Нажмите «Выполнить покрытие» (см. раздел «Выполнение модульных тестов»):
Левая рамка: результаты запуска
Среднее поле: код тестируемого класса. Зеленые линии закрыты, красные линии не закрыты
Поле справа: охватываются все классы, дважды щелкните имя пакета, чтобы просмотреть подробное покрытие классов (по сравнению с окном пакетов IntelliJ IDEA данные о покрытии классов отсутствуют)
Нижнее боковое поле: что выводит модульный тест
2. Выходной отчет
В окне запущенного процесса есть строка «JMockit: Отчет о покрытии, записанный в» и вывод результата, который представляет собой каталог файла отчета о покрытии, созданный EclEmma:
Отчет о покрытии
- Откройте файл index.html в каталоге:
Нажмите, чтобы просмотреть подробное описание файлов классов:
2.6 Тестирование вариантов
Мутационное тестирование является отличным дополнением к охвату. Это делает модульные тесты более надежными, чем покрытие. (подробности см. в разделе 5.4)
IntelliJ IDEA
- 1. Создайте прогон
Щелкните раскрывающийся список «Выполнить» и выберите «Редактировать конфигурации...», чтобы открыть окно «Выполнить/отладить конфигурации»:
Нажмите знак «+» в левом верхнем углу и выберите «Добавить PIT Runner»:
Обратите внимание, что два элемента «Целевые классы и исходный каталог» соответствуют точному пути к пакету и модулю класса модульного теста.После подтверждения нажмите кнопку «ОК», чтобы подтвердить создание.
2. Беги
После завершения создания щелкните значок «Выполнить» на панели инструментов, чтобы запустить его.
3. Выходной отчет
Последняя строка «Открыть отчет в браузере» в запущенном процессе и окне вывода результата — это ссылка на отчет, созданная плагином.
Нажмите, чтобы открыть отчет:
Eclipse
- 1. Беги
Щелкните правой кнопкой мыши класс модульного теста «Запуск от имени» -> «2 PIT Mutation Test», чтобы запустить:
Окно запуска процесса и вывода результата:
2. Выходной отчет
Вы можете просмотреть возможные дефекты кода, обнаруженные при тестировании мутаций, в этом окне: (Это лучше, чем плагин IDEA PIT)
Отчет об испытаниях можно просмотреть в этом окне:
Чтобы лучше разрабатывать и внедрять модульное тестирование в будущем, пожалуйста, продолжайте читать следующий контент.
3 Платформа модульного тестирования
3.1 TestNG
Junit4
иTestNG
даJava
Очень популярный фреймворк для модульного тестирования. так какTestNG
Более лаконичный, гибкий и многофункциональный, поэтому мы выбираемTestNG
.
Далее передается сJunit4
сравнение, чтобы понятьTestNG
Функции:
Поддержка аннотаций
Junit4
иTestNG
Сравнение аннотаций:
характеристика | JUnit4 | TestNG |
---|---|---|
тестовая аннотация | @Test | @Test |
Выполнить перед выполнением набора тестов | – | @BeforeSuite |
Выполнить после выполнения набора тестов | – | @AfterSuite |
выполнить перед тестированием | – | @BeforeTest |
выполнить после теста | – | @AfterTest |
Выполнить до выполнения тестовой группы | – | @BeforeGroups |
Выполнить после выполнения тестовой группы | – | @AfterGroups |
Выполнить перед выполнением тестового класса | @BeforeClass | @BeforeClass |
Выполняется после выполнения тестового класса | @AfterClass | @AfterClass |
выполнить до выполнения тестового метода | @Before | @BeforeMethod |
Выполняется после выполнения тестового метода | @After | @AfterMethod |
игнорировать тест | @ignore | @Test(enbale=false) |
ожидаемое исключение | @Test(expected = Exception.class) | @Test(expectedExceptions = Exception.class) |
тайм-аут | @Test(timeout = 1000) | @Test(timeout = 1000) |
// Тестовый метод TODO тестовый набор тестовых групп разница
существуетJunit4
середина,@BeforeClass
и@AfterClass
Может использоваться только для статических методов.TestNG
Такого ограничения нет.
исключительный тест
Тестирование исключений относится к тому, какие исключения следует генерировать в модульном тесте.
@Test(expected = ArithmeticException.class)
public void divisionWithException() {
int i = 1/0;
}
@Test(expectedExceptions = ArithmeticException.class)
public void divisionWithException() {
int i = 1/0;
}
игнорировать тест
Игнорировать тесты означает, что этот модульный тест можно игнорировать.
@Ignore("Not Ready to Run")
@Test
public void divisionWithException() {
System.out.println("Method is not ready yet");
}
@Test(enabled=false)
public void divisionWithException() {
System.out.println("Method is not ready yet");
}
испытание временем
Тестирование по времени означает, что если модульный тест выполняется дольше заданного времени (в миллисекундах), тест завершится ошибкой.
@Test(timeout = 1000)
public void infinity() {
while (true);
}
@Test(timeOut = 1000)
public void infinity() {
while (true);
}
набор тестов
Наборное тестирование относится к объединению нескольких модульных тестов в модуль и их последовательному запуску.
@RunWith
и@Suite
Аннотации используются для выполнения комплексных тестов. Ниже приведен код, показанный в "JunitTest5
"требуется после выполнения"JunitTest1
"и"JunitTest2
" также выполняется вместе. Все объявления нужно делать внутри класса.
java
@RunWith(Suite.class) @Suite.SuiteClasses({JunitTest1.class, JunitTest2.class})
public class JunitTest5 {
это использоватьXML
конфигурационный файл для выполнения комплексных тестов. Следующая конфигурация будет "TestNGTest1
"и"TestNGTest2
«Расстреляны вместе.
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
<suite name="My test suite">
<test name="testing">
<classes>
<class name="com.fsecure.demo.testng.TestNGTest1" />
<class name="com.fsecure.demo.testng.TestNGTest2" />
</classes>
</test>
</suite>
TestNG
Другой способ использования концепции групп: каждый метод испытаний может быть отнесен к группе в соответствии с функциональными характеристиками. Например:
@Test(groups="method1")
public void testingMethod1() {
System.out.println("Method - testingMethod1()");
}
@Test(groups="method2")
public void testingMethod2() {
System.out.println("Method - testingMethod2()");
}
@Test(groups="method1")
public void testingMethod1_1() {
System.out.println("Method - testingMethod1_1()");
}
@Test(groups="method4")
public void testingMethod4() {
System.out.println("Method - testingMethod4()");
}
Это класс с 4 методами, 3 группами (метод1, метод2 и метод4). Он более лаконичен в использовании, чем пакет XML.
подXML
Файл настраивает группу выполнения какmethed1
модульные тесты.
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
<suite name="My test suite">
<test name="testing">
<groups>
<run>
<include name="method1"/>
</run>
</groups>
<classes>
<class name="com.fsecure.demo.testng.TestNGTest5_2_0" />
</classes>
</test>
</suite>
Группировка делает интеграционные тесты более мощными. Например, мы могли бы просто выполнить все тесты в группах с именемDatabaseFuntion
тест.
Параметрический тест
Параметризованное тестирование относится к передаче различных значений параметров в модульный тест, чтобы проверить, правильно ли интерфейс обрабатывает различные параметры.
@RunWith
и@Parameter
Аннотации используются для предоставления значений параметров для модульных тестов,@Parameters
должен вернутьсяList
, параметры будут переданы в качестве параметров конструктору класса.
@RunWith(value = Parameterized.class)
public class JunitTest6 {
private int number;
public JunitTest6(int number) {
this.number = number;
}
@Parameters
public static Collection<Object[]> data() {
Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 }, { 4 } };
return Arrays.asList(data);
}
@Test
public void pushTest() {
System.out.println("Parameterized Number is : " + number);
}
}
Его использование неудобно: параметризованная проверка метода должна определять тестовый класс. Параметры теста передаются через статический метод с аннотацией @Parameters и возвращают список значений параметров. Члены возвращаемого значения метода затем инициализируются как члены класса через конструктор класса. Наконец, члены класса используются в качестве параметров для проверки тестируемого метода.
использоватьXML
Есть два способа предоставить параметры для теста, файла или аннотации @DataProvider.
XML
Параметризованный тест конфигурации файла
Добавьте аннотацию @Parameters к методу, и данные параметров будут предоставлены XML-файлом конфигурации TestNG. После этого мы можем повторно использовать тестовый пример с другим набором данных или даже с другим набором результатов. Кроме того, даже конечные пользователи QA или QE могут предоставить свои собственные XML-файлы для тестирования.
public class TestNGTest6_1_0 {
@Test
@Parameters(value="number")
public void parameterIntTest(int number) {
System.out.println("Parameterized Number is : " + number);
}
}
XML-файл
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
<suite name="My test suite">
<test name="testing">
<parameter name="number" value="2"/>
<classes>
<class name="com.fsecure.demo.testng.TestNGTest6_0" />
</classes>
</test>
</suite>
Параметризованный тест аннотации @DataProvider
использоватьXML
Данные инициализации файла, хотя и удобны, поддерживают только базовые типы данных. Для сложных типов вы можете использовать@DataProvider
Аннотация решена.
@Test(dataProvider = "Data-Provider-Function")
public void parameterIntTest(Class clzz, String[] number) {
System.out.println("Parameterized Number is : " + number[0]);
System.out.println("Parameterized Number is : " + number[1]);
}
//This function will provide the patameter data
@DataProvider(name = "Data-Provider-Function")
public Object[][] parameterIntTestProvider() {
return new Object[][]{
{Vector.class, new String[]{"java.util.AbstractList", "java.util.AbstractCollection"}},
{String.class, new String[] {"1", "2"}},
{Integer.class, new String[] {"1", "2"}}
};
}
@DataProvider как параметр объекта
P.S "TestNGTest6_3_0" - это простой объект с методами get и set.
@Test(dataProvider = "Data-Provider-Function")
public void parameterIntTest(TestNGTest6_3_0 clzz) {
System.out.println("Parameterized Number is : " + clzz.getMsg());
System.out.println("Parameterized Number is : " + clzz.getNumber());
}
//This function will provide the patameter data
@DataProvider(name = "Data-Provider-Function")
public Object[][] parameterIntTestProvider() {
TestNGTest6_3_0 obj = new TestNGTest6_3_0();
obj.setMsg("Hello");
obj.setNumber(123);
return new Object[][]{{obj}};
}
Параметризованный тест TestNG очень удобен в использовании, он может добавлять параметризованные тесты нескольких методов в тестовый класс (JUnit4 требует класс для одного метода).
Тест зависимости
Зависимое тестирование означает, что метод теста является зависимым и должен быть выполнен до выполнения другого теста. Если зависимый тест дает сбой, все подтесты игнорируются и не помечаются как не пройденные.
Фреймворк JUnit4 в основном ориентирован на изоляцию тестов и пока не поддерживает эту функцию.
Он использует dependOnMethods для реализации функции тестирования зависимостей следующим образом:
@Test
public void method1() {
System.out.println("This is method 1");
}
@Test(dependsOnMethods={"method1"})
public void method2() {
System.out.println("This is method 2");
}
Если метод1() выполняется успешно, то метод2() также будет выполнен, в противном случае метод2() будет проигнорирован.
Тестирование производительности
TestNG
Поддерживает тестирование производительности путем одновременного вызова тестового интерфейса из нескольких потоков.JUnit4
Не поддерживается, необходимо вручную добавить параллельный код для тестирования производительности.
@Test(invocationCount=1000, threadPoolSize=5, timeOut=100)
public void perfMethod() {
System.out.println("This is perfMethod");
}
Параллельное тестирование
TestNG
Он поддерживает одновременный вызов нескольких тестовых интерфейсов для выполнения тестов через несколько потоков, что может значительно сократить время выполнения теста по сравнению с традиционным однопоточным методом выполнения теста.
public class ConcurrencyTest {
@Test
public void method1() {
System.out.println("This is method 1");
}
@Test
public void method2() {
System.out.println("This is method 2");
}
}
Конфигурация параллельного теста:
<suite name="Concurrency Suite" parallel="methods" thread-count="2" >
<test name="Concurrency Test" group-by-instances="true">
<classes>
<class name="wow.unit.test.ConcurrencyTest" />
</classes>
</test>
</suite>
Резюме обсуждения
Из приведенного выше сравнения рекомендуется использовать TestNG в качестве среды модульного тестирования для проектов Java, поскольку TestNG имеет более краткие и мощные функции в параметризованном тестировании, тестировании зависимостей, пакетном тестировании (групповом) и параллельном тестировании. Кроме того, TestNG также покрывает все функциональные возможности JUnit4.
3.2 JMockit
Макетные сценарии использования:
Например, имитируйте следующие сценарии:
- 1. Вызов внешне зависимых приложений, таких как WebService и другие зависимости службы.
2. Вызовы уровня DAO (доступ к MySQL, Oracle, Emcache и другому базовому хранилищу) и т. д.
3. Асинхронно обмениваться уведомлениями между системами.
4. Метод B вызывается в методе A.
5. Некоторые приложения имеют свой класс (абстрактный, окончательный, статический), интерфейс, аннотацию, Enum и Native и т. д.
Принцип Mock-инструмента:
Mock
Принцип работы инструмента в основном следующий:
- 1. Этап записи: Запишите ожидания. Его также можно понимать как этап подготовки данных. Создайте зависимый класс, интерфейс или метод для имитации возвращаемых данных, затрат времени и количества вызовов.
2. Фаза воспроизведения: выполните тест, вызвав тестируемый код. В течение этого периода он будет вызывать объект Mock или метод записи первого этапа.
3. Стадия проверки: проверка. Вы можете проверить правильность возврата вызова, а также количество и последовательность вызовов фиктивного метода.
Сравнение некоторых текущих инструментов Mock:
Исторически или в настоящее время популярные инструменты Mock включают:EasyMock
,jMock
,Mockito
,Unitils Mock
,PowerMock
,JMockit
и другие инструменты.
Их характеристики сравниваются следующим образом:
Билл Бен.ITeye.com/blog/187219…
Отсюда видно,JMockit
Функции самые полные и мощные! Таким образом, инструмент Mock в нашем модульном тесте также выбирает JMockit. В то же время, в процессе разработки JMockit «Автоинъекция моков» и «Специальные поля для «любого» сопоставления аргументов» и различные полезные аннотации делают разработку модульных тестов более лаконичной и эффективной.
Введение в JMockit:
JMockit
заключается в том, чтобы помочь разработчикам писать модульные тестыMock
инструмент. Он разработан на основе пакета java.lang.instrument и использует библиотеку ASM для изменения байт-кода Java. Из-за этих двух моментов он может реализовать всемогущий Мок.
Типы JMockit, которые можно имитировать, включают:
- класс (абстрактный, окончательный, статический)
- interface
- enum
- annotation
- native
JMockit имеет два способа Mock:
- Behavior-oriented(Expectations & Verifications)
- State-oriented(MockUp)
С точки зрения непрофессионала, ориентированный на поведение — это Mock, основанный на поведении, который имитирует поведение целевого кода Mock, например, тестирование методом черного ящика. Ориентированный на состояние — это макет на основе состояния, который находится внутри целевого тестового кода. Входящие параметры можно проверить и сопоставить перед возвратом определенных результатов, подобно белому ящику. Новый MockUp, ориентированный на состояние, может в основном имитировать любой код или логику.
Ниже приведены API и инструменты JMockit:
Вы можете видеть, что Expectations, StrictExpectations и NonStrictExpectations, обычно используемые JMockit, ожидают записи и аннотирования @Tested, @Mocked, @NonStrict, @Injectable и других кратких стилей кода Mock. И JMockit также поставляется с инструментом Code Coverage для покрытия логики или покрытия кода в локальном модульном тестировании.
Использование JMockit:
Возьмем в качестве примера код «первого модульного теста»:
@Tested: JMockit автоматически создаст объект класса с аннотацией «@Tested» и будет использовать его в качестве тестируемого объекта. Установив параметр availableDuringSetup=true, тестируемый объект может быть создан до выполнения метода setUp.
@Tested(availableDuringSetup = true)
private BookService bookService;
@Injectable: JMockit автоматически создает объекты класса, аннотированные «@Injectable», и автоматически внедряет их в тестируемый объект.
@Injectable
private BookDAO bookDAO;
@Injectable
private UserService userService;
Связанные комментарии: // TODO будет добавлено
- @Mocked:
@Захват:
@Каскадирование:
Ожидания. Содержимое блока используется для имитации метода и указания возвращаемого значения метода, исключения, количества вызовов и затрат времени. Методы в этом блоке должны быть выполнены, иначе модульный тест завершится ошибкой.
/**
* 测试根据用户的Nick查询用户的图书列表方法
* 其中“getUserBooksByUserNick”方法最终需要通过UserId查询DB,
* 所以在调用此方法之前需要先对UserService类的getUserIdByNick方法进行Mock。
*/
@Test
public void testGetUserBooksByUserNick() throws Exception {
new Expectations() {
{
userService.getUserIdByNick(anyString);
result = 1234567;
times = 1;
}
};
List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc");
Assert.assertNotNull(bookList);
}
Связанные классы:
- StrictExpectations: методы Mock, объявленные в блоке, должны выполняться по порядку.
NonStrictExpectations: метод Mock, объявленный в блоке, не может быть выполнен.
MockUP: его можно добавить в блок Expectations для реализации методов определенных типов, таких как статические классы и интерфейсы Mock. Его также можно использовать независимо от блока ожиданий и нельзя использовать вне блока ожиданий (то есть его нельзя использовать на том же уровне или в то же время, что и блок ожиданий).
Утверждение: это наиболее распространенная проверка утверждений.
Assert.assertNotNull(bookList);
Проверки: особый вид блока проверки. Например: чтобы проверить, является ли метод, вызываемый в тестируемом классе, указанным параметром и количеством вызовов. По сравнению с Expectations, он находится в конце модульного теста и не имеет функции Mock.
Примечание. Обратитесь к разделу 7 за конкретными примерами использования комментариев, перечисленных выше.
4 Контент модульного тестирования
Во время модульного тестирования тестировщик понимает интерфейс и логическую структуру модуля в соответствии с дизайн-документом и исходным кодом. В основном используйте тестовые случаи белого ящика, дополненные тестовыми примерами черного ящика, чтобы он мог идентифицировать и реагировать на любые (разумные и необоснованные) входные данные. Это требует проверки всех локальных и глобальных структур данных программы, внешних интерфейсов и критических частей программного кода.
В модульном тесте тестируемый модуль в основном проверяется по пяти аспектам.
4.1 Тест интерфейса модуля
В начале модульного тестирования необходимо протестировать все интерфейсы тестируемого модуля. Если данные не поступают и не выходят должным образом, другие тесты бессмысленны. Майерс предлагает контрольный список для тестирования интерфейса в своей книге по тестированию программного обеспечения:
- Является ли количество входных параметров блока таким же, как количество формальных параметров блока
- Соответствуют ли атрибуты параметров каждого входа модуля соответствующим атрибутам формальных параметров
- Соответствует ли тип параметра каждого входа модуля соответствующему формальному типу параметра
- Совпадает ли количество фактических параметров, передаваемых вызываемому модулю, с количеством формальных параметров вызываемого модуля.
- Совпадают ли атрибуты фактических параметров, передаваемых вызываемому модулю, с атрибутами формальных параметров вызываемого модуля.
- Является ли тип фактического параметра, передаваемого вызываемому модулю, таким же, как тип формального параметра вызываемого модуля.
- Правилен ли порядок и количество аргументов при ссылке на внутреннюю функцию?
- ссылаются ли на параметр, не связанный с текущей записью
- Изменились ли переменные, используемые для ввода?
- Согласованы ли определения глобальных переменных при прохождении через разные модули
- Передаются ли ограничения в качестве параметров
- При использовании внешних ресурсов следует ли проверять доступность и вовремя освобождать ресурсы, такие как память, файлы, жесткие диски, порты и т. д.
Когда модуль выполняет операции ввода/вывода через внешние устройства, тест интерфейса должен быть расширен, а также должны быть добавлены следующие элементы теста:
- Верны ли свойства файла?
- Верны ли операторы Open и Close?
- Соответствует ли указанный формат оператору ввода-вывода
- Соответствует ли размер буфера размеру записи
- Открыт ли файл перед его использованием
- Будет ли выполнено условие конца файла
- Ошибки ввода-вывода проверяются и обрабатываются?
- Есть ли опечатки в выводе
4.2 Тестирование локальной структуры данных
Локальные структуры данных модуля являются наиболее распространенным источником ошибок, и тестовые случаи должны быть разработаны для проверки следующих типов ошибок:
- Неверная или противоречивая спецификация типа данных
- Использование переменной, которая еще не была назначена или инициализирована
- неправильное начальное значение или неправильное значение по умолчанию
- Имя переменной с ошибкой или ошибкой — используемая внешняя переменная или функция
- несовместимые типы данных
- Влияние глобальных данных на модули
- массив выходит за пределы
- неправильный указатель
4.3 Тест пути
Проверьте программные ошибки из-за ошибок вычислений, принятия решений и управления потоком. Поскольку невозможно выполнить исчерпывающее тестирование во время тестирования, во время модульного тестирования тестовые случаи должны быть разработаны в соответствии с методами проектирования тестирования «белого ящика» и «черного ящика», а также должны быть протестированы важные пути выполнения в модуле. Важные пути выполнения обычно относятся к тем путям, которые расположены в важных позициях, таких как конкретные алгоритмы, управление и обработка данных, а также могут относиться к более сложным и подверженным ошибкам путям. Важно максимально протестировать путь выполнения, и необходимо разработать тестовые случаи, которые приводят к ошибкам из-за неправильных вычислений, сравнений или потока управления. Кроме того, тестирование основных путей выполнения и циклов также может выявить большое количество ошибок пути.
При тестировании пути проверяются следующие ошибки: мертвый код, неправильный приоритет вычислений, ошибка алгоритма, смешивание операций разных классов, неправильная инициализация, ошибка точности - ошибка операции сравнения, ошибка присваивания, неверный знак выражения ——>, >=; = , ==, ! Неправильное использование = и переменных цикла - неправильное назначение и другие ошибки и т. д.
Операции сравнения тесно связаны с потоком управления, и при разработке тестовых случаев необходимо уделять внимание обнаружению ошибок в операциях сравнения:
- Сравнение различных типов данных (обратите внимание на сравнение классов-оболочек с базовыми типами)
- Неправильный логический оператор или приоритет
- Сравнения двух значений не равны из-за проблем с точностью арифметических операций с плавающей запятой.
- Неверные переменные и компараторы в реляционных выражениях
- «Ошибка 1», то есть ненормальное или несуществующее состояние в цикле.
- Невозможно выйти из цикла при обнаружении расходящихся циклов
- Невозможно завершить цикл при обнаружении расходящихся итераций
- Неправильное изменение переменных цикла
4.4 Тест обработки ошибок
Пути обработки ошибок — это пути, по которым могут возникать ошибки, и пути, по которым выполняется обработка ошибок. Код обработки ошибок выполняется, когда возникает ошибка, или пользователь уведомляется о процессе, или выполнение останавливается и программа переходит в безопасное состояние ожидания. Тестировщики должны знать, что каждая строка программного кода может быть выполнена, и они не должны считать, что вероятность возникновения ошибки мала и не тестируется. Общее тестирование обработки ошибок программного обеспечения должно учитывать следующие возможные ошибки:
- Непонятно ли описание ошибки, и можно ли локализовать ошибку
- Соответствует ли отображаемая ошибка фактической ошибке
- Правильная обработка ошибочных состояний
- Привело ли состояние ошибки к вмешательству системы до того, как ошибка будет обработана, и т. д.
При тестировании обработки ошибок проверьте следующее:
- Проверяет ли программа наличие ошибок до и после использования ресурсов или других модулей
- После возникновения ошибки можно ли ее обработать, например, выдать ошибку, уведомить пользователя, зарегистрировать
- Эффективна ли обработка ошибок, правдивы ли и подробно описаны ошибки, о которых сообщается и регистрируется, до вмешательства системы.
4.5 Граничное тестирование
Граничное тестирование — это последняя задача модульного тестирования. Код часто ошибается на границе, например: в сегменте кода есть n-й цикл, при достижении n-го цикла ошибка может быть ошибочной; или в массиве из n элементов очень сложно доступ к n-му элементу подвержен ошибкам. Поэтому особое внимание следует уделять ошибкам, которые могут возникнуть в потоке данных и потоке управления, когда он точно равен, больше или меньше определенного значения сравнения. Эти места нужно тщательно и тщательно протестировать.
Кроме того, если есть требования к производительности модуля, также требуется тест производительности критического пути. Чтобы определить наихудшие и средние факторы, влияющие на время выполнения. Вот особенности, которые необходимо проверить для граничного тестирования:
- Правильно ли обрабатываются обычные юридические данные
- Правильно ли обрабатываются обычные незаконные данные
- Правильно ли обрабатываются (законные) данные в пределах границы, ближайшей к границе.
- Правильно ли обрабатываются (незаконные) данные, ближайшие к границе за границей, и т. д.
- Есть ли ошибка в 0-й, 1-й и n-й раз из n циклов?
- Есть ли ошибка при взятии максимального и минимального значений в операции или суждении
- Есть ли ошибка, когда точно равно, больше или меньше определенного значения сравнения в потоке данных, потоке управления
5. Спецификации юнит-тестов
5.1 Соглашения об именах
- Структура каталогов: каталог модульного теста «test» в структуре каталогов Maven.
Имя пакета: имя пакета тестируемого класса.
Имя класса: имя класса для тестирования + Test
Имя метода: тест + имя тестируемого метода + 4 + содержание теста (сценарий)
5.2 Содержание теста
В части 4 описываются пять основных моментов, которые необходимо протестировать, а здесь представлен тестовый контент, который должен быть включен или охвачен, по крайней мере, на уровне кода на стороне сервера.
Service
- Тестирование локальной структуры данных
- испытание пути
- тест обработки ошибок
- Граничное тестирование
HTTP-интерфейс
- фиктивный тест интерфейса
- Тестирование локальной структуры данных
- испытание пути
- тест обработки ошибок
- Граничное тестирование
HSF-интерфейс
- фиктивный тест интерфейса
- Тестирование локальной структуры данных
- испытание пути
- тест обработки ошибок
- Граничное тестирование
Инструменты
- фиктивный тест интерфейса
- Тестирование локальной структуры данных
- испытание пути
- тест обработки ошибок
- Граничное тестирование
5.3 Покрытие
Чтобы юнит-тесты разрабатывались с достаточной детализацией, при реализации юнит-тестов должны соблюдаться следующие требования:
-
Покрытие выписки достигает 100%
Покрытие операторов означает, что каждый исполняемый оператор в тестируемом модуле покрывается тестовым набором. Покрытие операторов — это наименьшее требование к покрытию, и следует обратить внимание на значение покрытия операторов. Например, использование программы, которая никогда не выполнялась, для управления космическим челноком, чтобы подняться в небо, а затем вывести его на точную орбиту, последствия такого поведения невообразимы. При реальном тестировании не каждый оператор может быть выполнен. Во-первых, это «мертвый код», то есть код, который не может быть выполнен ни при каких обстоятельствах из-за ошибки проектирования кода. Во-вторых, это не «мертвый код», а код, который не выполняется, потому что требуемые входные данные и условия очень трудно достичь или реализация модульных тестов ограничена. Поэтому, когда исполняемый оператор не выполняется, необходимо углубиться в программу для детального анализа. Если он относится к двум вышеуказанным случаям, то можно считать, что покрытие завершено. Но для последнего постарайтесь протестировать его как можно больше. Если ни один из вышеперечисленных случаев не соответствует действительности, это означает, что дизайн тестового набора недостаточен, и его необходимо перепроектировать. -
Покрытие филиала достигает 100%
Покрытие ветвления означает, что оператор ветвления принимает истинное значение и ложное значение по одному разу. Операторы ветвления являются важными операторами обработки для потока управления программой.Разработайте тесты в различных направлениях потока, которые могут проверить правильность этих потоков управления. Охват ветвей позволяет проверять выходные данные этих ветвей, повышая адекватность тестирования. -
Переопределить пути обработки ошибок
То есть путь обработки исключений -
Покрытие программных функций для устройств
Характеристики программного обеспечения включают в себя функции, производительность, свойства, конструктивные ограничения, количество состояний, количество строк ветвей и т. д. -
Проверьте вычисление пробных номинальных значений данных, сингулярных значений данных и граничных значений. Запустите тесты с гипотетическими типами данных и значениями данных, отклонив неправильный ввод.
Модульное тестирование обычно выполняется тем, кто написал программу, но руководитель проекта должен быть заинтересован в результатах тестирования. Все тестовые примеры и результаты тестов являются важными данными для разработки модуля и должны быть надлежащим образом сохранены.
Прикрепил:Весь ли код нуждается в покрытии юнит-тестами?
5.4 Тестирование вариантов
Подход, основанный на тестовом покрытии, действительно может помочь нам найти некоторые очевидные избыточности кода или пропуски тестов. Однако практика показала, что эти традиционные методы могут найти только очень ограниченные проблемы в тесте. Многие проблемы с кодом и тестами невозможно найти со 100% охватом. Однако метод «тестирования мутаций кода» может компенсировать недостатки традиционных методов и производить более эффективные модульные тесты.
Тестирование мутации кода помогает нам улучшить модульное тестирование, «мутируя» код. «Мутация» относится к изменению фрагмента кода для изменения поведения кода (конечно, чтобы убедиться, что синтаксис разумен). В двух словах, тестирование изменения кода сначала пытается внести такое изменение в код, затем запускает модульные тесты и проверяет, не терпят ли какие-либо тесты неудачу из-за этой изменения кода. Если это не удается, то мутация «убивается», что мы и ожидаем увидеть. В противном случае это означает, что мутация «выжила», в этом случае нужно изучить «почему».
В целом, тестовое покрытие — это хороший способ обеспечить качество модульных тестов. Тестирование изменения кода может выявлять потенциальные проблемы в коде и тестах более эффективно, чем традиционные методы покрытия тестами, и может сделать модульные тесты более надежными.
Прикрепил:Для чего именно полезно тестовое покрытие (скорость)?
6 Интеграция с ЦИСЭ
пропускать
7 примеров модульных тестов
7.1 Service
Service
Пример модульного теста слоя.
Обычный пробный тест:
/**
* 测试根据用户的Nick查询用户的图书列表方法
* 其中“userService.getUserBooksByUserNick”方法最终需要通过UserId查询DB,
* 所以在调用此方法之前需要先对UserService类的getUserIdByNick方法进行Mock。
* 其中“bookDAO.getUserBooksByUserId”方法最终需要通过UserId查询DB,
* 所以在调用此方法之前需要先对BookDAO类的getUserBooksByUserId方法进行Mock。
*/
@Test
public void testGetUserBooksByUserNick4Success() throws Exception {
final List<BookDO> bookList = new ArrayList<BookDO>();
bookList.add(new BookDO());
new Expectations() {
{
userService.getUserIdByNick(anyString); // Mock的接口
result = 1234567; // 接口返回值
times = 1; // 接口被调用的次数
bookDAO.getUserBooksByUserId(anyLong);
result = bookList;
times = 1;
}
};
List<BookDO> resultBookList = bookService.getUserBooksByUserNick("moyuan.jcc");
Assert.assertNotNull(resultBookList);
}
2. Обработка ошибок (исключений):
/**
* 测试根据用户的Nick查询用户的图书列表方法,注意在@Test添加expectedExceptions参数
* 验证其中“userService.getUserBooksByUserNick”接口出现异常时,对异常的处理是否符合预期.
* 其中“bookDAO.getUserBooksByUserId”方法不会被调用到。
*/
@Test(expectedExceptions = {RuntimeException.class})
public void testGetUserBooksByUserNick4Exception() throws Exception {
final List<BookDO> bookList = new ArrayList<BookDO>();
bookList.add(new BookDO());
new Expectations() {
{
userService.getUserIdByNick(anyString); // Mock的接口
result = new RuntimeException("exception unit test"); // 接口抛出异常
times = 1; // 接口被调用的次数
bookDAO.getUserBooksByUserId(anyLong);
result = bookList;
times = 0; // 上面接口出现异常后,此接口不会被调用
}
};
List<BookDO> resultBookList = bookService.getUserBooksByUserNick("moyuan.jcc");
Assert.assertNotNull(resultBookList);
}
3. Моделирование реализации конкретного метода:
/**
* 测试发送离线消息方法
* 消息队列:当离线消息超过100条时,删除最旧1条,添加最新一条。
* 但消息存在DB或Tair中,所以需要Mock消息的存储。
*/
@Test
public void testAddOffLineMsg() throws Exception {
final Map<Long, MsgDO> msgCache = new ArrayList<Long, MsgDO>();
new Expectations() {
{
new MockUp<BookDAO>() {
@Mock
public void addMsgByUserId(long userId, MsgDO msgDO) {
msgCache.put(userId, msgDO);
}
};
new MockUp<BookDAO>() {
@Mock
public List<MsgDO> getUserBooksByUserId(long userId) {
return msgCache.get(userId);
}
};
}
};
final int testAddMsgCount = 102;
for(int i = 0; i < testAddMsgCount; i++) {
msgService.addMsgByUserId(123L, new MsgDO(new Date(), "this is msg" + i));
}
List<MsgDO> msgList = msgService.getMsgByUserId(123L);
Assert.assertTrue(msgList.size() == 100);
new Verifications() {
{
// 验证 addMsgByUserId 接口是否被调用了100次
MsgDAO.addMsgByUserId(anyLong, withInstanceOf(MsgDO.class));
times = testAddMsgCount;
// 验证是否对消息内容进行相就次数的转义
SecurityUtil.escapeHtml(anyString);
times = testAddMsgCount;
}
};
}
7.2 HTTP
HTTP
Пример тестирования модуля интерфейса.
1. Spring MVC Controller
public final class BookControllerTest {
@Tested(availableDuringSetup = true)
private BookController bookController;
@Injectable
private BookService bookService;
private MockMvc mockMvc;
@BeforeMethod
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
}
/**
*<strong> </strong>********************************
* getBookList unit test
*<strong> </strong>********************************
*/
@Test
public void testgetBookList4Success() throws Exception {
new StrictExpectations() {
{
new MockUp<CookieUtil>(){
@Mock
public boolean isLogined(){
return true;
}
};
userService.getUserBooksByUserNick(anyString);
result = null;
times = 1;
}
};
ResultActions httpResult = this.mockMvc.perform(get("/education/getBookList.do?userNick=hello"))
.andDo(print()).andExpect(status().isOk());
MockHttpServletResponse response = httpResult.andReturn().getResponse();
String responseStr = response.getContentAsString();
// 如果存在多版本客户端的情况下,注意返回值向后兼容,此处需要多种格式验证.
Assert.assertEquals(responseStr, "{\"code\":1,\"msg\":\"success\",\"data\":\"\"}");
}
}
2. Параметризованный тест
@DataProvider(name = "getBookListParameterProvider")
public Object[][] getBookListParameterProvider() {
return new String[][]{
{"hello", "{\"code\":1,\"msg\":\"success\",\"data\":\"\"}"},
{"123", "{\"code\":301,\"msg\":\"parameter error\",\"data\":\"\"}"}
};
}
@Test(dataProvider = "getBookListParameterProvider")
public void testgetBookList4Success(String nick ,String resultCheck) throws Exception {
new StrictExpectations() {
{
new MockUp<CookieUtil>() {
@Mock
public boolean isLogined() {
return true;
}
};
userService.getUserBooksByUserNick(anyString);
result = null;
times = 1;
}
};
ResultActions httpResult = this.mockMvc.perform(get("/education/getBookList.do?userNick=" + nick))
.andDo(print()).andExpect(status().isOk());
MockHttpServletResponse response = httpResult.andReturn().getResponse();
String responseStr = response.getContentAsString();
// 如果存在多版本客户端的情况下,注意返回值向后兼容,此处需要多种格式验证.
Assert.assertEquals(responseStr, resultCheck);
}
7.3 Инструменты
Пример теста статического служебного класса.
1. Статический метод:
java @Test public void testMethod() { new StrictExpectations(CookieUtil) { { CookieUtil.isLogined(); result =
или
java @Test public void testMethod() { new MockUp<CookieUtil>(){ @Mock public boolean isLogined(){ return true;
Приложение 1: Руководство по модульному тестированию
1. Старайтесь, чтобы модульные тесты были небольшими и быстрыми
Теоретически все наборы тестов должны быть полностью пройдены перед отправкой любого кода. Сохранение выполнения тестового кода в соответствии с ожиданиями может сократить итеративные циклы разработки.
2. Модульные тесты должны быть полностью автоматическими/неинтерактивными.
Наборы тестов обычно выполняются на регулярной основе, и выполнение должно быть полностью автоматизировано, чтобы иметь смысл. Тест, вывод которого требует ручной проверки, не является хорошим модульным тестом.
3. Упростите запуск модульных тестов
Настройте среду разработки, предпочтительно с помощью одной команды или нажатия кнопки, чтобы запустить один тестовый пример или набор тестов.
4. Оцените тест
Выполните анализ покрытия выполненных тестов, чтобы получить точное покрытие выполнения кода и выяснить, какой код не был выполнен.
5. Немедленно исправьте неудачные тесты
Каждый разработчик должен убедиться, что новые тестовые случаи успешно выполняются перед отправкой, а когда код отправляется, существующие тестовые случаи также могут выполняться.
Если регулярно выполняемый тестовый пример дает сбой, вся команда должна прекратить работу, чтобы исправить проблему.
6. Держите тесты на уровне модулей
Модульное тестирование — это тестирование класса. «Тестируемый класс» должен соответствовать только одному «тестируемому классу», а поведение «тестируемого класса» должно тестироваться изолированно. Необходимо соблюдать осторожность, чтобы избежать использования среды модульного тестирования для проверки рабочего процесса всей программы, что неэффективно и сложно в обслуживании. Тестирование рабочего процесса имеет свою собственную сферу деятельности, но оно ни в коем случае не является модульным тестом и должно создаваться и выполняться отдельно.
7. От простого к сложному
Простое тестирование намного лучше, чем отсутствие тестирования вообще. Простой «тестовый класс» подскажет создание базового тестового скелета «тестируемого класса», который может проверить достоверность среды сборки, среды модульного тестирования, среды выполнения и инструментов анализа покрытия, а также может подтвердить «Тестируемый класс». «Можно интегрировать и вызывать.
Вот тестовая версия «Hello, world!»:
void testDefaultConstruction()
{
Foo foo = new Foo();
assertNotNull(foo);
}
8. Делайте тесты независимыми
Чтобы тест был стабильным, надежным и простым в обслуживании, тестовые примеры не должны зависеть ни друг от друга, ни от порядка выполнения. (На самом деле, TestNG имеет функцию предоставления зависимостей, и, возможно, некоторые сценарии также нуждаются в зависимостях.)
9.Keep tests close to the class being tested
[Аннотация: я намерен перевести это правило. Я лично считаю это правило спорным. Большинство библиотек C++, Objective-C и Python отделяют тестовый код от каталога кода функции. Обычно каталог test находится на том же уровне, что и каталог src Тестируемый модуль и имя класса часто не имеют префикса Test. Это гарантирует, что функциональный код и тестовый код изолированы, структура каталогов понятна, и при распространении исходного кода легче исключить тестовые случаи. ]
If the class to test is Foo the test
class should be called FooTest (not TestFoo) and kept in the same package (directory) as Foo. Keeping test classes in separate directory trees makes them harder to access and maintain.
Убедитесь, что среда сборки настроена так, чтобы
тестовые классы не попадают в производственные библиотеки или исполняемые файлы.
10. Тестовые примеры с разумными названиями
Убедитесь, что каждый метод проверяет только одну конкретную функцию «тестируемого класса», и назовите метод тестирования соответствующим образом. Типичным соглашением об именах является «test[MethodName]», например, «testSaveAs()», «testAddListener()», «testDeleteProperty()» и т. д.
11. Тестируйте только публичные интерфейсы
Модульное тестирование можно определить как тестирование класса через его общедоступный API. Некоторые инструменты тестирования позволяют тестировать закрытые члены класса, но этого следует избегать, так как это делает тестирование утомительным и трудным в обслуживании. Если есть закрытый член, который действительно нужно протестировать напрямую, рассмотрите возможность рефакторинга его в общедоступный метод служебного класса. Но имейте в виду, что это делается для улучшения дизайна, а не для помощи в тестировании.
12. Относитесь к этому как к черному ящику
С точки зрения сторонних пользователей проверьте, соответствует ли класс указанным требованиям. и удалось убрать его с дороги.
13. Думайте об этом как о белой коробке
В конце концов, тестируемый класс написан программистом и должен быть протестирован с большим усилием в наиболее сложной логической части.
14. Функции кунжута также проверяются
Обычно рекомендуется тестировать все важные функции, а некоторые методы-сезамы, такие как простые сеттеры и геттеры, можно игнорировать. Но все же есть веские причины для тестирования кунжутных функций:
«Сезам» трудно определить, и разные люди понимают его по-разному.
С точки зрения тестирования черного ящика невозможно узнать, какой код является сезамом.
Даже повторно используемые функции могут содержать ошибки, как правило, результат «копирования-вставки» кода:
private double weight_;
private double x_, y_;
public void setWeight(int weight)
{
weight = weight_; // error
}
public double getX()
{
return x_;
}
public double getY()
{
return x_; // error
}
Поэтому рекомендуется тестировать все методы, ведь варианты использования кунжута тоже легко тестировать.
15. Сначала сосредоточьтесь на покрытии выполнения
Относитесь к «покрытию выполнения» и «фактическому покрытию тестами» по-разному. Первоначальная цель тестирования должна заключаться в обеспечении высокого покрытия выполнения, что гарантирует успешное выполнение кода при небольшом количестве введенных значений параметров. Как только покрытие выполнения будет готово, пора приступить к улучшению покрытия тестами. Обратите внимание, что фактическое покрытие тестами трудно измерить (и оно стремится к 0%).
Рассмотрим следующие общедоступные методы:
void setLength(double length);
Вызовом setLength(1.0) вы можете получить 100% покрытие выполнения. Но для достижения 100% фактического покрытия тестами метод должен вызываться столько раз, сколько существует двойных чисел с плавающей запятой, и правильность поведения должна проверяться одно за другим. Это, несомненно, невыполнимая задача.
16. Переопределить граничные значения
Убедитесь, что все граничные значения параметров переопределены. Для чисел проверьте отрицательное значение, 0, положительное значение, минимум, максимум, NaN (не число), бесконечность и т. д. Для строк проверьте пустые строки, односимвольные, не-ASCII, многобайтовые строки и т. д. Для типов коллекций , проверка на пустое, 1, первое, последнее и т. д., на даты проверка на 1 января, 29 февраля, 31 декабря и т. д. Сам тестируемый класс также подразумевает некоторые граничные значения в определенных случаях. Смысл в том, чтобы как можно тщательнее проверить эти граничные значения, поскольку все они являются главными подозреваемыми.
17. Предоставьте генератор случайных значений
Когда граничные значения покрыты, еще одним простым способом дальнейшего улучшения тестового покрытия является генерация случайных параметров, чтобы каждое выполнение теста имело разные входные данные.
Для этого вам нужно предоставить служебный класс, который генерирует случайные значения примитивных типов (например, числа с плавающей запятой, целые числа, строки, даты и т. д.). Генераторы должны охватывать все диапазоны значений для различных типов.
Если время тестирования относительно короткое, подумайте о том, чтобы обернуть еще один слой циклов, чтобы охватить как можно больше входных комбинаций. В следующем примере проверяется, возвращается ли исходное значение после двойного преобразования порядка байтов "little endian" и "big endian". Поскольку процесс тестирования быстрый, его можно запускать миллион раз.
void testByteSwapper()
{
for (int i = 0; i < 1000000; i++) {
double v0 = Random.getDouble();
double v1 = ByteSwapper.swap(v0);
double v2 = ByteSwapper.swap(v1);
assertEquals(v0, v2);
}
}
18. Тестируйте каждую функцию только один раз
В тестовом режиме иногда нельзя не злоупотреблять утверждениями. Эта практика усложняет техническое обслуживание, и ее следует избегать. Явно протестируйте только функции, указанные в имени метода тестирования.
Потому что для общего кода важной целью является сохранение как можно меньшего количества тестового кода.
19. Используйте явные утверждения
«assertEquals(a, b)» всегда следует использовать вместо «assertTrue(a == b)», потому что первый дает более значимую информацию об ошибке теста. Это правило особенно важно в ситуациях, когда входные значения заранее неизвестны, как, например, в предыдущем примере с использованием случайных комбинаций значений параметров.
20. Обеспечьте обратное тестирование
Тестирование на исторических данных относится к преднамеренному написанию проблемного кода для проверки надежности и правильной обработки ошибок.
Предположим, что если параметру следующего метода будет передано отрицательное число, немедленно будет выброшено исключение:
void setLength(double length) throws IllegalArgumentException
Следующий метод можно использовать для проверки правильности обработки этого особого случая:
try {
setLength(-1.0);
fail(); // If we get here, something went wrong
}
catch (IllegalArgumentException exception) {
// If we get here, all is fine
}
21. Имейте это в виду при разработке кода
Написание и поддержка модульных тестов обходятся дорого, а сокращение общедоступных интерфейсов и циклической сложности кода — эффективный способ снизить затраты и упростить написание и поддержку тестового кода с высоким покрытием.
несколько советов:
22. Сделайте члены класса постоянными и инициализируйте их в конструкторе. Уменьшите количество методов установки.
23. Ограничьте чрезмерное использование наследования и общедоступных виртуальных функций.
24. Сократите количество общедоступных интерфейсов, используя дружественные классы (C++) или область действия пакета (Java).
25. Избегайте ненужных ответвлений логики.
26. Пишите как можно меньше кода в логических ветвях.
27. Используйте исключения и утверждения для максимально возможной проверки правильности параметров в общедоступных и частных интерфейсах.
28. Ограничьте использование функций быстрого доступа. Для черного ящика все методы должны быть протестированы одинаково. Рассмотрим следующий короткий пример:
public void scale(double x0, double y0, double scaleFactor)
{
// scaling logic
}
public void scale(double x0, double y0)
{
scale(x0, y0, 1.0);
}
29. Удаление последнего упрощает тестирование, но также несколько увеличивает нагрузку на пользовательский код.
30. Не обращайтесь к предустановленным внешним ресурсам
Код модульного теста не должен предполагать внешнюю среду выполнения, чтобы его можно было выполнять в любое время и в любом месте. Чтобы обеспечить необходимые ресурсы для теста, эти ресурсы должны быть предоставлены самим тестом.
Например, класс, анализирующий файл определенного типа, может внедрить содержимое файла в тестовый код. Записывайте во временный файл во время теста и удаляйте его после теста вместо того, чтобы читать напрямую с заданного адреса.
31. Взвесьте стоимость тестирования
Не писать модульные тесты дорого, но писать модульные тесты так же дорого. Чтобы найти подходящий компромисс между этими двумя показателями, если измерять охват выполнения, отраслевой стандарт обычно составляет около 80%.
Как правило, трудно достичь 100-процентного охвата выполнения для обработки ошибок и обработки исключений для чтения и записи внешних ресурсов. Невозможно смоделировать сбой базы данных в середине транзакции, но это может быть непомерно дорого по сравнению с обширной проверкой кода.
32. Сделайте тестирование приоритетным
Модульное тестирование — это типичный восходящий процесс, и если у вас недостаточно ресурсов для тестирования всех модулей системы, вам следует сначала сосредоточиться на модулях более низкого уровня.
33. Тестируйте свой код с учетом обработки ошибок
Рассмотрим следующий пример:
Handle handle = manager.getHandle();
assertNotNull(handle);
String handleName = handle.getName();
assertEquals(handleName, "handle-01");
Если первое утверждение не выполняется, последующие операторы вызывают сбой кода, а остальные тесты не выполняются. Всегда будьте готовы к сбоям в тестах, чтобы один не пройденный элемент теста не прерывал выполнение всего набора тестов. Приведенный выше пример можно переписать как:
Handle handle = manager.getHandle();
assertNotNull(handle);
if (handle == null) return;
String handleName = handle.getName();
assertEquals(handleName, "handle-01");
34. Напишите тестовые примеры для воспроизведения ошибок
Каждый раз, когда сообщается об ошибке, должен быть написан тестовый пример, чтобы воспроизвести ошибку (то есть не пройти тест) и использовать его в качестве стандарта проверки для успешного исправления кода.
35. Знайте свои ограничения
Модульные тесты никогда не смогут доказать правильность вашего кода! !
Неудачный тест может указывать на ошибку в коде, но успешный тест ничего не доказывает.
Наиболее эффективными вариантами использования модульного тестирования являются проверка и документирование требований на более низком уровне, а также регрессионное тестирование: разработка или рефакторинг кода без нарушения корректности существующей функциональности.
Приложение 2: Рекомендации по книгам
- Путь тестирования программного обеспечения Google
«Искусство модульного тестирования»
«Разработка через тестирование»
«Рефакторинг для улучшения дизайна существующего кода»
Приложение 3: Ссылки
- модульный тест:
Определение, содержание, этапы модульного тестирования
Важность и основное значение модульного тестирования
Подробно объясните содержание модульного тестирования
Серия руководств по TestNG: параллельное выполнение тестов
Китайский перевод руководств по модульному тестированию
руководство по модульному тестированию
- Тестовое покрытие:
Весь ли код нуждается в покрытии юнит-тестами?
Для чего именно полезно тестовое покрытие (скорость)?
- Разработка через тестирование:
- JMockit:
Использование макета в модульном тестировании и практика использования макета артефакта jmockit
Публичный идентификатор: longjiazuoA