跳到主要内容

Junit开发指南

基类

cn.shoptnt.framework.test.BaseTest

此类加入了基础的注解:

@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest()
@ComponentScan("cn.shoptnt")
@Transactional
@Rollback()
public abstract class BaseTest {
}

基类加入了事务和自动回滚的注解,那么在单元测试中相应的操作最终会被回滚,而不影响下次测试

bootstrap.yml

需要在src/main/test/resources中定义一个bootstrap.yml,他和main/resources中的只有一项profile不一样:

spring:
application:
name: admin-api
cloud:
config:
uri: http://localhost:8888
label: master
profile: test
server:
port: 8082

shardingsphere druid的配置

spring:
shardingsphere:
props:
sql:
show: true
sharding:
default-data-source-name: ds0
datasource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.2.115:3306/default_database?useUnicode=true&characterEncoding=utf8&autoReconnect=true
username: root
password: 123456

tocken权限

在BaseTest中,定义了seller1,seller2,buyer1,buyer2四个tocken值,不需要在自己的单元测试中重复定义:

tokenuidsellerid
seller13
seller24
buyer11
buyer22

示例

    @Test
public void testAdd(){
Map brand = new HashMap();
brand.put("name","nametest");
brand.put("logo","logotest");

//执行插入的操作
daoSupport.insert("es_brand",brand);

//在此事务中,插入的数据可见
String dblogo = daoSupport.queryForString("select logo from es_brand where name=?","nametest");

//断言中也证明事务可见
Assert.assertEquals(dblogo,"logotest");

//测试结束后,会回滚测试中对数据库的操作
}

多数据源事务

我们系统因分库存在多个数据源,那么默认的事务管理是对商品库的事务管理,如果你的单元测试需要事务回滚,需要在自己的测试类上声明自己的事务管理,比如系统相关的单元测试如果要回滚:

@Transactional(value = "systemTransactionManager",rollbackFor = Exception.class)
public class SettingManagerTest extends BaseTest{
...

事务的周期

值得注意的是,每个单元测试(即每个@Test)都是一个事务周期

public class MyTest extends BaseTest {
@Test
public void testA(){ }

@Test
public void testB(){ }
}

如上所示,testA中对数据的操作,在testB中是不可见的,如果有要重复利用的操作,可以在testB中调用testA:

public class MyTest extends BaseTest {
@Test
public void testA(){ }

@Test
public void testB(){ testA(); }
}

当然根据你的业务场景需要,可以在自己的单元测试上注解@Rollback(false),来禁用回滚:

@Rollback(false)
public class MyTest extends BaseTest {
...
}

测试数据的准备和清除

如果测试场景需要,可以通过@Before、@After 注解来构造、清除测试所需数据:

    @Before
public void insertTestData(){
}

@After
public void cleanTestData(){
}

对象比较断言

为了方便restful api的单元测试,在BaseTest中内置了一个对象比较 ResultMatcher,使用方法如下:

    @Test
public void testAdd() throws Exception {

//构建一个预期的对象
Brand brand = new Brand();
brand.setName("name1");
brand.setLog("logo1");

//在断言中直接使用 objectEquals
mockMvc.perform(post("/goods/brands").param(...)
.andExpect( objectEquals( brand ) );

}

说明

objectEquals 比较的是controller中的返回值是否和预期的一样

实际上是将response的body(json)转为对象,再和预期的对象进行比较

这就要求要比较的对象必须实现了equals方法和toString方法

参数的批量校验

在BaseTest中提供了一个构建多参数map的方法 toMultiValueMaps ,使用示例:

    @Test
public void testAddParams() throws Exception {

//定义参数名
String[] names = new String[]{"name","logo","message"};

//定义几组要测试的参数情况和提示信息断言message
String[] values1 = new String[]{"","http:www.baidu.com","品牌名称不能为空"};
String[] values2 = new String[]{"三只松鼠","","品牌图标不能为空"};

List<MultiValueMap<String, String>> list = toMultiValueMaps(names,values2,values1);
for (MultiValueMap<String,String> params : list){
String message = params.get("message").get(0);
ErrorMessage error = new ErrorMessage("004",message);
mockMvc.perform(post("/goods/brands")
.params( params ))
.andExpect(status().is(400))
.andExpect( objectEquals( error ));
}

}

当测试参数校验很多的时候,重要的是集中精力排列下面的参数及提示的message:

        String[] names = new String[]{"name","logo","message"};
String[] values1 = new String[]{"","http:www.baidu.com","品牌名称不能为空"};
String[] values2 = new String[]{"三只松鼠","","品牌图标不能为空"};

consumer的测试

consumer的单元测试不用面向消息测试,直接面向业务测试即可,举例说明,假设有如下消费者:

/**
订单状态变化时发送短信
**/
@Component
public class OrderSmsMsgSender implements OrderStatusChangeEvent {

@Override
public void orderChange(OrderStatusChangeMsg orderMessage) {
//发送短信的代码
}

}

则直接调用此类来测试即可:

public class OrderSmsMsgSenderTest extends BaseTest{

@Autowire
private OrderSmsMsgSender orderSmsMsgSender
@Test
public void testSendSms(){
OrderStatusChangeMsg msg = new OrderStatusChangeMsg();
msg.setOrdersn("xxxx")
orderSmsMsgSender.orderChange(msg);

//执行断言,略...
}

}

规范

  1. 测试类的包名,和相应的测试目标的类相同。
  2. 测试的结果靠断言,而非system.print+肉眼
  3. 单元测试中不允许出现控制台的输出(system.print)
  4. 所有controller都必须提供单元测试
  5. 复杂逻辑的业务类也要提供单元测试
  6. 在代码请求合并之前,必须保证所有的单元测试通过(包括其他人的)