单元测试实战
什么是单元测试?先别管她是什么,也别急着甩锅给测试同学,我这里告诉你,单元测试100%工作必须由开发人员来做。别转眼珠子,就是你,接盘吧,少侠!
第2章
直接上代码
有这么个函数1
/**
* 求和
* @param x 输入Integer
* @param y 输入Integer
* @return 输出x+y
*/
public Integer add(Integer x,Integer y){
return x + y;
}
你怎么保证这函数没问题呢?比如输入两个最大整数Integer.MAX_VALUE,返回的还正确吗?
来,我们验证一下,写这么个函数2
@Test
public void testAdd(){
Integer x = 4;
Integer y = 5;
Integer z = add(x,y);
assertNotNull(z);//断言z非空
assertTrue((x+y)==z);//断言x+y=z
try{
z = add(Integer.MAX_VALUE,Integer.MAX_VALUE);
fail("不允许输入值的和大于最大整数!"); //断言并抛出异常
}catch(AssertionError e){
fail();//add应直接抛错,不应走这条路
}catch(Exception e){}
}
看到好多assert函数,是什么?断言,org.junit.Assert提供,用于判断结果预期,错误则抛出AssertionError
常用的有assertNotNull,assertNull,assertTrue, assertFalse,assertNotEquals,assertThat等
顾名思义assertNull就是参数是空,正常往下走,非空则抛错
还有个@Test,这又是啥?测试标记注解,被标明的函数运行时将被junit执行,稳如void main
常用的有@BeforeClass(全部方法执行前),@Before(test方法前),
@Test(测试方法),@Ignore(不执行的测试方法),@After(test后)
执行顺序BeforeClass-> Before->Test->After-> Before->Test->After->……
所以以上,咱需要 import static org.junit.Assert.*;
pom.xml增加
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
回头看!
函数2做了什么呢?
1)验证函数1基本正确性
x+y都不等于z还敢叫求和函数?
2)验证边界条件
任一输入过大或过小,和过大或过小,都是不行的
3)验证异常和错误处理
什么时候该抛,什么时候不该抛,得好好想想
为什么要做这些?
1)可控
人非圣贤,孰能无过,但交到甲方时候的工程不能出错啊,不然验收现场就是*会,产品同理
2)迎接变化
需求永远在变,周全而深远的考虑可以让我们敢于迎接变化
3)重构
没有单测的项目谁敢重构,你敢啊,不服墙就服你
本章主题:一般单测怎么做的
第3章
有这么个类3
@Service
public class BaseService{
@Autowired
BaseDao baseDao;
public PO get(Long id){
PO po = getDao().get(id);
return po;
}
private BaseDao getDao() {
return baseDao;
}
}
baseDao是需要spring注入进来,还是会有外部依赖,比如数据库,网络等,我想隔离开,分小部分测测
那我就写了这么个类4
public class mock{
@InjectMocks
BaseService baseService;//模拟主体对象
@Mock
BaseDao baseDao;//模拟注入对象
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);//初始化注解模拟注入
}
@Test
void testGet(){
PO po = new PO();
when(baseDao.get(anyLong())).thenReturn(po);//假定baseDao.get(id)返回vo,不往下层业务层延伸
PO result= baseService.get(id); //调了baseDao.get方法,会返回po,而不是往下调
assertNotNull(result);
assertEquals(result, po);
}
}
看到@InjectMocks、@Mock注解,代表baseDao被注入到了baseService中。
也可以自定义mock,如BaseDao baseDao = mock(BaseDao.class);
when().then则是测试桩,用于模拟行为,可返回对象,也可以抛出异常
anyLong()对应方法参数匹配Long,还有any(),anyFloat()等
模拟的意义在于将业务逻辑阻隔小范围内,防止延伸到变化过多的区域,推进可维护、模块化。
所以以上,咱需要 import static org.mockito.Mockito.*;
pom.xml增加
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
本章主题:模拟单测怎么做的
第4章
我怎么知道类3中的baseDao被spring注入成功呢?
我写了这么个函数5
@Test
public void testSpringBean(){
ApplicationContext context = newClassPathXmlApplicationContext("spring.xml");//获取spring上下文
assertNotNull(context.getBean("baseDao"));//断言不为空
}
手动取,好土,于是我换了一个类6
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath: spring.xml"
})
public class SpringTest {
@Autowired
BaseService baseService;
@Test
public void test(){
assertNotNull(baseService);//注入成功
assertNotNull(baseService.getDao());//baseDao注入成功
}
}
利用junit4才有的新功能,结合spring-test,优化了写法。
作为超类给其他需要测试spring注入的类用岂不是美滋滋?
所以以上,咱需要pom.xml增加
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
本章主题:spring注入怎么测
第5章
类3放回这里来看看
@Service
public class BaseService{
@Autowired
BaseDao baseDao;
public PO get(Long id){
PO po = getDao().get(id);
return po;
}
private BaseDao getDao() {
return baseDao;
}
}
我怎么确定baseDao.get(id)能正确从数据库取到数据呢?
于是我改写了类6
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath: spring-test.xml"
})
@TransactionConfiguration
@Transactional
public class SpringTest {
@Autowired
BaseDao baseDao;
@Test
public void test(){
assertNotNull(baseDao);//注入成功
assertNotNull(baseDao.get(1L)); //获取id为1的记录
assertEquals(baseDao.get(1L).getId(),1); //验证记录id
assertEquals(baseDao.get(1L).getName(),”a”); //验证记录name
}
}
加了@Transactional等注解是要干什么呢?
利用数据的事务回滚达到单测而不影响数据库正常数据
在此之前还有些准备工作得做
spring-test.xml得少不同于原spring.xml
可以采取改配置文件路径,也可以改变配置文件属性,为了和正常区分,最好自定义另外的配置文件
还需要增加jdbc命名空间
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd">
<!-- 初始化数据库表 -->
<jdbc:initialize-database data-source="oracleSource" ignore-failures="NONE">
<!—建表脚本-->
<jdbc:scriptlocation="classpath:sql/init_table.sql"/>
<!—数据脚本-->
<jdbc:scriptlocation="classpath:sql/init_data.sql"/>
</jdbc:initialize-database>
初始化时建表填数据,然后就可以增删改查啦
init_table.sql如下
set names 'utf8';
DROP TABLE IF EXISTS TABLENAME;
CREATE TABLE TABLENAME (
ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
name varchar(50) DEFAULT NULL,
PRIMARY KEY (ID)
);
init_data.sql如下
set names 'utf8';
insert into tableName (name) values ('a')
数据库的可以选择mysql,新建个test数据库用于单测,也可以用H2内存数据库哦
本章主题:持久层怎么单测
第6章
有这么个类7
@RestController
@RequestMapping("/api/v1/test")
public class BaseController {
@Autowired
BaseService baseService;
@RequestMapping(value=" {id}",method=RequestMethod.GET)
public Result get(@PathVariable Long id){
return new Result(baseService.get(id));
}
}
怎么验证url路径以及返回参数呢?
我就写了这么个类8
public class BasicControllerTest extends SpringTest{
public MockMvc mockMvc;
@Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.standaloneSetup(controllerTest).build();//mvn模拟
}
@Test
public void testGet() throws Exception {
String mediaType = "application/json;charset=UTF-8";
Long id = 1L;
String response = mockMvc.perform(
get("/api/v1/test/{id}",id)//get方法,url路径
.contentType(MediaType.parseMediaType(mediaType))//返回mime格式
.accept(MediaType.APPLICATION_JSON)//接受mime格式
)
.andDo(print())//打印
.andExpect(handler().handlerType(ControllerTest.class))//期望执行类
.andExpect(handler().methodName("get"))//期望执行方法
.andExpect(content().contentType(mediaType))//期望mime格式
.andExpect(status().isOk())//期望状态200
.andReturn().getResponse().getContentAsString();//返回响应数据
Boolean success = JsonPath.read(response, "$.success");//用jsonPath处理json节点数据
assertTrue(success);
}
}
SpringTest类就是删除无关数据、带有很多注解的类6
mockMvc利用http请求模拟响应,并对响应进行期望验证
JsonPath语法是:
json:"{\"success\":true,\"data\":{\"id\":1,\"name\"=\"a\"},\"message\":null}"
"$.success"则是取true,"$.data.name"则是取"a"
以上,咱需要
import staticorg.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import staticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import staticorg.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import com.jayway.jsonpath.JsonPath;
pom.xml增加
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
本章主题:控制层springMVC怎么单测
第1章
现在大概知道什么是单元测试了吧?
关键就是单元两个字,无限的细分单元,做到可测试,也就做到了模块化可维护。
下图是测试金字塔,单测占了测试工作的70%,如果不想继续
合并需求分支->发测->改bug->发测->改bug……,无限循环的话,就赶紧单测写起来
每次重构完,单测跑一圈全通过,那状态,比用了飘柔还自信啊。
myeclipse,idea等IDE提供了快速写单测的方法哦
下面介绍myeclipse的,
对要测的类右键new JUnit Test Case,选New JUnit 4 test
Source Folder选src/test/java,package一样,Next->
选中要测的方法,完成则自动形成了测试类。当然,这只用于后补测试的情况。
单测接触久了,你就该玩TDD了。什么是TDD?
上一篇: SpringBoot单元测试
下一篇: springboot单元测试