Unit testing

It is important for everyone to guide unit testing, but it is rare to do well in unit testing. In this class, we learned some principles and best practices of unit testing from the teacher.

Unit testing principles

1.1 Macro principles: AIR principles

At a macro level, unit tests as a whole must adhere to AIR principles.

  1. A: automation
  2. R: Repeatability
  3. I: independence

1.2 Practical operation principle: BCDE principle

  1. B: Border Border value test
  2. C: Correct the input and get the expected result
  3. D: Design is combined with Design documents
  4. E: Error indicates that the program has an Error

1.3 Introduction to common unit testing frameworks

Second, unit test actual combat

2.1 Basic Examples

  1. @before Execute Before each test case
  2. @after Executes After each test case
@RunWith(SpringRunner.class)
@SpringBootTest
public class JUnitDemoTest {

    private static final Logger logger = LoggerFactory.getLogger(JUnitDemoTest.class);

    @BeforeClass
    public static void setUpBeforeClass(a) throws Exception {
        logger.debug("before class");
    }

    @AfterClass
    public static void setUpAfterClass(a) throws Exception {
        logger.debug("after class");
    }

    @Before
    public void setUp(a) {
        logger.debug("setup for this test");
    }

    @After
    public void tearDown(a) {
        logger.debug("tearDown for this test");
    }

    @Test
    public void testCase1(a) {
        logger.debug("test case 1 excute...");
    }

    @Test
    public void testCase2(a) {
        logger.debug("test case 1 excute..."); }}Copy the code

The results

Junitdemotest-before class junitdemotest-started JUnitDemoTest in 16.716 seconds (JVM running for 18.076) JUnitDemoTest - setup for this test JUnitDemoTest - test case 1 excute... JUnitDemoTest - tearDown for this test JUnitDemoTest - setup for this test JUnitDemoTest - test case 1 excute... JUnitDemoTest - tearDown for this test JUnitDemoTest - after classCopy the code

2.2 Transaction related tests

You can control a test case and roll back the transaction when the test is done.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application.properties"})
@Transactional
@Rollback
public class TransactionalTest extends AbstractTransactionalJUnit4SpringContextTests {

    private static final Logger logger = LoggerFactory.getLogger(TransactionalTest.class);

    @BeforeTransaction
    public void beforeTranscationalDo(a) {}@AfterTransaction
    public void afterTranscationalDo(a) {}// The transaction is automatically rolled back according to class annotations
    @Test
    public void testOne(a) {}// Override class annotations automatically roll back the transaction, declared not to roll back
    @Rollback(false)
    public void testTwo(a) {}/**
     * As of Spring 3.0,@NotTransactional is deprecated in favor of moving
     * the non-transactional test method to a separate (non-transactional)
     * test class or to a @BeforeTransaction or @AfterTransaction method. As
     * an alternative to annotating an entire class with @Transactional,
     * consider annotating individual methods with @Transactional; doing so
     * allows a mix of transactional and non-transactional methods in the
     * same test class without the need for using @NotTransactional. * /
    @NotTransactional
    public void testThree(a) {}}Copy the code

The last NotTransactional was deprecated.

It is recommended that if you want to test a non-transactional method, you write the method separately to a non-transactional test class, or write the test statement to the @beforeTransaction or @AfterTransaction methods.

2.3 Test Coverage

2.4 Database code related unit testing practices


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AdminApplication.class)
public class MyBatisPlusTest {
    @Resource
    private UserDao userMapper;
    @Autowired
    //private DruidDataSource druidDataSource;
    private HikariDataSource dataSource;
    /** * test SQL injection, solution: to verify the special character of the parameter */
    @Test
    public void testLogin(a){
        // SQL injection example
        List<User> user=userMapper.login("'' or 1 = 1 limit 1 --"."123456");
        // Skip the login link
        // Verify whether SQL injection skips login. If not, the test succeeds; otherwise, the test fails
        // If you need to check for SQL injection vulnerability, you can use mybatis AbstractSqlInjector
        QueryWrapper<User> wrapper=new QueryWrapper();
        wrapper.eq("user_name"."'' or 1 = 1 limit 1 --");
        wrapper.eq("password"."123456");
        List<User> user2=userMapper.selectList(wrapper);
        // If no data is found
        assertThat(user2==null);
    }
    /** * if the number of connections is exceeded, obtain the number * no connection prompt timeout, you can catch the exception, prepare a bottom-pocket solution */
    @Test
    public void testSqlConnectionFull(a){
        List list=new ArrayList();
        try {
            // Increase the maximum load of the validation database in sequence
            for(int i=0; i<1000; i++){ list.add(dataSource.getConnection()); } List<User> users=userMapper.selectList(null);
            assertThat(users==null);
        } catch(SQLException throwables) { throwables.printStackTrace(); }}/** * verify that the database connection has been occupying a growing number of connections, avoiding thread deadlocks, etc. */
    @Test
    public void testConnectionOverTime(a){
        List<User> users=null;
        while (true){
            users=userMapper.selectList(null);
            assertThat(users==null); }}/** * Verify the crazy insert data exception rollback ** consider the business scenario, long business scenario, if the execution fails, then the problem of rolling back the transaction in turn will generally use the distributed transaction SEATA to solve this series of problems ** here is only a unit test if the failure of the simple business will be rolled back */
    @Transactional(rollbackFor = Exception.class)
    @Test
    public void testTransactionRollback(a){
        while (true){
            User user = new User();
            user.setUserName("2222");
            user.setPassword("123456");
            user.setRealName("testTransactionRollback"); userMapper.insert(user); }}/** * verify unique constraint exception, primary key exception, provide a backstop solution ** fail to save data, not because of constraint exception, insert data failed * insert failure, use autoincrement primary key, unique index exception, need to backup data, facilitate later data audit */
    @Test
    public void testRuntimeException(a){
        try{
            User user = new User();
            user.setUserName("2222");
            user.setPassword("123456");
            user.setRealName("testRuntimeException");
            assertThat(userMapper.insert(user)).isGreaterThan(0);
        }catch (Exception e){
            User user = new User();
            user.setUserName("2222");
            user.setPassword("123456");
            user.setRealName("testRuntimeException"); userMapper.insert(user); }}/** * Use the default MybaitS-Plus page search, check whether the module pages are correct * verify that the number of pages per page is too large, query direct memory overflow and other situations, need to use the Mybatis - Plus big data enhancement component * increase the maximum amount of a query data */
    @Test
    public void testPage(a) {
        System.out.println("----- baseMapper built-in page ------");
        Page<User> page = new Page<>(1.200000000);
        IPage<User> userIPage = userMapper.selectPage(page, new QueryWrapper<User>()
                .gt("age".6));
        assertThat(page).isSameAs(userIPage);
        System.out.println("Total number ------>" + userIPage.getTotal());
        System.out.println("Current page number ------>" + userIPage.getCurrent());
        System.out.println("Current number of pages displayed ------>" + userIPage.getSize());
        print(userIPage.getRecords());
        System.out.println("----- baseMapper built-in page ------");
    }

    @Test
    public void testSelectOne(a) {
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }

    @Test
    public void testInsert(a) {
        User user = new User();
        user.setRealName("testInsert");
        user.setUserName("2222");
        user.setPassword("123456");
        assertThat(userMapper.insert(user)).isGreaterThan(0);
        // Successfully get the writable ID directly
        assertThat(user.getId()).isNotNull();
    }

    @Test
    public void testDelete(a) {
        assertThat(userMapper.deleteById(3L)).isGreaterThan(0);
        assertThat(userMapper.delete(new QueryWrapper<User>()
                .lambda().eq(User::getUserName, "smile"))).isGreaterThan(0);
    }

    @Test
    public void testUpdate(a) {
        User user = userMapper.selectById(2);
        assertThat(user.getUserName()).isEqualTo("123");
        assertThat(user.getUserName()).isEqualTo("keep");

        userMapper.update(
                null,
                Wrappers.<User>lambdaUpdate().set(User::getPassword, "1231123").eq(User::getId, 2)); assertThat(userMapper.selectById(2).getPassword()).isEqualTo("1231123");
    }

    @Test
    public void testSelect(a) {
        List<User> userList = userMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
    }

    @Test
    public void testSelectCondition(a) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("max(id) as id");
        List<User> userList = userMapper.selectList(wrapper);
        userList.forEach(System.out::println);
    }


    private <T> void print(List<T> list) {
        if(! CollectionUtils.isEmpty(list)) { list.forEach(System.out::println); }}}Copy the code