博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
单元测试(Unit testing)
阅读量:5824 次
发布时间:2019-06-18

本文共 15273 字,大约阅读时间需要 50 分钟。

  有些东西尝到甜头才觉得它的好,单元测试(后续就简称ut)对我来说就是这样。不管你在做的项目是松还是紧,良好的ut都会让你事半功倍。

  UT的定义可以打开进行一下了解,文中提到的写UT的几个好处确实深有体会。

 写UT能给你带来什么?

  • Finds problems early 更早的发现bug,而不是在你所有代码都开发完成之后,在你提交测试之后。我们每写完一个功能点,完成一个接口,都要问自己一句:它有问题吗?当你无法确认的回答自己没问题的时候,就应该写一写UT了。当你的代码提交测试的时候自己心里都没有一点谱,可以说你不是一个有责任心的程序员。
  • Facilitates change 可以理解为让你能够”拥抱变化“。这里的”变化“可以是需求的变更(这是一定会发生的,不要埋怨产品经理了),自己进行的代码重构(没有UT进行重构我只能问一句谁给你的勇气)等一切会导致代码变动的东西。代码改变了,你如何尽可能保证它还是正确的呢,UT可以作为你验证代码的手段。无论代码怎么变,只要UT通过,你就可以放心的改动代码,笑对需求变更。

如何写UT?

  下面就自己实践的一些东西和大家分享下,不一定是正确的,只是我目前写UT的方式。很欢迎大家批评指正。

  编程语言java,测试框架junit+mockito,大家可以换成自己使用的测试框架。maven依赖:

junit
junit
4.11
test
org.mockito
mockito-core
1.10.19

  以一个简单的查询小米手机的service为例,来说明UT的写法。项目结构:

    

  MiOneDto:小米手机实体类

1 package com.itany.ut.dto; 2  3 import java.math.BigDecimal; 4  5 /** 6  * 小米手机 7  */ 8 public class MiOneDto { 9     //唯一标识10     private String id;11     //型号12     private String type;13     //售价14     private BigDecimal salePrice;15     //库存16     private int stockQty;17     18     public String getId() {19         return id;20     }21     public void setId(String id) {22         this.id = id;23     }24     public String getType() {25         return type;26     }27     public void setType(String type) {28         this.type = type;29     }30     public BigDecimal getSalePrice() {31         return salePrice;32     }33     public void setSalePrice(BigDecimal salePrice) {34         this.salePrice = salePrice;35     }36     public int getStockQty() {37         return stockQty;38     }39     public void setStockQty(int stockQty) {40         this.stockQty = stockQty;41     }42     @Override43     public String toString() {44         return "MiOneDto [id=" + id + ", type=" + type + ", salePrice=" + salePrice + ", stockQty=" + stockQty + "]";45     }46     47 }
MiOneDto

  MiOneDao:查询数据库接口

1 package com.itany.ut.dao;2 3 import com.itany.ut.dto.MiOneDto;4 5 public interface MiOneDao {6 7     public MiOneDto queryUniqueMiOne(String id);8 }
MiOneDao

  MiOneSalePriceService:查询价格的webservice接口

1 package com.itany.ut.remoteService;2 3 import java.math.BigDecimal;4 5 public interface MiOneSalePriceService {6 7     public BigDecimal querySalePrice(String miOneId);8 }
MiOneSalePriceService

  MiOneServiceImpl:小米手机查询service实现类

1 package com.itany.ut.service.impl; 2  3 import java.math.BigDecimal; 4  5 import com.itany.ut.dao.MiOneDao; 6 import com.itany.ut.dto.MiOneDto; 7 import com.itany.ut.remoteService.MiOneSalePriceService; 8 import com.itany.ut.service.MiOneService; 9 10 public class MiOneServiceImpl implements MiOneService{11     12     private MiOneDao miOneDao;13     14     private MiOneSalePriceService salePriceService;15     16     @Override17     public MiOneDto queryUniqueMiOne(String id) {18         MiOneDto miOneDto = miOneDao.queryUniqueMiOne(id);19         if(miOneDto != null){20             BigDecimal salePrice = salePriceService.querySalePrice(id);21             miOneDto.setSalePrice(checkPrice(salePrice));22         }23         return miOneDto;24     }25     26     private BigDecimal checkPrice(BigDecimal price){27         if(price == null || price.compareTo(BigDecimal.ZERO) < 0){28             return BigDecimal.ZERO;29         }30         return price;31     }32 33     //省略getter和setter34     35     36     37 }
MiOneServiceImpl

   下面开始编写MiOneService的的UT类MiOneServiceTest。

1 package com.itany.ut.service; 2 import static org.mockito.Matchers.*; 3 import static org.mockito.Mockito.*; 4 import static org.junit.Assert.*; 5  6 import java.math.BigDecimal; 7  8 import org.junit.Before; 9 import org.junit.Test;10 import org.mockito.Mock;11 import org.mockito.MockitoAnnotations;12 import org.mockito.Spy;13 14 import com.itany.ut.dao.MiOneDao;15 import com.itany.ut.dto.MiOneDto;16 import com.itany.ut.remoteService.MiOneSalePriceService;17 import com.itany.ut.service.impl.MiOneServiceImpl;18 19 /**20  * 查询小米手机单元测试21  */22 public class MiOneServiceTest {23 24     @Before25     public void before(){26         MockitoAnnotations.initMocks(this);27     }28     29     @Spy30     MiOneServiceImpl miOneService;31     32     @Mock33     MiOneDao miOneDao;34     35     @Mock36     MiOneSalePriceService salePriceService;37     38     public void init(){39         //使用spring @Autowired 的可以使用spring-test的工具类ReflectionTestUtils.setField进行注入40         //如果你的service用到了静态类的一些方法,是直接使用XX.xx()调用的,可以考虑在service中申明一个该类的实例,方便进行单元测试41         miOneService.setMiOneDao(miOneDao);42         miOneService.setSalePriceService(salePriceService);43     }44     45     @Test46     public void testQueryMiOne(){47         init();48         String miOneId = "001";49         50         MiOneDto miOneDto = new MiOneDto();51         miOneDto.setId("001");52         miOneDto.setType("小米3");53         miOneDto.setStockQty(10);54         //当使用 001 id 查询数据库的时候,返回一部小米3手机,库存是1055         when(miOneDao.queryUniqueMiOne(eq(miOneId))).thenReturn(miOneDto);56         //当使用 001 id查询价格的时候返回199957         when(salePriceService.querySalePrice(eq(miOneId))).thenReturn(new BigDecimal("1999"));58         //根据 001查询小米手机信息59         MiOneDto dto = miOneService.queryUniqueMiOne(miOneId);60         assertNotNull(dto);61         assertEquals(10, dto.getStockQty());62         assertEquals(miOneId,dto.getId());63         assertEquals("小米3",dto.getType());64         assertEquals(new BigDecimal("1999"),dto.getSalePrice());65         66     }67     68 }

  关于Mockio的用法大家可以自行参考官方文档 或者使用自己的UT框架实现。

  我们测试的是MiOneServiceImpl的queryUniqueMiOne(String id)方法,对于MiOneServiceImpl依赖的接口我们可以直接mock。单元测试一个很重要的一点是测试环境的封闭性,我不需要真正用dao查询数据库,真正的调用remoteService的接口来获取数据。反过来说,即使MiOneDao和MiOneSalePriceService还没有开发完成,我依然能够对MiOneServiceImpl进行单元测试。集成测试(integration)才需要测试不同系统、接口之间的交互。

  通过testQueryMiOne这个UT我们可以测试MiOneServiceImpl调用MiOneDao和MiOneSalePriceService的时候参数传递是正确的,返回值处理的是正确的。

  可能过段时间产品经理跑过来说:芃朋,我们准备举行一场优惠活动,不同型号手机有不同优惠。面对需求变更,我们需要更改现有代码,同时要增加或修改UT。

  现在新增了一个webservice接口,查询优惠金额接口MiOneFavourablePriceService,代码如下:

1 package com.itany.ut.remoteService; 2  3 import java.math.BigDecimal; 4  5 import com.itany.ut.dto.MiOneDto; 6  7 public interface MioneFavourablePriceService { 8  9     /**10      * 根据类型和售价获取优惠金额11      * 小米3,售价>=1999时,优惠200元,否则优惠0元12      * 小米4,售价>=1999是,优惠100元,否则优惠0元13      */14     public BigDecimal queryFavourablePrice(MiOneDto miOneDto);15     16 }

MiOneServiceImpl类改动如下,增加了处理优惠金额的逻辑:

1 package com.itany.ut.service.impl; 2  3 import java.math.BigDecimal; 4  5 import com.itany.ut.dao.MiOneDao; 6 import com.itany.ut.dto.MiOneDto; 7 import com.itany.ut.remoteService.MiOneSalePriceService; 8 import com.itany.ut.remoteService.MioneFavourablePriceService; 9 import com.itany.ut.service.MiOneService;10 11 public class MiOneServiceImpl implements MiOneService{12     13     private MiOneDao miOneDao;14     15     private MiOneSalePriceService salePriceService;16     17     private MioneFavourablePriceService favourablePriceService;18 19     @Override20     public MiOneDto queryUniqueMiOne(String id) {21         MiOneDto miOneDto = miOneDao.queryUniqueMiOne(id);22         if(miOneDto != null){23             BigDecimal salePrice = salePriceService.querySalePrice(id);24             miOneDto.setSalePrice(checkPrice(salePrice));25             BigDecimal favourablePrice = favourablePriceService.queryFavourablePrice(miOneDto);26             miOneDto.setSalePrice(miOneDto.getSalePrice().subtract(checkPrice(favourablePrice)));27         }28         return miOneDto;29     }30     31     private BigDecimal checkPrice(BigDecimal price){32         if(price == null || price.compareTo(BigDecimal.ZERO) < 0){33             return BigDecimal.ZERO;34         }35         return price;36     }37 38     //省略getter和setter39     40     41 }

我们在获取到销售价格的基础上,再调用MioneFavourablePriceService获取商品优惠金额,然后用销售价格减去优惠金额作为手机真正的销售金额。下面我们来看一下UT:

testQueryMiOne方法应该还是测试通过的,需要增加优惠金额的测试方法。

1 package com.itany.ut.service;  2 import static org.mockito.Matchers.*;  3 import static org.mockito.Mockito.*;  4 import static org.junit.Assert.*;  5   6 import java.math.BigDecimal;  7   8 import org.junit.Before;  9 import org.junit.Test; 10 import org.mockito.Mock; 11 import org.mockito.MockitoAnnotations; 12 import org.mockito.Spy; 13  14 import com.itany.ut.dao.MiOneDao; 15 import com.itany.ut.dto.MiOneDto; 16 import com.itany.ut.remoteService.MiOneSalePriceService; 17 import com.itany.ut.remoteService.MioneFavourablePriceService; 18 import com.itany.ut.service.impl.MiOneServiceImpl; 19  20 /** 21  * 查询小米手机单元测试 22  */ 23 public class MiOneServiceTest { 24  25     @Before 26     public void before(){ 27         MockitoAnnotations.initMocks(this); 28     } 29      30     @Spy 31     MiOneServiceImpl miOneService; 32      33     @Mock 34     MiOneDao miOneDao; 35      36     @Mock 37     MiOneSalePriceService salePriceService; 38      39     @Mock 40     MioneFavourablePriceService favourablePriceService; 41      42     public void init(){ 43         //使用spring @Autowired 的可以使用spring-test的工具类ReflectionTestUtils.setField进行注入 44         //如果你的service用到了静态类的一些方法,是直接使用XX.xx()调用的,可以考虑在service中申明一个该类的实例,方便进行单元测试 45         miOneService.setMiOneDao(miOneDao); 46         miOneService.setSalePriceService(salePriceService); 47         miOneService.setFavourablePriceService(favourablePriceService); 48     } 49     /** 50      * 无优惠 51      */ 52     @Test 53     public void testQueryMiOne(){ 54         init(); 55         String miOneId = "001"; 56          57         MiOneDto miOneDto = new MiOneDto(); 58         miOneDto.setId("001"); 59         miOneDto.setType("小米3"); 60         miOneDto.setStockQty(10); 61         //当使用 001 id 查询数据库的时候,返回一部小米3手机,库存是10 62         when(miOneDao.queryUniqueMiOne(eq(miOneId))).thenReturn(miOneDto); 63         //当使用 001 id查询价格的时候返回1999 64         when(salePriceService.querySalePrice(eq(miOneId))).thenReturn(new BigDecimal("1999")); 65         //根据 001查询小米手机信息 66         MiOneDto dto = miOneService.queryUniqueMiOne(miOneId); 67         assertNotNull(dto); 68         assertEquals(10, dto.getStockQty()); 69         assertEquals(miOneId,dto.getId()); 70         assertEquals("小米3",dto.getType()); 71         assertEquals(new BigDecimal("1999"),dto.getSalePrice()); 72          73     } 74     /** 75      * 小米3手机优惠测试 76      */ 77     @Test 78     public void testMiOne3FavourablePrice(){ 79         init(); 80         MiOneDto miOneDto1 = new MiOneDto(); 81         miOneDto1.setId("001"); 82         miOneDto1.setType("小米3"); 83         miOneDto1.setStockQty(10); 84         //当使用 001 id 查询数据库的时候,返回一部小米3手机 85         when(miOneDao.queryUniqueMiOne(eq("001"))).thenReturn(miOneDto1); 86         //当使用 001 id 查询价格的时候返回1999 87         when(salePriceService.querySalePrice(eq("001"))).thenReturn(new BigDecimal("1999")); 88          89         MiOneDto miOneDto2 = new MiOneDto(); 90         miOneDto2.setId("002"); 91         miOneDto2.setType("小米3"); 92         miOneDto2.setStockQty(10); 93         //当使用 002 id 查询数据库的时候,返回一部小米3手机 94         when(miOneDao.queryUniqueMiOne(eq("002"))).thenReturn(miOneDto2); 95         //当使用 002 id 查询价格的时候返回1600 96         when(salePriceService.querySalePrice(eq("002"))).thenReturn(new BigDecimal("1600")); 97          98         //销售金额>=1999时,返回优惠金额200 99         when(favourablePriceService.queryFavourablePrice(argThat(new org.mockito.ArgumentMatcher
(){100 101 @Override102 public boolean matches(Object argument) {103 MiOneDto dto = (MiOneDto)argument;104 if(dto != null && "小米3".equals(dto.getType()) && dto.getSalePrice().compareTo(new BigDecimal("1999")) >= 0){105 return true;106 }107 return false;108 }109 110 }))).thenReturn(new BigDecimal("200"));111 112 //根据 001查询小米手机信息113 MiOneDto dto1 = miOneService.queryUniqueMiOne("001");114 assertNotNull(dto1);115 assertEquals(10, dto1.getStockQty());116 assertEquals("001",dto1.getId());117 assertEquals("小米3",dto1.getType());118 assertEquals(new BigDecimal("1799"),dto1.getSalePrice());119 120 //根据 002查询小米手机信息121 MiOneDto dto2 = miOneService.queryUniqueMiOne("002");122 assertNotNull(dto2);123 assertEquals(10, dto2.getStockQty());124 assertEquals("002",dto2.getId());125 assertEquals("小米3",dto2.getType());126 assertEquals(new BigDecimal("1600"),dto2.getSalePrice());127 }128 129 /**130 * 小米4手机优惠测试131 */132 @Test133 public void testMiOne4FavourablePrice(){134 init();135 MiOneDto miOneDto1 = new MiOneDto();136 miOneDto1.setId("001");137 miOneDto1.setType("小米4");138 miOneDto1.setStockQty(10);139 //当使用 001 id 查询数据库的时候,返回一部小米4手机140 when(miOneDao.queryUniqueMiOne(eq("001"))).thenReturn(miOneDto1);141 //当使用 001 id 查询价格的时候返回1999142 when(salePriceService.querySalePrice(eq("001"))).thenReturn(new BigDecimal("1999"));143 144 MiOneDto miOneDto2 = new MiOneDto();145 miOneDto2.setId("002");146 miOneDto2.setType("小米4");147 miOneDto2.setStockQty(10);148 //当使用 002 id 查询数据库的时候,返回一部小米4手机149 when(miOneDao.queryUniqueMiOne(eq("002"))).thenReturn(miOneDto2);150 //当使用 002 id 查询价格的时候返回1600151 when(salePriceService.querySalePrice(eq("002"))).thenReturn(new BigDecimal("1600"));152 153 //销售金额>=1999时,返回优惠金额100154 when(favourablePriceService.queryFavourablePrice(argThat(new org.mockito.ArgumentMatcher
(){155 156 @Override157 public boolean matches(Object argument) {158 MiOneDto dto = (MiOneDto)argument;159 if(dto != null && "小米4".equals(dto.getType()) && dto.getSalePrice().compareTo(new BigDecimal("1999")) >= 0){160 return true;161 }162 return false;163 }164 165 }))).thenReturn(new BigDecimal("100"));166 167 //根据 001查询小米手机信息168 MiOneDto dto1 = miOneService.queryUniqueMiOne("001");169 assertNotNull(dto1);170 assertEquals(10, dto1.getStockQty());171 assertEquals("001",dto1.getId());172 assertEquals("小米4",dto1.getType());173 assertEquals(new BigDecimal("1899"),dto1.getSalePrice());174 175 //根据 002查询小米手机信息176 MiOneDto dto2 = miOneService.queryUniqueMiOne("002");177 assertNotNull(dto2);178 assertEquals(10, dto2.getStockQty());179 assertEquals("002",dto2.getId());180 assertEquals("小米4",dto2.getType());181 assertEquals(new BigDecimal("1600"),dto2.getSalePrice());182 }183 184 }

通过testMiOne3FavourablePrice()和testMiOne4FavourablePrice()方法,可以验证我们新增的优惠金额功能是否正确;通过testQueryMiOne()保证修改后的代码没有对之前的业务逻辑造成影响。

上面只是通过一个简单的例子说明java中UT的写法(临界值和异常测试没有包含)。UT的颗粒度是要精细到每个方法,还是到某个service服务,需要我们自己评估;面对复杂繁多的业务场景,是否要全部测试到,是否能测试到都会是我们面临的问题。总之,只有每行代码都是经过单元测试的,我们才能说编码工作完成了。

 

转载于:https://www.cnblogs.com/duanpeng/p/5136963.html

你可能感兴趣的文章
oracle系列(五)高级DBA必知的Oracle的备份与恢复(全录收集)
查看>>
hp 服务器通过串口重定向功能的使用
查看>>
国外10大IT网站和博客网站
查看>>
android第十一期 - SmoothSwitchLibrary仿IOS切换Activity动画效果
查看>>
zabbix 批量web url监控
查看>>
MongoDB CookBook读书笔记之导入导出
查看>>
shell如何快速锁定所有账号
查看>>
HTML 5实现的手机摇一摇
查看>>
Linux 文件IO理解
查看>>
Ninject 2.x细说---2.绑定和作用域
查看>>
30个非常时尚的网页联系表单设计优秀示例
查看>>
使用membership(System.Web.Security)来进行角色与权限管理
查看>>
opticom 语音质量验证白皮书
查看>>
3D实时渲染中的BSP树和多边形剔除
查看>>
Frank Klemm's Dither and Noise Shaping Page: Dither and Noise Shaping In MPC/MP+
查看>>
网络抓包的部署和工具Wireshark【图书节选】
查看>>
Redis在Windows+linux平台下的安装配置
查看>>
Maven入门实战笔记-11节[6]
查看>>
Local declaration of 'content' hides instance variable
查看>>
ASP.NET中 HTML标签总结及使用
查看>>