Junit开发指南
基类
com.enation.app.javashop.framework.test.BaseTest
此类加入了基础的注解:
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest()
@ComponentScan("com.enation.app.javashop")
@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
druid的配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/v7_goods
    username: root
    password: kingapex
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      web-stat-filter:
        enabled: false
因druid的bug,使用 @AutoConfigureMockMvc 会导致他的一个filter报空指针异常,所以在我们的test 配置中要增加的配置:
spring.datasource.druid.web-stat-filter.enabled=false
来禁用druid的filter
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都必须提供单元测试
 - 复杂逻辑的业务类也要提供单元测试
 - 在代码请求合并之前,必须保证所有的单元测试通过(包括其他人的)