单元测试的意义与技术选型

简单应用
spring单元测试

需要首先引入jar包并指定测试类SpringJUnit4ClassRunner

 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13</version>
     <scope>test</scope>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-test</artifactId>
     <version>5.2.10.RELEASE</version>
 </dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AppTest {
    @Autowired
    private ResumeMapper resumeMapper;
    @Test
    public void testmp() {
        List<Resume> resumes = resumeMapper.selectList(null);
        for (int i = 0; i < resumes.size(); i++) {
            Resume resume = resumes.get(i);

            System.out.println(resume);
        }
    }
}
springboot单元测试

springboot对单元测试的支持十分完善,需要引入的jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

使用方式:在测试类的类头部需要添加@RunWith(SpringRunner.class)@SpringBootTest注解,在测试方法的顶端添加@Test即可

@RunWith(SpringRunner.class)
@SpringBootTest
public class SysUserServiceImplTest {
    @Autowired
    private SysUserService sysUserService;
    @Test
    public void queryAllPerms() throws Exception {
        List<String> allPerms = sysUserService.queryAllPerms(1L);
        System.out.println(allPerms);
    }
}    
单元测试意义

单元测试主要是测试程序员自己编写的代码逻辑的正确性,而非是端到端的集成测试,它不需要测试所依赖的外部系统的逻辑正确性。

测试用例应该什么时候跑?

  • 功能实现之后
  • 重构之后,单元测试是保证重构不出错最有效的手段

技术选型:junit4+jmockit1.5+jacoco

Junit4用于编写单元测试,通过编写正常的业务流程,并进行相应的断言测试,来验证接口。

JMockit是google code上面的一个java单元测试mock项目,它很方便地让你对单元测试中的final类,静态方法,构造方法进行mock,主要是通过jmockit模拟一个返回对象。

jacoco用于统计单元测试覆盖率,其他相似工具如Cobertura、Emma、Clover。
单元测试不要盲目追求覆盖率,单元测试指的是类或方法,而不是模块或系统。

问题现状

目前组内成员开发过程中,既没有单元测试也没有Code Review流程。
写好代码直接提交,然后丢给黑盒测试组,测试发现问题就提bug给开发团队再修改,测不出的问题就留在线上出了问题在修复。

什么是mock

如果代码中依赖了外部系统或者不可控组件,比如,需要依赖数据库、网络通信、文件系统等,那我们就需要将被测代码与外部系统解依赖,而这种解依赖的方法就叫作mock。
mock 的方式主要有两种,手动 mock 和利用框架 mock。
依赖注入是编写可测试性代码的最有效手段。通过依赖注入,我们在编写单元测试的时候,可以通过 mock 的方法解依赖外部服务。

有一些典型的不好测试的代码,注意编码时规避掉

未决行为,即代码的输出结果是不确定的,如时间、随机数相关的

public class OrderService {
    public long caculateDelayDays(Date dueTime) {
        long currentTimestamp = System.currentTimeMillis();
        if (dueTime.getTime() >= currentTimestamp) {
            return 0;
        }
        long delayTime = currentTimestamp - dueTime.getTime();
        long delayDays = delayTime / 86400;
        return delayDays;
    }
}

以上判断是否超期的逻辑最好能封装到函数,这样测试时可以直接mock结果

public class OrderService {
    public long caculateDelayDays(Date dueTime) {
        long currentTimestamp = System.currentTimeMillis();
        if (!isExpired(dueTime, currentTimestamp)) {
            return 0;
        }
        long delayTime = currentTimestamp - dueTime.getTime();
        long delayDays = delayTime / 86400;
        return delayDays;
    }

    private boolean isExpired(Date dueTime, long currentTimestamp) {
        if (dueTime.getTime() >= currentTimestamp) {
            return false;
        }
        return true;
    }
}
JMockit测试的两种实现方式

JMockit有两种测试方式,一种是基于行为的,一种是基于状态的测试。

  • 基于行为的mock,对mock目标代码的行为进行模仿,更像黑盒测试
  • 基于状态的mock,是站在目标测试代码内部的。可以对传入的参数进行检查、匹配,才返回某些结果,类似白盒。

基于状态的 new MockUp基本上可以mock任何代码或逻辑。

final UserBaseInfo userBaseInfo = new UserBaseInfo();
new NonStrictExpectations(){
    {
	new MockUp<SessionContainer>() {
	    @Mock
	    public SessionCache getSession() {
		return new SessionCache();
	    }
	}.getMockInstance();
	
	new MockUp<SessionCache>() {
	    @Mock
	    public UserBaseInfo getUserBaseInfo() {
		return userBaseInfo;
	    }
	}.getMockInstance();
    }
};

类命名和方法命名以Test结尾,方法添加注解@Test,需要引入的对象添加注解@Injectable

public class UserHeartRateServieTest{
	@Injectable
	private UserHeartRateResultDao userHeartRateResultDaoMock;
	@Test
	public void insertUserHeartRateTest(){
		new NonStrictExpectations(){
		    {
		    userHeartRateResultDaoMock.insertUserHeartRateResult((UserHeartRateResult)any);
		    }
		};
	}
}

new NonStrictExpectations(){
            {
                userHeartRateResultDaoMock.selectByIdOrUserId(anyInt,anyInt);
                result = userHeartRateResult;
                
                userHeartRateDataDaoMock.getUserHeartRateDataListByGroupId(anyString);
                result = list;
            }
        };

pom.xml单元测试配置

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.googlecode.jmockit</groupId>  
    <artifactId>jmockit</artifactId>  
    <version>1.5</version>  
    <scope>test</scope>  
</dependency>  
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.7.4.201502262128</version>
    <configuration>
    <includes>
    </includes>
    <excludes>
    </excludes>
</plugin>
Expectations与NonStrictExpectations

Expectations块里声明的mock方法,是一定要被执行的,如果没有被执行,会认为整个测试case不通过;NonStrictExpectations就没有这个限制

new Expectations(){
	{
		executorDao.getExecutorById(anyInt);
		result = rtnMap;
		teamDao.getTeamById(anyInt);
		result = teamMap;
		teamDao.getEnergyByGreenAreaId(anyInt);
		result = 1;
	}
};

对于需要引入工具类的代码行,可以作为参数传进来

参考

JMokit使用方式

单元测试参考:springboot(16)Spring Boot使用单元测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值