UnitTest и PowerMock
При изучении компьютерного языка, я думаю, помимо изучения его синтаксиса, самое главное научиться выполнять модульное тестирование в этой языковой среде, потому что модульное тестирование может помочь вам найти ошибки на ранней стадии; в то же время добавить защитный net к вашей программе, чтобы ваша модификация не разрушила исходную функцию; модульное тестирование также может помочь вам написать лучший код, в конце концов, код, который нельзя протестировать, не должен быть хорошим кодом; кроме того, это также может повысить вашу уверенность , смело могу сказать "в моей программе нет багов".
Каждый язык имеет свою общую структуру модульного тестирования.В этой статье в основном рассказывается, как мы используем PowerMock в Java для решения проблем, с которыми мы сталкиваемся при написании модульных тестов.Из слова Mock мы видим, что такие проблемы в основном решают проблему зависимости.
При написании юнит-тестов, чтобы упростить работу теста и уменьшить внешнюю неопределенность, мы обычно изолируем тестируемый класс от других зависимых классов, иначе чем больше у вас зависимых классов, тем больше вам нужно подготовиться. , особенно когда это зависит от сети или внешней базы данных, привнесет в тест большую неопределенность иНаши одиночные тесты должны соответствовать требованиям быстрого и воспроизводимого выполнения., поэтому изоляция или независимость является важным шагом.
Библиотека PowerMock на Java — это очень мощная библиотека зависимостей.Три функции, упомянутые ниже, могут помочь вам решить большинство проблем:
- Внедрение зависимых объектов через PowerMock
- Моделирование статических функций с помощью PowerMock
- Как имитировать выходные параметры
Внедрение зависимых объектов через PowerMock
Предположим, у вас есть два класса,MyService
а такжеMyDao
,MyService
зависит отMyDao
, и они определяются следующим образом
// MyDao.java
@Mapper
public interface MyDao {
/**
* 根据用户 id 查看他最近一次操作的时间
*/
Date getLastOperationTime(long userId);
}
// MyService.java
@Service
public class MyService {
@Autowired
private MyDao myDao;
public boolean operate(long userId, String operation) {
Date lastTime = myDao.getLastOperationTime(userId);
// ...
}
}
Эта услуга предоставляетoperate
интерфейс, пользователь будет ограничен одной рабочей частотой при вызове этого интерфейса, поэтому система будет записывать время последней операции каждого пользователя черезMyDao.getLastOperationTime(long userId)
Приобретение интерфейса, теперь мы должныMyService
Категорияoperate
Как проводить модульное тестирование?
Вы можете подумать об использовании SpringBoot, который может автоматически инициализироваться для нас.myDao
объект, но есть некоторые проблемы с этим:
- SpringBoot запускается медленно, что может увеличить время модульного тестирования.
- Поскольку время — это постоянно меняющаяся величина, может быть, время, которое вы сконструировали, на этот раз удовлетворяет условию теста, но в следующий раз, когда вы запустите тест, оно может не соответствовать условию.
По вышеуказанным причинам мы обычно не запускаем контекст SpringBoot при модульном тестировании, а используем PowerMock, чтобы помочь нам внедрить зависимости.Для приведенного выше случая наш тестовый пример можно записать следующим образом:
// MyServiceTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest({MyService.class, MyDao.class})
public class MyServiceTest {
@Test
public void testOperate() throws IllegalAccessException {
// 构造一个和当前调用时间永远只差 4 秒的返回值
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, -4);
Date retTime = calendar.getTime();
// spy 是对象的“部分 mock”
MyService myService = PowerMockito.spy(new MyService());
MyDao md = PowerMockito.mock(MyDao.class);
PowerMockito
.when(md.getLastOperationTime(Mockito.any(long.class)))
.thenReturn(retTime);
// 替换 myDao 成员
MemberModifier.field(MyService.class, "myDao").set(myService, md);
// 假设最小操作的间隔是 5 秒,否则返回 false
Assert.assertFalse(myService.operate(1, "test operation"));
}
}
Из приведенного выше кода мы сначала строим время возвратаretTime
, имитируемый интервал работы составляет 4 секунды, что гарантирует, что условие не изменится при каждом запуске теста, тогда мы используемspy
построить тестMyService
объект,spy
а такжеmock
Разница в том,spy
Только частично смоделированные объекты, то есть только модифицированные здесьmyService.myDao
членов, остальные остаются прежними.
Затем мы определяем издевательский объектMyDao md
вызывающее поведение, когдаmd.getLastOperationTime
Когда функция вызывается, верните время, которое мы построилиretTime
, тестовая среда настроена на этом этапе, после этого вы можете легко протестироватьoperate
функция.
Моделирование статических функций с помощью PowerMock
Упомянутое выше использование PowerMock для внедрения зависимостей может охватывать большинство сценариев зависимости в тесте, и еще одна распространенная зависимость — это статические функции, такие как некоторые из наших собственных.CommonUtil
функции в служебном классе.
Продолжая использовать приведенный выше пример, предположим, что мы хотим вычислить интервал между текущим временем и временем последнего действия пользователя и использоватьpublic static long getTimeInterval(Date lastTime)
Реализуйте эту функцию следующим образом:
// CommonUtil.java
class CommonUtil {
public static long getTimeInterval(Date lastTime) {
long duration = Duration.between(lastTime.toInstant(),
new Date().toInstant()).getSeconds();
return duration;
}
}
нашoperator
Функция изменена следующим образом
// MyService.java
// ...
public boolean operate(long userId, String operation) {
Date lastTime = myDao.getLastOperationTime(userId);
long duration = CommonUtil.getTimeInterval(lastTime);
if (duration >= 5) {
System.out.println("user: " + userId + " " + operation);
return true;
} else {
return false;
}
}
// ...
Здесь изmyDao
Получите время последней операции, затем вызовитеCommonUtil.getTimeInterval
Вычислить интервал операции и вернуться, если он меньше 5 секундfalse
, иначе выполнить операцию и вернутьсяtrue
. Итак, мой вопрос: как здесь избавиться от зависимости статической функции? Давайте посмотрим непосредственно на тестовый код
// MyServiceTest.java
@PrepareForTest({MyService.class, MyDao.class, CommonUtil.class})
public class MyServiceTest {
// ...
@Test
public void testOperateWithStatic() throws IllegalAccessException {
// ...
PowerMockito.spy(CommonUtil.class);
PowerMockito.doReturn(5L).when(CommonUtil.class);
CommonUtil.getTimeInterval(Mockito.anyObject());
// ...
}
}
Сначала в аннотации@PrepareForTest
увеличить вCommonUtil.class
, все еще используюspy
парный классCommonUtil
Сделайте макет, если вы этого не сделаете, поведение всех статических функций в этом классе изменится, что вызовет проблемы для ваших тестов.spy
Следующие две строки кода следует читать вместе, это означает, что при вызовеCommonUtil.getTimeInterval
, возвращает 5; это странный способ записи, но он требуется для PowerMock. К этому моменту вы овладели искусством имитации статических функций.
Как имитировать выходные параметры
Некоторые функции будут выводиться путем изменения объекта, на который ссылается параметр.Например, в следующем сценарии, предполагая, что наша операция является длительной задачей, нам необходимо постоянно обучать статус задачи, обновлять ее в памяти и предоставлять интерфейсы внешних запросов.Следующий код:
// MyTask.java
// ...
public boolean run() throws InterruptedException {
while (true) {
updateStatus(operation);
if (operation.getStatus().equals("success")) {
return true;
} else {
Thread.sleep(1000);
}
}
}
public void updateStatus(Operation operation) {
String status = myDao.getStatus(operation.getOperationId());
operation.setStatus(status);
}
// ...
В приведенном выше кодеrun()
— это задача опроса, которая постоянно обновляет статус операции, и когда статус достигает"success"
остановись, когда увидишь,updateStatus
Это то, что мы называем функцией.Хотя у нее нет возвращаемого значения, она изменяет объект, на который ссылается параметр, поэтому такой тип параметра также называется выходным параметром.
Сейчас мы будем тестироватьrun()
поведение функции, чтобы увидеть, будет ли она"success"
Выход в состоянии, тогда нам нужен mockupdateStatus
функцию, что делать? Вот его тестовый код:
@Test
public void testUpdateStatus() throws InterruptedException {
// 初始化被测对象
MyTask myTask = PowerMockito.spy(new MyTask());
myTask.setOperation(new MyTask.Operation());
// 使用 doAnswer 来 mock updateStatus 函数的行为
PowerMockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
MyTask.Operation operation = (MyTask.Operation)args[0];
operation.setStatus("success");
return null;
}
}).when(myTask).updateStatus(Mockito.any(MyTask.Operation.class));
Assert.assertEquals(true, myTask.run());
}
В приведенном выше коде мы используемdoAnswer
издеватьсяupdateStatus
поведение, эквивалентное использованиюanswer
функция замены оригиналаupdateStatus
функция, здесь мы будемoperation
Статус установлен на"success"
, ждатьmyTask.run()
возврат функцииtrue
. Итак, мы снова научились мокать функции с выходными параметрами.
Приведенный выше код предназначен только для иллюстрации сценария приложения, а не кода уровня производственной среды, и все они прошли тест, для удобства последующего обучения вы можете скачать его здесь:GitHub.com/Джи Ниу/Арити…
Ссылаться на: