当前位置:网站首页>Two schemes of unit test

Two schemes of unit test

2022-07-07 09:00:00 bboyzqh

principle

unit testing FIRST The principles are as follows

  • Fast (fast): Unit tests should be run quickly , Otherwise, it will cost a lot of development / Deployment time .
  • Isolation (isolated): Different test cases are isolated . One test does not rely on another test .
  • repeatable (repeatable): Unit tests are repeatable , And when running repeatedly , Unit tests always give the same results ( The system environment is irrelevant ).
  • Self verification (self-validating): Unit tests can verify their results , When they all pass , Give a simple “OK” The report , When they fail , You need to output concise details .
  • In time (timely): Programmers before the code goes online , They should be written in time , To prevent bug.

Junit+Mockito+H2 database

 

The overall idea is to serve two parties API、 Configuration data are mock The way , Use a combination of H2 Memory database to initialize data to achieve the purpose of unit testing .

Two party service API use mock The way

To reduce unit test execution time , Need to remove two-party Services api And with mock Object substitution ,api mock The way is usually in Spring At startup mock Inject , namely mock Object replaces the real object . Here you can use Spring Self contained @TestExecutionListeners Annotation to unify mock Second party services api, The execution steps are as follows :

  1. establish TestContextManager
  2. Execute each one in turn AbstractTestExecutionListener Example of beforeTestClass Method
  3. Execute each one in turn AbstractTestExecutionListener Example of prepareTestInstance Method
  4. Spring Load and parse XML, In this process BeanFactoryPostProcessor
  5. When executing a test method , Execute each one in turn AbstractTestExecutionListener Example of beforeTestMethod Method
  6. Perform test methods
  7. Execute each one in turn AbstractTestExecutionListener Example of afterTestMethod Method  

In the above way, press @Mock The objects marked by annotations can be unified mock, Do not load the external environment , Keep the environment isolated 、 The unit test runs fast . The detailed code is as follows :

public class MockitoBeansPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<Class<?>, MockitoBeansTestExecutionListener.MockBeanWrapper> allMockBeans = MockitoBeansTestExecutionListener.resolvedAllMockBeans();
        for (Map.Entry<Class<?>, MockitoBeansTestExecutionListener.MockBeanWrapper> mockBeanWrapperEntry : allMockBeans.entrySet()) {
            beanFactory.registerResolvableDependency(mockBeanWrapperEntry.getKey(), mockBeanWrapperEntry.getValue().getMockObject());
            beanFactory.registerSingleton(mockBeanWrapperEntry.getValue().getBeanName(), mockBeanWrapperEntry.getValue().getMockObject());
        }
    }

}	

public class MockitoBeansTestExecutionListener extends DependencyInjectionTestExecutionListener {

    private static  Map<Class<?>, MockBeanWrapper> mockBeans = new ConcurrentHashMap<>();
    private static  Map<Class<?>, List<Field>> injectMockBeans = new ConcurrentHashMap<>();
    private static boolean hasInitialized = false;

    public static Map<Class<?>, MockBeanWrapper> resolvedAllMockBeans() {
        Assert.isTrue(hasInitialized);
        return Collections.unmodifiableMap(mockBeans);
    }

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        Field[] declaredFields = testContext.getTestClass().getDeclaredFields();
        //  Get the parent class Mock Method 
        Field[] superClassFields = testContext.getTestClass().getSuperclass().getDeclaredFields();
        List<Field> mockFields = Lists.newArrayList(declaredFields);
        if (superClassFields.length > 0) {
            mockFields.addAll(Lists.newArrayList(superClassFields));
        }
        // Will need mock The object is created 
        for (Field field : mockFields) {
            Mock mockAnnon = field.getAnnotation(Mock.class);
            if (mockAnnon != null) {
                field.setAccessible(true);
                MockBeanWrapper wrapper = new MockBeanWrapper();
                Class<?> type = field.getType();
                wrapper.setMockObject(Mockito.mock(type));
                wrapper.setBeanType(type);
                wrapper.setBeanName(StringUtils.isEmpty(mockAnnon.value()) ? field.getName() : mockAnnon.value());
                mockBeans.putIfAbsent(wrapper.getBeanType(), wrapper);
                injectMockBeans.compute(testContext.getTestClass(), (targetClass, waitInjectFields) -> {
                    if (waitInjectFields == null) {
                        waitInjectFields = new ArrayList<>();
                    }
                    waitInjectFields.add(field);
                    return waitInjectFields;
                });
            }
        }
        hasInitialized = true;
    }

    @Override
    public void beforeTestMethod(TestContext testContext) throws Exception {
        Object testInstance = testContext.getTestInstance();
        List<Field> fields = injectMockBeans.get(testContext.getTestClass());
        if (fields != null) {
            for (Field field : fields) {
                field.setAccessible(true);
                field.set(testInstance, mockBeans.get(field.getType()).getMockObject());
            }
        }
    }

    @Data
    public class MockBeanWrapper {
        private String beanName;
        private Class<?> beanType;
        private Object mockObject;
    }
}


//  Unit test base class 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:promotion/config/application-unittest.xml"})
@TestExecutionListeners({MockitoBeansTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        SqlScriptsTestExecutionListener.class})
public abstract class AbstractTest {
    @Mock
    protected LogisticService logisticService;
    
    @Mock
    protected ItemBatchSpecService itemBatchSpecService;
    
    //......
}

H2 Data initialization of memory database

H2 Database is an embedded memory database , Its grammar and MySQL The grammar is very close to , It is very suitable for unit test data preparation scenarios . So we need to achieve The data in the charge of this application directly depends on H2 Database storage , A single use case uses [email protected] Annotations prepare data separately , That is, the responsible method is not applied mock, From the upper layer to the real database call . The detailed configuration is as follows :

<jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:common/common_schema.sql"/>
    </jdbc:embedded-database>

    <bean id="h2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:common/mybatis-config.xml"/>
        <property name="mapperLocations">
            <list>
                <value>classpath:mapper/brule/Promotion*.xml</value>
            </list>
        </property>
    </bean>
    
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yt.smc.dal.flashbuy.mapper,com.yt.smc.dal.brule.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="h2SqlSessionFactory"/>
    </bean>

Unit test examples

The unit test class only needs to inherit the unit test base class AbstractTest that will do , The sample code is as follows :

public class NoBatchPlaceOrderTest extends AbstractTest {

    @Autowired
    private TradeService tradeService;

    @Sql("classpath:promotion/db/trade/placeorder/nobatch_place_order_01.sql")
    @Test
    public void testplaceOrderFinishTest1() {
        Mockito.when(itemPriceService.skuPrice(any())).thenReturn(getNoBatchSkuPriceMap());

        SmcResultData<List<CouponOwnerShareDTO>> smcResultData = new SmcResultData<>();
        smcResultData.setData(Lists.newArrayList());
        Mockito.when(couponTradeService.shareCouponOwner(any(), any())).thenReturn(smcResultData);

        SmcResultData<PaceOrderFinishReDTO> paceOrderFinishResult = new SmcResultData<>();
        PaceOrderFinishReDTO paceOrderFinishReDTO = new PaceOrderFinishReDTO();
        paceOrderFinishResult.setData(paceOrderFinishReDTO);
        Mockito.when(promotionPlaceExecuteService.placeOrderFinishPromotion(any(), any())).thenReturn(paceOrderFinishResult);

        Mockito.when(itemSqueryAdapter.listItemByIds(Mockito.any())).thenReturn(getItemDetailList());

        PlaceOrderRequestDTO requestDTO = buildPlaceOrderRequest1();
        PlaceOrderResponseDTO responseDTO = tradeService.placeOrderFinish(requestDTO);
        System.out.println(JSON.toJSONString(responseDTO));
        Assert.assertNotNull(responseDTO);
        Assert.assertEquals(1, responseDTO.getPromotionPlaceOrderDTOS().size());
        Assert.assertEquals(16900, responseDTO.getPromotionPlaceOrderDTOS().get(0).getOrderPrice().longValue());
        Assert.assertEquals(1, responseDTO.getPromotionPlaceOrderDTOS().get(0).getPromotionPlaceActivityDTOs().size());
        Assert.assertEquals(3000, responseDTO.getPromotionPlaceOrderDTOS().get(0).getPromotionPlaceActivityDTOs().get(0).getActivityCreatePrice().longValue());
    }	

Spock+Mockito+H2 database

use Spock Their ideas are basically the same Juit Think the same , The difference is in using groovy Language for writing unit tests . Use groovy Conduct unit testing based on behavior driven development (BDD) Thought , During unit testing, use cases need to be mapped to given、when and then, And Junit Compared with this way, it is closer to business expression . First, introduce dependencies as follows ( Pay attention to the matching version , Inconsistent versions may cause unit tests to fail ):

 <!--Spock The test framework -->
            <dependency>
                <groupId>org.spockframework</groupId>
                <artifactId>spock-spring</artifactId>
                <version>1.3-groovy-2.5</version>
                <scope>test</scope>
            </dependency>
            
            <dependency>
                <groupId>org.codehaus.groovy</groupId>
                <artifactId>groovy-all</artifactId>
                <version>2.4.4</version>
            </dependency>

Based on the above mock Idea creation AbstractSpockTest Base class , as follows :

@ContextConfiguration(locations = "classpath:promotion/config/application-unittest.xml")
@TestExecutionListeners([
        MockitoBeansTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        SqlScriptsTestExecutionListener.class
])
abstract class AbstractSpockTest extends Specification {

    @Mock
    protected ItemBatchSpecService itemBatchSpecService;
    
    //......
}

Examples of unit tests are as follows :

class UserTaskServiceSpockTest extends AbstractSpockMockTest {

    @Autowired
    private UserTaskService userTaskService;

    @Sql("classpath:db/share/query_share_detail_info.sql")
    @Unroll
    def " The shared person scans the sharing code details unit test "() {
        given:
        Mockito.when(activityService.checkActivityById(any(), any())).thenReturn(getCheckReturnResult())
        Mockito.when(activityService.checkUser(any(), any(), any())).thenReturn(getCheckReturnResult())
        Mockito.when(iSnsUserServiceWrapper.getSnsUserById(any())).thenReturn(getSnsTaoBaoUserDTO())
        ShareDetailQueryRequest shareDetailQueryRequest = new ShareDetailQueryRequest(shareCode: shareCode, userId: userId)

        expect:
        ServiceResult<ScanShareResultVO> result = userTaskService.queryShareDetailInfo(shareDetailQueryRequest)
        result.getData().getSelf() == self
        result.getData().getSourceUserId() == sourceUserId

        where:
        shareCode | userId | self | sourceUserId
        "da0f4a99c41ff8cc3634830552239c8fb5503076ab" | 3L | false | 5L
        "da0f4a99c41ff8cc3634830552239c899e683076ab" | 4L | true | 5L
    }
}

summary

The principles of the two implementation methods are the same , In terms of style "Spock+Mockito+H2 database " This method has less code 、 Closer to business expression , recommend !

原网站

版权声明
本文为[bboyzqh]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202130629348362.html