

单元测试是阶段性测试的首要环节,也是白盒测试的一种,该内容的编写与实践可以前置在研发完成,研发在编写业务代码的时候就需要生成对应代码的单元测试。本篇文章介绍了什么是单元测试、为什么要单元测试、单元测试的框架以及单元测试的工具。
<br></p><p><strong>作者:京东物流 秦彪</strong></p><p>(1)单元测试环节:</p><p>测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:</p><p>1) 单元测试: 针对计算机程序模块进行输出正确性检验工作。</p><p>2) 集成测试: 在单元测试基础上,整合各个模块组成子系统,进行集成测试。</p><p>3) 系统测试: 将整个交付所涉及的协作内容都纳入其中考虑,包含计算机硬件、软件、接口、操作等等一系列作为一个整体,检验是否满足软件或需求说明。</p><p>4) 验收测试: 在交付或者发布之前对所做的工作进行测试检验。</p><p>单元测试是阶段性测试的首要环节,也是白盒测试的一种,该内容的编写与实践可以前置在研发完成,研发在编写业务代码的时候就需要生成对应代码的单元测试。单元测试的发起人是程序设计者,受益人也是编写程序的人,所以对于程序员,非常有必要形成自我约束力,完成基本的单元测试用例编写。</p><p>(2)单元测试特征:</p><p>由上可知,单元测试其实是针对软件中最小的测试单元来进行验证的。这里的单元就是指相关的功能子集,比如一个方法、一个类等。值得注意的是作为最低级别的测试活动,单元测试验证的对象仅限于当前测试内容,与程序其它部分内容相隔离,总结起来单元测试有以下特征:</p><p>1) 主要功能是证明编写的代码内容与期望输出一致。</p><p>2) 最小最低级的测试内容,由程序员自身发起,保证程序基本组件正常。</p><p>3) 单元测试尽量不要区分类与方法,主张以过程性的方法为测试单位,简单实用高效为目标。</p><p>4) 不要偏离主题,专注于测试一小块的代码,保证基础功能。</p><p>5) 剥离与外部接口、存储之间的依赖,使单元测试可控。</p><p>6) 任何时间任何顺序执行单元测试都需要是成功的。</p><p>(1)单元测试意义:</p><p>程序代码都是由基本单元不断组合成复杂的系统,底层基本单元都无法保证输入输出正确性,层级递增时,问题就会不断放大,直到整个系统崩溃无法使用。所以单元测试的意义就在于保证基本功能是正常可用且稳定的。而对于接口、数据源等原因造成的不稳定因素,是外在原因,不在单元测试考虑范围之内。</p><p>(2)使用main方法进行测试:</p><div></div><p>假如要对上面的Controller进行测试,可以编写如下的代码示例,使用main方法进行测试的时候,先启动整个工程应用,然后编写main方法如下进行访问,在单步调试代码。</p><div></div><p>(3)使用main方法进行测试的缺点:</p><p>1) 通过编写大量的main方法针对每个内容做打印输出到控制台枯燥繁琐,不具备优雅性。</p><p>2) 测试方法不能一起运行,结果需要程序员自己判断正确性。</p><p>3) 统一且重复性工作应该交给工具去完成。</p><p>JUnit官网:https://junit.org/。JUnit是一个用于编写可重复测试的简单框架。它是用于单元测试框架的xUnit体系结构的一个实例。</p><p>JUnit的特点:</p><p>(1) 针对于Java语言特定设计的单元测试框架,使用非常广泛。</p><p>(2) 特定领域的标准测试框架。</p><p>(3) 能够在多种IDE开发平台使用,包含Idea、Eclipse中进行集成。</p><p>(4) 能够方便由Maven引入使用。</p><p>(5) 可以方便的编写单元测试代码,查看测试结果等。</p><p>JUnit的重要概念:</p><p>JUnit的一些注意事项及规范:</p><p>(1) 测试方法必须使用@Test 修饰</p><p>(2) 测试方法必须使用public void 进行修饰,不能带参数</p><p>(3) 测试代码的包应该和被测试代码包结构保持一致</p><p>(4) 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖</p><p>(5) 测试类一般使用 Test作为类名的后缀</p><p>(6) 测试方法使一般用test 作为方法名的前缀</p><p>JUnit失败结果说明:</p><p>(1) Failure:测试结果和预期结果不一致导致,表示测试不通过</p><p>(2) error:由异常代码引起,它可以产生于测试代码本身的错误,也可以是被测代码的Bug</p><p>(1) 断言的API</p><p>(2) JUnit常用注解:</p><p>1) @Test: 定义一个测试方法 @Test(excepted=xx.class): xx.class 表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的 @Test(timeout = 毫秒数) :测试方法执行时间是否符合预期。</p><p>2) @BeforeClass: 在所有的方法执行前被执行,static 方法全局只会执行一次,而且第一个运行。</p><p>3) @AfterClass:在所有的方法执行之后进行执行,static 方法全局只会执行一次,最后一个运行。</p><p>4) @Before:在每一个测试方法被运行前执行一次。</p><p>5) @After:在每一个测试方法运行后被执行一次。</p><p>6) @Ignore:所修饰的测试方法会被测试运行器忽略。</p><p>7) @RunWith:可以更改测试执行器使用junit测试执行器。</p><h4 id="h6">3.3.1 Controller层单元测试</h4><p>(1) Springboot中使用maven引入Junit非常简单, 使用如下依赖即可引入:</p><div></div><p>(2) 上面使用main方法案例可以使用如下的Junit代码完成:</p><div></div><p>只需要在类或者指定方法上右键执行即可,可以直接充当postman工作访问指定url,且不需要写请求代码,这些都由工具自动完成。 <br></p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb61173fd.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_测试方法_02' style="width: 796px; visibility: visible;"></p><p>(3)案例中相关组件介绍</p><p>本案例中构造mockMVC对象时,也可以使用如下方式:</p><div></div><p>其中MockMVC是Spring测试框架提供的用于REST请求的工具,是对Http请求的模拟,无需启动整个模块就可以对Controller层进行调用,速度快且不依赖网络环境。</p><p>使用MockMVC的基本步骤如下:</p><ol><li>mockMvc.perform执行请求</li><li>MockMvcRequestBuilders.post或get构造请求</li><li>MockHttpServletRequestBuilder.param或content添加请求参数</li><li>MockMvcRequestBuilders.contentType添加请求类型</li><li>MockMvcRequestBuilders.accept添加响应类型</li><li>ResultActions.andExpect添加结果断言</li><li>ResultActions.andDo添加返回结果后置处理</li><li>ResultActions.andReturn执行完成后返回相应结果</li></ol><h4 id="h7">3.3.2 Service层单元测试</h4><p>可以编写如下代码对Service层查询方法进行单测:</p><div></div><p>执行结果:</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cbda70151.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_单元测试_03' style="width: 642px; visibility: visible;"></p><h4 id="h8">3.3.3 Dao层单元测试</h4><p>可以编写如下代码对Dao层保存方法进行单测:</p><div></div><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb6117a1b.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_测试方法_04' style="width: 577px; visibility: visible;"></p><p>其中@Rollback(value = true) 可以执行单元测试之后回滚所新增的数据,保持数据库不产生脏数据。</p><h4 id="h9">3.3.4 异常测试</h4><p>(1) 在service层定义一个异常情况:</p><div></div><p>(2) 在service的测试类中定义单元测试方法:</p><div></div><p>(3) 执行单元测试也会通过,原因是@Test注解中的定义了异常</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb6117e5cb097948.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_测试方法_05' style="width: 620px; visibility: visible;"></p><h4 id="h10">3.3.5 测试套件测多个类</h4><p>(1) 新建一个空的单元测试类</p><p>(2) 利用注解@RunWith(Suite.class)和@SuiteClasses标明要一起单元测试的类</p><div></div><p>运行结果:</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb61182f8a.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_java开发如何规范地写单元测试_06' style="width: 627px; visibility: visible;"></p><h4 id="h11">3.3.6 idea中查看单元测试覆盖率</h4><p>(1) 单测覆盖率</p><p>测试覆盖率是衡量测试过程工作本身的有效性,提升测试效率和减少程序bug,提升产品可靠性与稳定性的指标。</p><p>统计单元测试覆盖率的意义:</p><p>1) 可以洞察整个代码中的基础组件功能的所有盲点,发现相关问题。</p><p>2) 提高代码质量,通常覆盖率低表示代码质量也不会太高,因为单测不通过本来就映射出考虑到各种情况不够充分。</p><p>3) 从覆盖率的达标上可以提高代码的设计能力。</p><p>(2) 在idea中查看单元测试覆盖率很简单,只需按照图中示例的图标运行,或者在单元测试方法或类上右键Run 'xxx' with Coverage即可。执行结果是一个表格,列出了类、方法、行数、分支覆盖情况。</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb61184eb.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_java开发如何规范地写单元测试_07' style="width: 597px; visibility: visible;"></p><p>(3) 在代码中会标识出覆盖情况,绿色的是已覆盖的,红色的是未覆盖的。</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb61188df.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_Test_08' style="width: 595px; visibility: visible;"></p><p>(4) 如果想要导出单元测试的覆盖率结果,可以使用如下图所示的方式,勾选 Open generated HTML in browser</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb6118c5d9b19897.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_测试方法_09' style="width: 601px; visibility: visible;"></p><p>导出结果:</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb6118e9c4c75778.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_测试方法_10' style="width: 609px; visibility: visible;"></p><h4 id="h12">3.3.7 JUnit插件自动生成单测代码</h4><p>(1) 安装插件,重启idea生效</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cba0.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_java开发如何规范地写单元测试_11' style="width: 679px; visibility: visible;"></p><p>(2) 配置插件</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cbcaf25386.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_java开发如何规范地写单元测试_12' style="width: 669px; visibility: visible;"></p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb61199fee.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_java开发如何规范地写单元测试_13' style="width: 678px; visibility: visible;"></p><p>(3) 使用插件</p><p>在需要生成单测代码的类上右键generate...,如下图所示。</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb6119c7c.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_单元测试_14' style="width: 487px; visibility: visible;"></p><p>生成结果:</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb6119ec09d11386.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_Test_15' style="width: 586px; visibility: visible;"></p><p>在单元测试过程中主张不要依赖特定的接口与数据来源,此时就涉及到对相关数据的模拟,比如Http和JDBC的返回结果等,可以使用虚拟对象即Mock对象进行模拟,使得单元测试不在耦合。</p><p>Mock过程的使用前提:</p><p>(1) 实际对象时很难被构造出来的</p><p>(2) 实际对象的特定行为很难被触发</p><p>(3) 实际对象可能当前还不存在,比如依赖的接口还没有开发完成等等。</p><p>Mockito官网:https://site.mockito.org 。Mockito和JUnit一样是专门针对Java语言的mock数据框架,它与同类的EasyMock和jMock功能非常相似,但是该工具更加简单易用。</p><p>Mockito的特点:</p><p>(1) 可以模拟类不仅仅是接口</p><p>(2) 通过注解方式简单易懂</p><p>(3) 支持顺序验证</p><p>(4) 具备参数匹配器</p><p>maven引入spring-boot-starter-test会自动将mockito引入到工程中。</p><p>(1) 在之前的代码中在定义一个BookService接口, 含义是借书接口,暂且不做实现</p><div></div><p>(2) 在之前的StudentService类中新增一个orderBook方法,含义是学生预定书籍方法,其中实现内容调用上述的BookService的orderBook方法。</p><div></div><p>(3) 编写单元测试方法,测试StudentService的orderBook方法</p><div></div><p>(4) 执行结果:</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb611a2df.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_Test_16' style="width: 796px; visibility: visible;"></p><p> (5) 结果解析<br></p><p>上述内容并没有实现BookService接口的orderBook(String name)方法。但是使用mockito进行模拟数据之后,却通过了单元测试,原因就在于Mockito替换了本来要在StudentService的orderBook方法中获取的对象,此处就模拟了该对象很难获取或当前无法获取到,用模拟数据进行替代。</p><p style="text-align:center;"><img src='https://s2.51cto.com/images/blog//_65cb611a5cac.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java开发如何规范地写单元测试 java单元测试执行顺序_单元测试_17' style="width: 796px; visibility: visible;"></p><p>常用API:</p><p>上述案例中用到了mockito的when、any、theWhen等语法。接下来介绍下都有哪些常用的API:</p><p>1) mock:模拟一个需要的对象</p><p>2) when:一般配合thenXXX一起使用,表示当执行什么操作之后怎样。</p><p>3) any: 返回一个特定对象的缺省值,上例中标识可以填写任何String类型的数据。</p><p>4) theReturn: 在执行特定操作后返回指定结果。</p><p>5) spy:创造一个监控对象。</p><p>6) verify:验证特定的行为。</p><p>7) doReturn:返回结果。</p><p>8) doThrow:抛出特定异常。</p><p>9) doAnswer:做一个自定义响应。</p><p>10) times:操作执行次数。</p><p>11) atLeastOnce:操作至少要执行一次。</p><p>12) atLeast:操作至少执行指定的次数。</p><p>13) atMost:操作至多执行指定的次数。</p><p>14) atMostOnce:操作至多执行一次。</p><p>15) doNothing:不做任何的处理。</p><p>16) doReturn:返回一个结果。</p><p>17) doThrow:抛出一个指定异常。</p><p>18) doAnswer:指定一个特定操作。</p><p>19) doCallRealMethod:用于监控对象返回一个真实结果。</p><p>(1) 打桩</p><p>Mockito中有Stub,所谓存根或者叫打桩的概念,上面案例中的Mockito.when(bookService.orderBook(any(String.class))).thenReturn(expectBook);就是打桩的含义,先定义好如果按照既定的方式调用了什么,结果就输出什么。然后在使用Book book = studentService.orderBook(""); 即按照指定存根输出指定结果。</p><div></div><p>(2) 参数匹配</p><p>上例StudentService的orderBook方法中的any(String.class) 即为参数匹配器,可以匹配任何此处定义的String类型的数据。</p><p>(3) 次数验证</p><div></div><p>(4) 顺序验证</p><div></div><p>(5) 异常验证</p><div></div><p> <br></p><p> <br></p>
讯享网

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/164272.html