单元测试之道
来源:互联网 发布:jav网络机顶盒v6 编辑:程序博客网 时间:2024/05/17 01:41
开发设计
抽象产品和需求
1、站在用户角度思考产品的行为
2、分析业务领域模型
3、测试领域模型
一切面向接口设计
1、设计基于restful的风格接口:
- uri代表了资源的实体
- http method描述了资源的状态转化,目前有四个:get,post,put,delete
- 例如,获取用户信息,get /user/info or /user/1
2、设计业务方法接口
- 每个业务方法应该都能描述用户的操作
- 基于最简单的业务方法进行组合或扩展
- 测试每一个或每一组方法
3、设计功能性接口
- 单一原则
- 依赖倒置
单元测试
了解junit
- 使用assert断言,验证程序输出的结果是否正确
- 使用@Before初始化测试数据
- 使用@After清除测试后的数据
- 使用@Test注明测试单元
- 使用@Test中的expected测试异常状态单元
编写可测试的类
- 在src/main/test中创建单元测试
- 测试的package需要和被测试的类的package保持一致
- 对于需要测试的Function或者类,尽可能使用默认的修饰符,避免使用private修饰符
- 如果Function使用了外部类,使用mock方式处理外部类的方法返回数据
- 基于单一原则,设计类以及函数
- 基于依赖倒置原则,设计和抽象对象和函数
了解mockito
方法介绍
1、使用mock函数模拟一个外部类的实现
2、使用spy函数监控一个外部类的调用
3、使用verify验证被spy过的外部类的调用次数
4、mock后的对象可以使用
when..thenDo..something的表达式处理函数内的调用逻辑
测试ibatis的dao
1、初始化dataSource,可以实现一个测试工具类,注入相关的数据库连接的配置,然后实例化出来
2、然后初始化SqlMapClientFactoryBean,只需要将相关的xml配置设置到mappingLocations属性,然后调用afterPropertiesSet初始化SqlMapClient,最后就可以直接调用getObjectClient
3、dao一般都继承于SqlMapClientDaoSupport,例如UserDao,那么基于前面的DataSource和SqlMapClient,就可以直接使用new UserDao(dataSource,sqlMapClient)构建出来
4、通过使用@Before和@After,构建dao和销毁dao
测试业务层的函数
1、如果业务层的函数只是一些逻辑处理,那么可以直接使用junit的assert进行验证
2、如果函数依赖了dao层,那么使用mock模拟成dao处理
3、如果函数依赖了其他service服务,那么同样使用mock进行模拟处理
示例如下:
public class BaseOrderMinusLockBusinessTest { OrderAddInStockBusiness orderAddInStockBusiness=new OrderAddInStockBusiness(); Staff staff; Order order; @Before public void setUp(){ staff=new Staff(); order=new Order(); orderAddInStockBusiness.goodsSectionOrderRecordDao= Mockito.mock(GoodsSectionOrderRecordDao.class); orderAddInStockBusiness.wmsInventoryUpdateBusiness=Mockito.mock(WmsInventoryUpdateBusiness.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { List<Long> orderIds=(List<Long>)invocationOnMock.getArguments()[1]; List<GoodsSectionOrderRecord> list=new ArrayList<GoodsSectionOrderRecord>(); for(Long orderId:orderIds){ GoodsSectionOrderRecord gor=new GoodsSectionOrderRecord(); gor.setOrderId(orderId); gor.setGetNum(1); list.add(gor); GoodsSectionOrderRecord gor1=new GoodsSectionOrderRecord(); gor1.setOrderId(orderId); gor1.setGetNum(2); list.add(gor1); } return list; } }).when(orderAddInStockBusiness.goodsSectionOrderRecordDao).findByOrderIds(Mockito.any(Staff.class), Mockito.anyListOf(Long.class), Mockito.anyLong()); } @Test public void testOp0(){ orderAddInStockBusiness.op0(staff,order); Mockito.doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { List<WmsChangeAffect> wmsChanges=(List<WmsChangeAffect>)invocationOnMock.getArguments()[0]; Asserts.check(wmsChanges.size()==2,"需要更新的货位库存数有误!"); return null; } }).when(orderAddInStockBusiness.wmsInventoryUpdateBusiness) .updateAssoBatch(Mockito.anyListOf(WmsChangeAffect.class), Mockito.any(Staff.class)); }}
测试控制器层的函数
1、遵循单一原则,controller和service实现一对一的映射
2、controller是属于state-ful的类,所以只需要将service mock处理,并验证传递的参数是否被service成功调用
测试领域模型
1、由于业务使用的贫血模型,所以领域模型分为model和service
2、model的get、set方法有些可能也包含了业务逻辑,所以对get和set方法也需要进行单元测试
测试多线程的函数
1、一般用于测试多线程环境下某个业务场景是否能保障线程安全,例如开启100个线程,同时对一个共享变量计数,同时对一个共享变量计数10次,那么最终期望的结果为1000
2、测试性能,验证最终期望的消费时间是否合理
3、测试多线程组件是否正确,例如阻塞队列的函数、计划任务、并发聚合函数等
示例如下:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:spring/spring-*.xml")public class TestStockService { @Resource IStockService stockService; Staff staff; Long companyId = 10001L; User user; Company company; Long sysItemId=1l; Long sysSkuId=3l; Long warehouseId=4l; ExecutorService executor= Executors.newFixedThreadPool(10); @Resource JdbcTemplate jdbcTemplate; @Before public void setUp() throws Exception { company = new Company(); company.setId(companyId); CompanyProfile p = new CompanyProfile(); p.setCompanyId(company.getId()); p.setDbInfo(new DbInfo()); company.setProfile(p); staff = new Staff(); staff.setCompanyId(company.getId()); staff.setCompany(company); staff.setId(10001L); Map<Long, User> userMap = new HashMap<Long, User>(); user = new User(); user.setTaobaoId(10001L); user.setId(10L); userMap.put(user.getId(), user); staff.setUserIdMap(userMap); initData(); } private void initData() { jdbcTemplate.update(" delete from stock_0 where company_id=? ",companyId); jdbcTemplate.update(" delete from stock_order_record_0 where company_id=? ",companyId); jdbcTemplate.update("insert into stock_0(company_id,sys_item_id,sys_sku_id,available_in_stock,lock_stock,defective_stock,ware_house_id) " + " values(?,?,?,?,?,?,?)",companyId,sysItemId,sysSkuId,-100,100,100,warehouseId); for(int i=0;i<100;i++){ jdbcTemplate.update("insert into stock_order_record_0(stock_status,lock_type,sid,order_id,sys_item_id,sys_sku_id,num,warehouse_id,company_id,stock_num,difference_value)" + " values(?,?,?,?,?,?,?,?,?,?,?) ",3,0,i,i,sysItemId,sysSkuId,1,warehouseId,companyId,0,1); } jdbcTemplate.update("insert into stock_0(company_id,sys_item_id,sys_sku_id,available_in_stock,lock_stock,defective_stock,ware_house_id) " + " values(?,?,?,?,?,?,?)",companyId,sysItemId,sysSkuId+1,-100,100,100,warehouseId); for(int i=0;i<100;i++){ jdbcTemplate.update("insert into stock_order_record_0(stock_status,lock_type,sid,order_id,sys_item_id,sys_sku_id,num,warehouse_id,company_id,stock_num,difference_value)" + " values(?,?,?,?,?,?,?,?,?,?,?) ",3,0,i,i,sysItemId,sysSkuId+1,1,warehouseId,companyId,0,1); } } @Test public void save4StockInventory(){ final CountDownLatch latch=new CountDownLatch(1); for(int i=0;i<10;i++){ final StockChangeAffect stockChangeAffect=new StockChangeAffect(); stockChangeAffect.setSysItemId(sysItemId); stockChangeAffect.setSysSkuId(sysSkuId); stockChangeAffect.setWareHouseId(warehouseId); stockChangeAffect.setAvailableStockWithLock((i+1)*10l); stockChangeAffect.setFromWms(false); executor.execute(new Runnable() { public void run() { try { latch.await(); stockService.save4StockInventory(staff,stockChangeAffect); } catch (Exception e) { e.printStackTrace(); } } }); } latch.countDown(); while(!executor.isTerminated()){ try { Thread.sleep(1000l); } catch (InterruptedException e) { e.printStackTrace(); } } }}
基于业务的边界分析
1、分析领域模型的状态转变,例如订单状态由待付款变为待发货的case,或者库存状态由充足变为缺货的case等
2、分析行为实施到不同的数据状态时,验证测试所期望的结果,例如:用户发货,如果对待发货的、待付款的、已发货的、异常状态的订单进行处理等不同的case
3、分析依赖模块的业务边界数据如何影响到自己的业务,例如订单向库存系统锁定库存,此时库存系统会返回各种库存状态,对于不同的库存状态需要编写不同的case进行校验
基于工具类的边界分析
1、空指针异常
2、数组越界异常
3、工具函数返回值是否是自己所期望的
4、最小值和最大值的测试
单元测试命名规范
1、一个单元测试类,在junit中称之为测试套件,一个套件中包含多个单元测试,那么套件的命名一般以Test开头,例如测试的类为UserDao,那么单元测试的套件名称为TestUserDao
2、标记为@Before的方法名称为setUp
3、标记为@After的方法名称为tearDown
4、标记为@Test的方法名称一般为test+测试的方法名称,例如测试UserDao.queryById,那么测试方法名为testQueryById
单元测试注意事项
1、一个单元测试的套件应该是针对某一个类,不要混合其他类的测试
2、一个单元测试应该尽可能地测试当前函数的逻辑,如果函数依赖了其他类或组件,应尽可能通过mock方式避开
3、单元测试的结构尽可能简单
4、每当重构一个类或函数时,应当要运行整个模块的单元测试
5、不要滥用Mockito工具,在必须要使用的时候才去使用它
6、每个单元测试应该尽可能简单,最好是一个case对应一种数据或者一个数据边界等
单元测试中使用到的代码不要设计得太复杂,可以通过复制的方式构建,然后进行一个测试数据的修改
测试覆盖率
测试覆盖率的查看能够有效了解到整个项目被测试的代码的概况
可以使用Cobertura的maven插件生成覆盖率报告,每次发布项目之前应该急需要生成一份报告出来
- 单元测试之道-JAVA
- vs2005单元测试之道
- 单元测试之道 笔记
- junit单元测试之道
- 单元测试之道
- 单元测试之道
- 单元测试系列一:单元测试之道
- 《单元测试之道》阅读笔记
- 代码整洁之道--单元测试
- 单元测试之道C#版
- 单元测试之道C#版
- Test_单元测试之道(Java)
- Java程序员修炼之道 之 单元测试
- 【Java程序员修炼之道 之 单元测试】
- 单元测试之道 -使用JUnit
- 单元测试之道(使用NUnit)
- 单元测试之道(使用NUnit)
- 单元测试之道JUnit笔记--1.Introduction
- 学习历程规划与记录-2017-06-12
- ENode 2.0
- 信用是金融市场的唯一生存法则 是企业和个人的“经济身份证”
- hdu-1878-欧拉回路-图论-并查集-java
- jstl
- 单元测试之道
- Socket下载一个文本文件
- 安卓代码规范
- 零配置部署 React
- c++ list, vector, map, set 区别与用法比较
- Python学习笔记--创建类:高级内容
- monkey初使用
- centos下安装memcached并启用SASL功能
- 普通版本mysleep和规避竞态条件的mysleep