Сводка по использованию Powermockito и Mockito

модульный тест

В последнее время компания продвигает модульное тестирование 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/макет…

Справочная документация