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值,不需要在自己的单元测试中重复定义:
token | uid | sellerid |
---|---|---|
seller1 | 3 | |
seller2 | 4 | |
buyer1 | 1 | |
buyer2 | 2 |
示例
@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);
//执行断言,略...
}
}
规范
- 测试类的包名,和相应的测试目标的类相同。
- 测试的结果靠断言,而非system.print+肉眼
- 单元测试中不允许出现控制台的输出(system.print)
- 所有controller都必须提供单元测试
- 复杂逻辑的业务类也要提供单元测试
- 在代码请求合并之前,必须保证所有的单元测试通过(包括其他人的)