Разговор о модульном тестировании

задняя часть модульный тест

Когда вы сталкиваетесь с проблемами, больше думайте, больше проверяйте и больше проверяйте, чтобы вы могли что-то получить, и тогда вы сможете проявить усердие и желание поделиться, и тогда вы сможете написать хорошую статью.

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

Определение и особенности

модульное тестирование: относится к проверке и проверке наименьшей тестируемой единицы в программном обеспечении.

Это определение немного абстрактно. Вот несколько особенностей модульного тестирования. Давайте почувствуем это: Как правило, функция оснащена несколькими модульными тестами, модульные тесты не должны зависеть от внешних систем, модульные тесты выполняются очень быстро, и модульные тесты должны не создавать тестовую среду.Грязные данные, модульные тесты можно запускать повторно.

преимущество

Модульное тестирование позволяет нам модифицировать и рефакторить бизнес-код, не беспокоясь о побочных эффектах модификации некоторого кода.

Модульное тестирование может помочь нам задуматься о рациональности разделения модулей.Если модульный тест написан с очень сложной логикой или функция слишком сложна для написания одного теста, это означает, что существует проблема с абстракцией модуля.

Модульное тестирование делает систему более удобной в сопровождении и читабельной; для новичков в команде чтение системного кода может начаться с модульного тестирования и знакомства с логикой системы после небольшого старта.

Болевые точки, которые будут решены в этой статье

  1. Когда пишется единый тест?
    Если ваша команда придерживается стиля TDD, его нужно писать перед кодированием, если нет, то не рекомендуется запускать дополнительные юнит-тесты после написания всего бизнес-кода. (Наиболее) подходящее время для модульного тестирования: после написания части бизнес-логики напишите несколько модульных тестов для ее проверки.
  2. Как написать одиночный тест?
    Иерархический модульный тест: уровень работы с базой данных, уровень зависимостей промежуточного программного обеспечения, уровень бизнес-логики, их соответствующие модульные тесты написаны отдельно и не зависят друг от друга.
  3. Одиночный тест работает слишком медленно?
  • Тест слоя Dao, используйте H2 для тестирования, сделайте независимый BaseH2Test, независимый test-h2-applicationContext.xml, только тест для dao
  • Тестирование сервисного уровня, опираясь на фреймворк mockito, используя аннотацию @RunWith(MockitoJUnitRunner.class), нет необходимости загружать другие bean-компоненты Spring, специальное использование
  • Для промежуточного ПО, которое зависит от внешнего (например, Redis, Diamond, mq), обратите внимание на отдельную загрузку и тестирование при обработке модульных тестов, особенно отдельно от дао-тестов.

Практика модульного тестирования в проектах Spring

мы основаны наunit-test-demoПрактика модульного тестирования в этом проекте.

Модульный тест уровня Дао

При написании одиночного теста в начале необходимо подключиться к БД DEV, в это время будет две беды: при проблеме с сетью одиночный тест не пройдет, а когда грязные данные генерируется в базе данных, приложение будет работать ненормально. Здесь мы выбираем H2 для модульного тестирования уровня DAO. Есть следующие шаги:

  • Создайте новый каталог h2 в разделе resources для хранения файлов schema.sql и data-prepare-user.sql.Первый используется для сохранения оператора построения таблицы, а второй используется для подготовки исходных данных.
  • test-data-source.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"   xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/jdbc
       http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">


    <!-- 初始化数据表结构 -->
    <jdbc:initialize-database data-source="dataSource">
        <jdbc:script location="classpath:h2/schema.sql" encoding="UTF-8"/>
        <jdbc:script location="classpath:h2/data-prepare-*.sql" encoding="UTF-8"/>
    </jdbc:initialize-database>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="url" value="${user.jdbc.url}"/>
        <property name="username" value="${user.jdbc.username}"/>
        <property name="password" value="${user.jdbc.password}"/>
        <!-- 连接池初始连接数 -->
        <property name="initialSize" value="3"/>
        <!-- 允许的最大同时使用中(在被业务线程持有,还没有归还给druid) 的连接数 -->
        <property name="maxActive" value="30"/>
        <!-- 允许的最小空闲连接数,空闲连接超时踢除过程会最少保留的连接数 -->
        <property name="minIdle" value="3"/>
        <!-- 从连接池获取连接的最大等待时间 5 秒-->
        <property name="maxWait" value="5000"/>

        <!-- 强行关闭从连接池获取而长时间未归还给druid的连接(认为异常连接)-->
        <property name="removeAbandoned" value="true"/>
        <!-- 异常连接判断条件,超过180 秒 则认为是异常的,需要强行关闭 -->
        <property name="removeAbandonedTimeout" value="180"/>

        <!-- 从连接池获取到连接后,如果超过被空闲剔除周期,是否做一次连接有效性检查 -->
        <property name="testWhileIdle" value="true"/>
        <!-- 从连接池获取连接后,是否马上执行一次检查 -->
        <property name="testOnBorrow" value="false"/>
        <!-- 归还连接到连接池时是否马上做一次检查 -->
        <property name="testOnReturn" value="false"/>
        <!-- 连接有效性检查的SQL -->
        <property name="validationQuery" value="SELECT 1"/>
        <!-- 连接有效性检查的超时时间 1 秒 -->
        <property name="validationQueryTimeout" value="1"/>

        <!-- 周期性剔除长时间呆在池子里未被使用的空闲连接, 10秒一次-->
        <property name="timeBetweenEvictionRunsMillis" value="10000"/>
        <!-- 空闲多久可以认为是空闲太长而需要剔除 30 秒-->
        <property name="minEvictableIdleTimeMillis" value="30000"/>

        <!-- 是否缓存prepareStatement,也就是PSCache,MySQL建议关闭 -->
        <property name="poolPreparedStatements" value="false"/>
        <property name="maxOpenPreparedStatements" value="-1"/>

        <!-- 是否设置自动提交,相当于每个语句一个事务 -->
        <property name="defaultAutoCommit" value="true"/>
        <!-- 记录被判定为异常的连接 -->
        <property name="logAbandoned" value="true"/>
        <!-- 网络读取超时,网络连接超时 -->
        <property name="connectionProperties" value="connectTimeout=1000;socketTimeout=3000"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"/>
        <property name="typeAliasesPackage" value="org.learnjava.dq.core.dal.bean"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="org.learnjava.dq.core.dal.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
</beans>
  • test-h2-applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 激活自动代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <!-- spring容器启动时,静态配置替换 -->
    <context:property-placeholder location="classpath*:*.properties" ignore-unresolvable="true"/>

    <context:component-scan base-package="org.learnjava.dq.core.dal.dao"/>

    <import resource="test-data-sources.xml"/>
</beans>
  • UserInfoDAOTest
    Этот файл является основным содержанием модульного теста слоя DAO. Я написал только один. Читатели и друзья могут скачать код и попрактиковаться самостоятельно, а остальное написать. PS: Здесь у нас только один DAO, поэтому загрузка контейнера Spring помещается в этот файл.Если DAO много, рекомендуется извлечь файл BaseH2Test, чтобы всем модульным тестам DAO нужно было загрузить контейнер Spring только один раз.
package org.learnjava.dq.core.dal.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.learnjava.dq.core.dal.bean.UserInfoBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Date;
import javax.annotation.Resource;
import static org.junit.Assert.*;

/**
 * 作用:
 * User: duqi
 * Date: 2017/6/24
 * Time: 09:33
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-h2-applicationContext.xml")
public class UserInfoDAOTest {

    @Resource
    private UserInfoDAO userInfoDAO;

    @Test
    public void saveUserInfoBean() throws Exception {
        UserInfoBean userInfoBean = new UserInfoBean();
        userInfoBean.setUserId(1003L);
        userInfoBean.setNickname("wangwu");
        userInfoBean.setMobile("18890987675");
        userInfoBean.setSex(1);
        userInfoBean.setUpdateTime(new Date());
        userInfoBean.setCreateTime(new Date());

        int rows = userInfoDAO.saveUserInfoBean(userInfoBean);

        assertEquals(1, rows);
    }

    @Test
    public void updateUserInfoBean() throws Exception {
    }

    @Test
    public void getUserInfoBeanByUserId() throws Exception {
    }

    @Test
    public void getUserInfoBeanByMobile() throws Exception {
    }

    @Test
    public void listUserInfoBeanByUserIds() throws Exception {
    }

    @Test
    public void removeUserInfoBeanByUserId() throws Exception {
    }

}

Модульный тест сервисного уровня

  • Mockito
    Mocktio — это очень простой в использовании фреймворк для имитации. Разработчики могут положиться на краткий API, предоставляемый Mockito, для написания красивых модульных тестов.

Mockito — это мока-фреймворк, который действительно хорош на вкус. Он позволяет вам писать красивые тесты с чистым и простым API. Mockito не дает вам похмелья, потому что тесты очень читабельны и выдают чистые ошибки проверки.

  • UserInfoManagerImplTest
    Модульное тестирование не должно зависеть от того, правильная ли логика выполнения слоя DAO [иначе это интеграционный тест], необходимо предположить, как выглядит поведение DAO, а затем проверить, правильная ли логика этого слоя .
    Здесь @RunWith(MockitoJUnitRunner.class) используется для украшения текущего класса модульного теста.Если существует несколько классов модульного теста, вы можете рассмотреть возможность извлечения базового класса BaseBizTest.
package org.learnjava.dq.biz.manager.impl;


import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.learnjava.dq.biz.domain.UserInfo;
import org.learnjava.dq.biz.manager.UserInfoManager;
import org.learnjava.dq.core.dal.bean.UserInfoBean;
import org.learnjava.dq.core.dal.dao.UserInfoDAO;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;

import static org.junit.Assert.*;

import static org.mockito.Mockito.*;

/**
 * 作用:
 * User: duqi
 * Date: 2017/6/24
 * Time: 09:55
 */
@RunWith(MockitoJUnitRunner.class)
public class UserInfoManagerImplTest {

    @Mock //用于定义被Mock的组件
    private UserInfoDAO userInfoDAO;

    @InjectMocks //用于定义待测试的组件
    private UserInfoManager userInfoManager = new UserInfoManagerImpl();

    private UserInfo userInfoToSave;

    @Before
    public void setUp() throws Exception {
        //用于初始化@Mock注解修饰的组件
        MockitoAnnotations.initMocks(this);

        userInfoToSave = new UserInfo();
        userInfoToSave.setMobile("18978760099");
        userInfoToSave.setUserId(7777L);
        userInfoToSave.setSex(1);
    }

    @Test
    public void saveUserInfo_case1() throws Exception {
        //step1 准备数据和动作
        doReturn(1).when(userInfoDAO).saveUserInfoBean(any(UserInfoBean.class));

        //step2 运行待测试模块
        Boolean res = userInfoManager.saveUserInfo(userInfoToSave);

        //step3 验证测试结果
        assertTrue(res);
    }

    @Test
    public void saveUserInfo_case2() throws Exception {
        //step1 准备数据和动作
        doReturn(0).when(userInfoDAO).saveUserInfoBean(any(UserInfoBean.class));

        //step2 运行待测试模块
        Boolean res = userInfoManager.saveUserInfo(userInfoToSave);

        //step3 验证测试结果
        assertFalse(res);
    }

    @Test
    public void updateUserInfo() throws Exception {
    }

    @Test
    public void getUserInfoByUserId() throws Exception {
    }

    @Test
    public void getUserInfoByMobile() throws Exception {
    }

    @Test
    public void listUserInfoByUserIds() throws Exception {
    }

    @Test
    public void removeUserInfoByUserId() throws Exception {
    }

}
  • Мокито: главное
    • MockitoJUnitRunner: контейнерная среда для запуска модульных тестов.
    • Макет: используется для имитации внешних компонентов, которые зависят от тестируемого модуля.
    • InjectMock: используется для идентификации тестируемого компонента.
    • org.mockito.Mockito.*: методы этого класса можно использовать для указания ожидаемого поведения компонента Mock, включая обработку исключений.

Суммировать

  1. Три этапа модульного тестирования
  • Подготовка данных, поведение
  • тестовый целевой модуль
  • Результаты проверки
  1. Помимо Junit, Mockito, H2, упомянутых в этой статье, существует множество других фреймворков для модульного тестирования, таких какTestNG,spockЖдать.
  2. В веб-проектах Java уровень контроллера обычно не пишет бизнес-логику, поэтому нет необходимости писать модульные тесты, но если вы хотите написать, есть способы, вы можете обратиться к моей предыдущей статье:Использование инфраструктуры Spock в проекте Spring Boot.
  3. Код юнит-тестов — это тоже онлайн-код, к нему следует относиться так же серьезно, как и к бизнес-коду, а также необходимо обращать внимание на повторное использование кода и тестовых данных.

использованная литература

  1. Упростите тестирование с помощью аннотации Mockito — Использование Mockito и JUnit [2]
  2. Искусство модульного тестирования
  3. Стандарты кодирования Java для Alibaba