В последнее время компания продвигает модульное тестирование Java-приложений и требует, чтобы охват модульного тестирования был увеличен до более чем 50%, чтобы обеспечить полное самотестирование онлайн-кода. Выбранная структура модульного тестирования компанииJunit 4.12
, платформа Mock выбираетMockito
иPowerMock
, при выбореJaCoCo
Чтобы выполнить обнаружение покрытия, ниже подробно описан мой опыт использования этих фреймворков.
импорт зависимостей
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.8.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
Использование PowerMockito
Mockito, EasyMock, JMock и другие популярные Mock-фреймворки имеют общий недостаток, они не могут имитировать статические, финальные, приватные методы и т. д., а PowerMock прекрасно решает недостатки вышеперечисленных фреймворков., давайте посмотрим, как всемогущий PowerMock решает вышеуказанные проблемы. Начнем с примера. Следующий код представляет собой класс, который нуждается в единственном тесте. Тестируемый класс не только вызывает Spring Bean, но также содержит статические чтения в Redis, плюсgetInstance()
Использование одноэлементных классов, теперь давайте один за другим издеваться над вышеперечисленными внешними классами.
Один тестовый класс (в основном тестирует метод queryStudentScoreByKeyword)
@Component
public class StudentService {
@Resource
private StudentDao studentDao;
public List<StudentBo> queryStudentScoreByKeyword(String name) {
System.out.println("invoke StudentService.queryStudentScoreByKeyword ...");
List<StudentBo> cacheList = RedisUtils.getArray(name, StudentBo.class);
if (CollectionUtils.isNotEmpty(cacheList)) {
return cacheList;
}
String keyword = processKeyword(name);
List<Student> students = studentDao.queryStudentByKeyWord(keyword);
List<Integer> ids = students.stream().map(Student::getId).collect(Collectors.toList());
List<Person> personList = SchoolManageProxy.getInstance().queryPerson(ids);
List<StudentBo> studentBos = CommonUtils.toBo(personList, students);
// 高亮结果
highlightResult(studentBos, name);
// 缓存到Redis
RedisUtils.setArray(name, studentBos, 10 * 60);
return studentBos;
}
private String processKeyword(String name) {
System.out.println("invoke StudentService.processKeyword ...");
String newName = name;
// do somethings
return newName;
}
private void highlightResult(List<StudentBo> result, String name) {
System.out.println("invoke StudentService.highlightResult ...");
// do keyword highlight
}
}
Один тестовый класс
@RunWith(PowerMockRunner.class)
@PrepareForTest({SchoolManageProxy.class, RedisUtils.class, StudentService.class})
// @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})
@SuppressStaticInitializationFor({"cn.ganzhiqiang.ares.unittest.SchoolManageProxy"})
public class StudentServiceTest {
@Mock
private StudentDao mockStudentDao;
@InjectMocks
private StudentService studentServiceUnderTest;
@Before
public void setUp() {
initMocks(this);
}
@Test
public void testQueryStudentScoreByKeyword() throws Exception {
studentServiceUnderTest = PowerMockito.spy(studentServiceUnderTest);
PowerMockito.mockStatic(RedisUtils.class);
PowerMockito.mockStatic(SchoolManageProxy.class);
// mock单例调用
SchoolManageProxy mockSchoolManageProxy = PowerMockito.mock(SchoolManageProxy.class);
PowerMockito.when(SchoolManageProxy.getInstance()).thenReturn(mockSchoolManageProxy);
when(mockSchoolManageProxy.queryPerson(anyList())).thenReturn(Collections.emptyList());
// mock掉对Redis的静态调用
PowerMockito.when(RedisUtils.getArray(eq("tom"), eq(StudentBo.class))).thenReturn(Collections.emptyList());
// 显示的mock掉静态的void的方法(可以不mock)
PowerMockito.doNothing().when(RedisUtils.class, "setArray", anyString(), anyList(), anyInt());
// mock私有方法processKeyword
PowerMockito.doReturn("tom").when(studentServiceUnderTest, "processKeyword", anyString());
// 跳过私有方法highlightResult的执行
PowerMockito.suppress(PowerMockito.method(StudentService.class, "highlightResult"));
// 使用Mockito来mock服务的调用
when(mockStudentDao.queryStudentByKeyWord(anyString())).thenReturn(Collections.emptyList());
// Run the test
final List<StudentBo> result = studentServiceUnderTest.queryStudentScoreByKeyword("tom");
}
}
Используйте mockito для имитации экземпляров
Предпочтительно, чтобы мы сначала использовали Mockito для имитации вызова Spring Bean.Mockito.mock
Вы можете издеваться над экземпляром, мы выбираем здесь@Mock
Обратите внимание, эффект тот же.
// 使用Mockito来mock服务的调用
when(mockStudentDao.queryStudentByKeyWord(anyString())).thenReturn(Collections.emptyList());
Моделирование статических вызовов Redis
Затем мы используем PowerMock, чтобы имитировать вызов статического метода.Обратите внимание, что нам нужноRedisUtils
класс, присоединяйтесь@PrepareForTest
В аннотации мы оба издевалисьgetArray
метод, также высмеянныйsetArray
метод, по сутиsetArray
Здесь нет необходимости издеваться над явным макетом.
PowerMockito.mockStatic(RedisUtils.class);
// mock掉对Redis的静态调用
PowerMockito.when(RedisUtils.getArray(eq("tom"), eq(StudentBo.class))).thenReturn(Collections.emptyList());
// 显式的mock掉静态的void的方法(可以不mock)
PowerMockito.doNothing().when(RedisUtils.class, "setArray", anyString(), anyList(), anyInt());
имитация одноэлементного класса
Фиктивный одноэлементный класс относительно сложен.Логически Powermock используется для имитации одноэлементного класса, а затем одноэлементный класс передается одноэлементному классу.getInstance
Нагромождение методов, возврат предыдущего макета, а затем накопление метода, фактически вызываемого классом макета, код выглядит следующим образом
PowerMockito.mockStatic(SchoolManageProxy.class);
// Powermock mock出单例类
SchoolManageProxy mockSchoolManageProxy = PowerMockito.mock(SchoolManageProxy.class);
// 给单例类的getInstance方法打桩
PowerMockito.when(SchoolManageProxy.getInstance()).thenReturn(mockSchoolManageProxy);
// 对mock类queryPerson的方法打桩
when(mockSchoolManageProxy.queryPerson(anyList())).thenReturn(Collections.emptyList());
имитация частного метода
можно увидетьqueryStudentScoreByKeyword
метод вызывает закрытый метод классаprocessKeyword
, если метод занимает слишком много времени, вы также можете использовать powermock для имитации частного метода,Обратите внимание, что studentServiceUnderTest должен использовать spy() для имитации
// mock 实例
// spy的标准是:如果不打桩,默认执行真实的方法,如果打桩则返回桩实现。
studentServiceUnderTest = PowerMockito.spy(studentServiceUnderTest);
// mock私有方法processKeyword
// doReturn(...) when(...)不做真实调用,但是when(...) thenReturn(...)还是会真实调用原方法,只是返回了指定的结果
PowerMockito.doReturn("tom").when(studentServiceUnderTest, "processKeyword", anyString());
Выполнение метода пропуска PowerMock
Также можно пропустить выполнение приватных методов с помощью PowerMock.
// 跳过私有方法highlightResult的执行
PowerMockito.suppress(PowerMockito.method(StudentService.class, "highlightResult"));
Суммировать
Раньше автор редко писал одиночный тест при написании кода, так как компания обязала улучшить охват одиночного теста, хотя эффективность разработки снизилась, это действительно привлекло мое внимание к одиночному тесту, и тогда я изучил Mock-фреймворки, такие как PowerMockito и Моккито. Powermock всемогущ, потому что он использует собственный загрузчик и методы манипулирования байт-кодом, и в то же время он очень прост и удобен в использовании, и это действительно очень хороший фреймворк.
Демонстрационный адрес:GitHub.com/LJ WL Green/макет…
Справочная документация