赞
踩
单元测试并不只是为了验证你当前所写的代码是否存在问题,更为重要的是它可以很大程度的保障日后因业务变更、修复Bug
或重构等引起的代码变更而导致(或新增)的风险。
同时将单元测试提前到编写正式代码进行(测试驱动开发),可以很好的提高对代码结构的设计。通过优先编写测试用例,可以很好的从用户角度来对功能的分解、使用过程和接口等进行设计,从而提高代码结构的高内聚、低耦合特性。使得对日后的需求变更或代码重构等更加高效、简洁。
因此编写单元测试对产品开发和维护、技术提升和积累具有重大意义!
首先写一个单元测试,这样有助于对后面内容的理解与实践。
**IntelliJ IDEA **IntelliJ IDEA
默认自带并启用TestNG
和覆盖率插件:
在设置窗口查看TestNG插件是否安装与启用:
同样,查看覆盖率插件可以搜索“Coverage”。IntelliJ IDEA的覆盖率统计工具有三种,JaCoCo、Emma和IntelliJ IDEA自带。
同样,查看并安装变异测试插件可以搜索“PIT mutation testing”。
Eclipse
Eclipse
需要自行安装单元测试相关插件:
执行TestNG单元测试的插件。可在Eclipse Marketplace搜索“TestNG”安装:
获取单元测试覆盖率的插件。可在Eclipse Marketplace搜索“EclEmma”安装:
同样,查看并安装变异测试插件可以搜索“Pitclipse”。
- <dependency>
- <groupId>org.testng</groupId>
- <artifactId>testng</artifactId>
- <version>${testng.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.jmockit</groupId>
- <artifactId>jmockit</artifactId>
- <version>${jmockit.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.jmockit</groupId>
- <artifactId>jmockit-coverage</artifactId>
- <version>${jmockit.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>${spring.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.kubek2k</groupId>
- <artifactId>springockito</artifactId>
- <version>${springockito.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.kubek2k</groupId>
- <artifactId>springockito-annotations</artifactId>
- <version>${springockito.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.tomcat</groupId>
- <artifactId>tomcat-servlet-api</artifactId>
- <version>${tomcat.servlet.api.version}</version>
- <scope>test</scope>
- </dependency>
下面介绍通过IDE
自动创建单元测试的方法(也可手动完成):IntelliJ IDEA
Eclipse:
2.在弹出的窗口中搜索“Test”,选择“TestNG class”后点击“Next”按钮:
3.在窗口中选择要创建的测试方法后点击“Next”按钮:
4.根据自己的情况设置包名、类名和Annotations等:
示例代码
可参考下例代码编写单元测试:
- package org.light4j.unit.test;
-
- import mockit.Expectations;
- import mockit.Injectable;
- import mockit.Tested;
- import org.testng.Assert;
- import org.testng.annotations.Test;
- import wow.unit.test.remote.UserService;
- import java.util.List;
-
- /**
- * 单元测试demo
- *
- * @author jiazuo.ljz
- */
- public class BookServiceTest {
-
- /**
- * 图书持久化类,远程接口
- */
- @Injectable
- private BookDAO bookDAO;
-
- /**
- * 用户服务,远程接口
- */
- @Injectable
- private UserService userService;
-
- /**
- * 图书服务,本地接口
- */
- @Tested(availableDuringSetup = true)
- private BookService bookService;
-
- /**
- * 测试根据用户的Nick查询用户的图书列表方法
- * 其中“getUserBooksByUserNick”方法最终需要通过UserID查询DB,
- * 所以在调用此方法之前需要先对UserService类的getUserIDByNick方法进行Mock。
- */
- @Test
- public void testGetUserBooksByUserNick() throws Exception {
- new Expectations() {
- {
- userService.getUserIDByNick(anyString); // Mock接口
- result = 1234567; // Mock接口的返回值
- times = 1; // 此接口会被调用一次
- }
- };
- List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc");
- Assert.assertNotNull(bookList);
- }
- }
IntelliJ IDEA
Eclipse
注:也可点击工具栏选项运行,从左至右依次是:覆盖率、调试、运行运行。
2.点击“运行”:
左侧框:单元测试运行结果
底侧框:单元测试打印输出的内容
Maven
IntelliJ IDEA
Eclipse
2.输出报告
运行过程以及结果输出的窗口中有一行“JMockit: Coverage report written to”,是EclEmma创建的覆盖率报告文件目录:
覆盖率报告
变异测试是覆盖率的一个很好的补充。相比覆盖率,它能够使单元测试更加健壮。(具体可见5.4节)IntelliJ IDEA
3. 输出报告
运行过程以及结果输出的窗口中最后一行“Open report in browser”即为插件创建的报告连接。
点击即可打开报告:
Eclipse
2. 输出报告
可在此窗口中查看变异测试发现的可能存在的代码缺陷:(这点比IDEA的PIT插件做的要好)
可在此窗口中查看测试报告:
为今后更好的开展与落实单元测试,请继续阅读下面内容。
Junit4
和TestNG
是Java
非常流行的单元测试框架。因TestNG
更加简洁、灵活和功能丰富,所以我们选用TestNG
。
下面通过与Junit4
的比较来了解一下TestNG
的特性:
Junit4
和TestNG
的注解对比:
// TODO 测试 测试方法 测试套件 测试组 的区别
在Junit4
中,@BeforeClass
和@AfterClass
只能用于静态方法。TestNG
无此约束。
异常测试是指在单元测试中应该要抛出什么异常是合理的。
- @Test(expected = ArithmeticException.class)
- public void divisionWithException() {
- int i = 1/0;
- }
- @Test(expectedExceptions = ArithmeticException.class)
- public void divisionWithException() {
- int i = 1/0;
- }
忽略测试是指这个单元测试可以被忽略。
- @Ignore("Not Ready to Run")
- @Test
- public void divisionWithException() {
- System.out.println("Method is not ready yet");
- }
- @Test(enabled=false)
- public void divisionWithException() {
- System.out.println("Method is not ready yet");
- }
时间测试是指一个单元测试运行的时间超过了指定时间(毫秒数),那么测试将失败。
- @Test(timeout = 1000)
- public void infinity() {
- while (true);
- }
- @Test(timeOut = 1000)
- public void infinity() {
- while (true);
- }
套件测试是指把多个单元测试组合成一个模块,然后统一运行。
@RunWith
和@Suite
注解被用于执行套件测试。下面的代码是所展示的是在“JunitTest5
”被执行之后需要“JunitTest1
”和“JunitTest2
”也一起执行。所有的声明需要在类内部完成。
java
- @RunWith(Suite.class) @Suite.SuiteClasses({JunitTest1.class, JunitTest2.class})
- public class JunitTest5 {
是使用XML
配置文件来执行套件测试。下面的配置将“TestNGTest1
”和“TestNGTest2
”一起执行。
- <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
- <suite name="My test suite">
- <test name="testing">
- <classes>
- <class name="com.fsecure.demo.testng.TestNGTest1" />
- <class name="com.fsecure.demo.testng.TestNGTest2" />
- </classes>
- </test>
- </suite>
TestNG
的另一种方式使用了组的概念,每个测试方法都可以根据功能特性分配到一个组里面。例如:
- @Test(groups="method1")
- public void testingMethod1() {
- System.out.println("Method - testingMethod1()");
- }
- @Test(groups="method2")
- public void testingMethod2() {
- System.out.println("Method - testingMethod2()");
- }
- @Test(groups="method1")
- public void testingMethod1_1() {
- System.out.println("Method - testingMethod1_1()");
- }
- @Test(groups="method4")
- public void testingMethod4() {
- System.out.println("Method - testingMethod4()");
- }
这是一个有4个方法,3个组(method1, method2 和 method4)的类。使用起来比XML的套件更简洁。
下面XML
文件配置了一个执行组为methed1
的单元测试。
- <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
- <suite name="My test suite">
- <test name="testing">
- <groups>
- <run>
- <include name="method1"/>
- </run>
- </groups>
- <classes>
- <class name="com.fsecure.demo.testng.TestNGTest5_2_0" />
- </classes>
- </test>
- </suite>
分组使集成测试更加强大。例如,我们可以只是执行所有测试中的组名为DatabaseFuntion
的测试。
参数化测试是指给单元测试传多种参数值,验证接口对多种不同参数的处理是否正确。
@RunWith
和@Parameter
注解用于为单元测试提供参数值,@Parameters
必须返回List
,参数将会被作为参数传给类的构造函数。
- @RunWith(value = Parameterized.class)
- public class JunitTest6 {
- private int number;
- public JunitTest6(int number) {
- this.number = number;
- }
- @Parameters
- public static Collection<Object[]> data() {
- Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 }, { 4 } };
- return Arrays.asList(data);
- }
- @Test
- public void pushTest() {
- System.out.println("Parameterized Number is : " + number);
- }
- }
它的使用很不方便:一个方法的参数化测试必须定义一个测试类。测试参数通过一个注解为@Parameters且返回值为List参数值列表的静态方法。然后将方法返回值成员通过类的构造函数初始化为类的成员。最后再将类的成员做为参数去测试被测试方法。
使用XML
文件或@DataProvider注解两种方式为测试提供参数。
XML
文件配置参数化测试
方法上添加@Parameters注解,参数数据由TestNG的XML配置文件提供。这样做之后,我们可以使用不同的数据集甚至是不同的结果集来重用一个测试用例。另外,甚至是最终用户,QA或者QE可以提供他们自己的XML文件来做测试。
- public class TestNGTest6_1_0 {
- @Test
- @Parameters(value="number")
- public void parameterIntTest(int number) {
- System.out.println("Parameterized Number is : " + number);
- }
- }
XML 文件
- <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
- <suite name="My test suite">
- <test name="testing">
- <parameter name="number" value="2"/>
- <classes>
- <class name="com.fsecure.demo.testng.TestNGTest6_0" />
- </classes>
- </test>
- </suite>
@DataProvider注解参数化测试
使用XML
文件初始化数据虽然方便,但仅支持基础数据类型。如需复杂的类型可使用@DataProvider
注解解决。
- @Test(dataProvider = "Data-Provider-Function")
- public void parameterIntTest(Class clzz, String[] number) {
- System.out.println("Parameterized Number is : " + number[0]);
- System.out.println("Parameterized Number is : " + number[1]);
- }
- //This function will provide the patameter data
- @DataProvider(name = "Data-Provider-Function")
- public Object[][] parameterIntTestProvider() {
- return new Object[][]{
- {Vector.class, new String[]{"java.util.AbstractList", "java.util.AbstractCollection"}},
- {String.class, new String[] {"1", "2"}},
- {Integer.class, new String[] {"1", "2"}}
- };
- }
@DataProvider作为对象的参数
P.S “TestNGTest6_3_0” 是一个简单的对象,使用了get和set方法。
- @Test(dataProvider = "Data-Provider-Function")
- public void parameterIntTest(TestNGTest6_3_0 clzz) {
- System.out.println("Parameterized Number is : " + clzz.getMsg());
- System.out.println("Parameterized Number is : " + clzz.getNumber());
- }
- //This function will provide the patameter data
- @DataProvider(name = "Data-Provider-Function")
- public Object[][] parameterIntTestProvider() {
- TestNGTest6_3_0 obj = new TestNGTest6_3_0();
- obj.setMsg("Hello");
- obj.setNumber(123);
- return new Object[][]{{obj}};
- }
TestNG的参数化测试使用起来非常方便,它可以在一个测试类中添加多个方法的参数化测试(JUnit4一个方法就需要一个类)。
依赖测试是指测试的方法是有依赖的,在执行的测试之前需要执行的另一测试。如果依赖的测试出现错误,所有的子测试都被忽略,且不会被标记为失败。
JUnit4框架主要聚焦于测试的隔离,暂时还不支持这个特性。
它使用dependOnMethods来实现了依赖测试的功能,如下:
- @Test
- public void method1() {
- System.out.println("This is method 1");
- }
- @Test(dependsOnMethods={"method1"})
- public void method2() {
- System.out.println("This is method 2");
- }
如果method1()成功执行,那么method2()也将被执行,否则method2()将会被忽略。
TestNG
支持通过多个线程并发调用一个测试接口来实现性能测试。JUnit4
不支持,若要进行性能测试需手动添加并发代码。
- @Test(invocationCount=1000, threadPoolSize=5, timeOut=100)
- public void perfMethod() {
- System.out.println("This is perfMethod");
- }
TestNG
支持通过多个线程并发调用多个测试接口执行测试,相对于传统的单线程执行测试的方式,可以很大程度减少测试运行时间。
- public class ConcurrencyTest {
- @Test
- public void method1() {
- System.out.println("This is method 1");
- }
- @Test
- public void method2() {
- System.out.println("This is method 2");
- }
- }
并行测试配置:
- <suite name="Concurrency Suite" parallel="methods" thread-count="2" >
- <test name="Concurrency Test" group-by-instances="true">
- <classes>
- <class name="wow.unit.test.ConcurrencyTest" />
- </classes>
- </test>
- </suite>
通过上面的对比,建议使用TestNG作为Java项目的单元测试框架,因为TestNG在参数化测试、依赖测试以、套件测试(组)及并发测试方面功能更加简洁、强大。另外,TestNG也涵盖了JUnit4的全部功能。
比如Mock以下场景:
1. 外部依赖的应用的调用,比如WebService等服务依赖。
2. DAO层(访问MySQL、Oracle、Emcache等底层存储)的调用等。
3. 系统间异步交互通知消息。
4. methodA里面调用到的methodB。
5. 一些应用里面自己的Class(abstract,final,static)、Interface、Annotation、Enum和Native等。
Mock
工具工作的原理大都如下:
1. Record阶段:录制期望。也可以理解为数据准备阶段。创建依赖的Class或Interface或Method,模拟返回的数据、耗时及调用的次数等。
2. Replay阶段:通过调用被测代码,执行测试。期间会Invoke到第一阶段Record的Mock对象或方法。
3. Verify阶段:验证。可以验证调用返回是否正确,及Mock的方法调用次数,顺序等。
历史曾经或当前比较流行的Mock工具有EasyMock
、jMock
、Mockito
、Unitils Mock
、PowerMock
、JMockit
等工具。
从这里可以看到,JMockit
的的功能最全面、强大!所以我们单元测试中的Mock工具也选择了JMockit。同时在开发的过程中,JMockit的“Auto-injection of mocks”及“Special fields for “any” argument matching”及各种有用的Annotation使单元测试的开发更简洁和高效。
JMockit
是用以帮助开发人员编写单元测试的Mock
工具。它基于java.lang.instrument包开发,并使用ASM库来修改Java的Bytecode。正因此两点,它可以实现无所不能的Mock。
JMockit可以Mock的种类包含了:
JMockit有两种Mock的方式:
通俗点讲,Behavior-oriented是基于行为的Mock,对Mock目标代码的行为进行模仿,像是黑盒测试。State-oriented是基于状态的Mock,是站在目标测试代码内部的。可以对传入的参数进行检查、匹配,才返回某些结果,类似白盒。而State-oriented的new MockUp基本上可以Mock任何代码或逻辑。
以下是JMockit的APIs和tools:
可以看到JMockit常用的Expectation、StrictExpectations和NonStrictExpectations期望录制及注解@Tested、@Mocked,@NonStrict、@Injectable等简洁的Mock代码风格。而且JMockit还自带了Code Coverage的工具供本地单元测试时候逻辑覆盖或代码覆盖率使用。
以“第一个单元测试”代码为例:
@Tested:JMockit会自动创建注解为“@Tested”的类对象,并将其做为被测试对象。 通过设置“availableDuringSetup=true”参数,可以使得被测试对象在“setUp”方法执行前被创建出来。
- @Tested(availableDuringSetup = true)
- private BookService bookService;
@Injectable:JMockit自动创建注解为“@Injectable”的类对象,并将其自动注入被测试对象。
- @Injectable
- private BookDAO bookDAO;
- @Injectable
- private UserService userService;
相关的注解还有:// TODO 待补充
Expectations:块里的内容是用来Mock方法,并指定方法的返回值、异常、调用次数和耗时。此块中的方法是必须被执行的,否则单元测试失败。
- /**
- * 测试根据用户的Nick查询用户的图书列表方法
- * 其中“getUserBooksByUserNick”方法最终需要通过UserId查询DB,
- * 所以在调用此方法之前需要先对UserService类的getUserIdByNick方法进行Mock。
- */
- @Test
- public void testGetUserBooksByUserNick() throws Exception {
- new Expectations() {
- {
- userService.getUserIdByNick(anyString);
- result = 1234567;
- times = 1;
- }
- };
- List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc");
- Assert.assertNotNull(bookList);
- }
相关的类还有:
Assert:是最常见的断言验证
Assert.assertNotNull(bookList);
Verifications:一种特殊的验证块。比如:要验证一个被测试类中,调用的某个方法是否为指定的参数、调用次数。相比Expectations它放在单元测试的最后且没有Mock功能。
注:以上列举的注释具体用法示例请查阅第7节内容
在单元测试时,测试人员根据设计文档和源码,了解模块的接口和逻辑结构。主要采用白盒测试用例,辅之黑盒测试用例,使之对任何(合理和不合理)的输入都要能鉴别和响应。这就要求对程序所有的局部和全局的数据结构、外部接口和程序代码的关键部分进行检查。
在单元测试中主要在5个方面对被测模块进行检查。
在单元测试开始时,应该对所有被测模块的接口进行测试。如果数据不能正常地输入和输出,那么其他的测试毫无意义。Myers在关于软件测试的书中为接口测试提出了一个检查表:
当模块通过外部设备进行输入/输出操作时,必须扩展接口测试,附加如下的测试项目:
模块的局部数据结构是最常见的错误来源,应设计测试用例以检查以下各种错误:
检查由于计算、判定和控制流错误而导致的程序错误。由于在测试时不可能做到穷举测试,所以在单元测试时要根据“白盒”测试和“黑盒”测试用例的设计方法设计测试用例,对模块中重要的执行路径进行测试。重要的执行路径是通常指那些处在具体实现的算法、控制、数据处理等重要位置的路径,也可指较复杂而容易出错的路径。尽可能地对执行路径进行测试非常重要,需要设计因错误的计算、比较或控制流而导致错误的测试用例。此外,对基本执行路径和循环进行测试也可发现大量的路径错误。
在路径测试中,要检查的错误有:死代码、错误的计算优先级、算法错误、混用不同类的操作、初始化不正确、精度错误——比较运算错误、赋值错误、表达式的不正确符号——>、>=;=、==、!=和循环变量的使用错误——错误赋值以及其他错误等。
比较操作和控制流向紧密相关,测试用例设计需要注意发现比较操作的错误:
错误处理路径是指可能出现错误的路径以及进行错误处理的路径。当出现错误时会执行错误处理代码,或通知用户处理,或停止执行并使程序进入一种安全等待状态。测试人员应意识到,每一行程序代码都可能执行到,不能自认为错误发生的概率很小而不进行测试。一般软件错误处理测试应考虑下面几种可能的错误:
在进行错误处理测试时,要检查如下内容:
边界测试是单元测试中最后的任务。代码常常在边界上出错,比如:在代码段中有一个n次循环,当到达第n次循环时就可能会出错;或者在一个有n个元素的数组中,访问第n个元素时是很容易出错的。因此,要特别注意数据流、控制流中刚好等于、大于或小于确定的比较值时可能会出现的错误。对这些地方需要仔细地认真加以测试。
此外,如果对模块性能有要求的话,还要专门对关键路径进行性能测试。以确定最坏情况下和平均意义下影响运行时间的因素。下面是边界测试的具体要检查的内容:
第4部分概括的列举了需要测试的5大点内容,此处为服务端代码层至少要包含或覆盖的测试内容。Service
HTTP接口
HSF接口
工具类
为了使单元测试能充分细致地展开,应在实施单元测试中遵守下述要求:
单元测试通常是由编写程序的人自己完成的,但是项目负责人应当关心测试的结果。所有的测试用例和测试结果都是模块开发的重要资料,需妥善保存。
测试覆盖方法的确可以帮我们找到一些显而易见的代码冗余或者测试遗漏的问题。不过,实践证明,这些传统的方法只能非常有限的发现测试中的问题。很多代码和测试的问题在覆盖达到100%的情况下也无法发现。然而,“代码变异测试”这种方法可以很好的弥补传统方法的缺点,产生更加有效的单元测试。
代码变异测试是通过对代码产生“变异”来帮助我们改进单元测试的。“变异”指的是修改一处代码来改变代码行为(当然保证语法的合理性)。简单来说,代码变异测试先试着对代码产生这样的变异,然后运行单元测试,并检查是否有测试是因为这个代码变异而失败。如果失败,那么说明这个变异被“消灭”了,这是我们期望看到的结果。否则说明这个变异“存活”了下来,这种情况下我们就需要去研究一下“为什么”了。
总而言之,测试覆盖这种方法是一种不错的保障单元测试质量的手段。代码变异测试则比传统的测试覆盖方法可以更加有效的发现代码和测试中潜在的问题,它可以使单元测试更加强壮。
省略
Service
层单元测试示例。普通Mock测试:
- /**
- * 测试根据用户的Nick查询用户的图书列表方法
- * 其中“userService.getUserBooksByUserNick”方法最终需要通过UserId查询DB,
- * 所以在调用此方法之前需要先对UserService类的getUserIdByNick方法进行Mock。
- * 其中“bookDAO.getUserBooksByUserId”方法最终需要通过UserId查询DB,
- * 所以在调用此方法之前需要先对BookDAO类的getUserBooksByUserId方法进行Mock。
- */
- @Test
- public void testGetUserBooksByUserNick4Success() throws Exception {
- final List<BookDO> bookList = new ArrayList<BookDO>();
- bookList.add(new BookDO());
- new Expectations() {
- {
- userService.getUserIdByNick(anyString); // Mock的接口
- result = 1234567; // 接口返回值
- times = 1; // 接口被调用的次数
-
- bookDAO.getUserBooksByUserId(anyLong);
- result = bookList;
- times = 1;
- }
- };
- List<BookDO> resultBookList = bookService.getUserBooksByUserNick("moyuan.jcc");
- Assert.assertNotNull(resultBookList);
- }
2.错误(异常)处理:
- /**
- * 测试根据用户的Nick查询用户的图书列表方法,注意在@Test添加expectedExceptions参数
- * 验证其中“userService.getUserBooksByUserNick”接口出现异常时,对异常的处理是否符合预期.
- * 其中“bookDAO.getUserBooksByUserId”方法不会被调用到。
- */
- @Test(expectedExceptions = {RuntimeException.class})
- public void testGetUserBooksByUserNick4Exception() throws Exception {
- final List<BookDO> bookList = new ArrayList<BookDO>();
- bookList.add(new BookDO());
- new Expectations() {
- {
- userService.getUserIdByNick(anyString); // Mock的接口
- result = new RuntimeException("exception unit test"); // 接口抛出异常
- times = 1; // 接口被调用的次数
-
- bookDAO.getUserBooksByUserId(anyLong);
- result = bookList;
- times = 0; // 上面接口出现异常后,此接口不会被调用
- }
- };
- List<BookDO> resultBookList = bookService.getUserBooksByUserNick("moyuan.jcc");
- Assert.assertNotNull(resultBookList);
- }
3. Mock具体方法实现:
- /**
- * 测试发送离线消息方法
- * 消息队列:当离线消息超过100条时,删除最旧1条,添加最新一条。
- * 但消息存在DB或Tair中,所以需要Mock消息的存储。
- */
- @Test
- public void testAddOffLineMsg() throws Exception {
- final Map<Long, MsgDO> msgCache = new ArrayList<Long, MsgDO>();
- new Expectations() {
- {
- new MockUp<BookDAO>() {
- @Mock
- public void addMsgByUserId(long userId, MsgDO msgDO) {
- msgCache.put(userId, msgDO);
- }
- };
- new MockUp<BookDAO>() {
- @Mock
- public List<MsgDO> getUserBooksByUserId(long userId) {
- return msgCache.get(userId);
- }
- };
- }
- };
-
- final int testAddMsgCount = 102;
- for(int i = 0; i < testAddMsgCount; i++) {
- msgService.addMsgByUserId(123L, new MsgDO(new Date(), "this is msg" + i));
- }
- List<MsgDO> msgList = msgService.getMsgByUserId(123L);
- Assert.assertTrue(msgList.size() == 100);
-
- new Verifications() {
- {
- // 验证 addMsgByUserId 接口是否被调用了100次
- MsgDAO.addMsgByUserId(anyLong, withInstanceOf(MsgDO.class));
- times = testAddMsgCount;
- // 验证是否对消息内容进行相就次数的转义
- SecurityUtil.escapeHtml(anyString);
- times = testAddMsgCount;
- }
- };
- }
HTTP
接口单元测试示例。1. Spring MVC Controller
- public final class BookControllerTest {
-
- @Tested(availableDuringSetup = true)
- private BookController bookController;
-
- @Injectable
- private BookService bookService;
-
- private MockMvc mockMvc;
-
- @BeforeMethod
- public void setUp() throws Exception {
- this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
- }
-
- /**
- *<strong> </strong>********************************
- * getBookList unit test
- *<strong> </strong>********************************
- */
- @Test
- public void testgetBookList4Success() throws Exception {
- new StrictExpectations() {
- {
- new MockUp<CookieUtil>(){
- @Mock
- public boolean isLogined(){
- return true;
- }
- };
- userService.getUserBooksByUserNick(anyString);
- result = null;
- times = 1;
- }
- };
- ResultActions httpResult = this.mockMvc.perform(get("/education/getBookList.do?userNick=hello"))
- .andDo(print()).andExpect(status().isOk());
- MockHttpServletResponse response = httpResult.andReturn().getResponse();
- String responseStr = response.getContentAsString();
- // 如果存在多版本客户端的情况下,注意返回值向后兼容,此处需要多种格式验证.
- Assert.assertEquals(responseStr, "{"code":1,"msg":"success","data":""}");
- }
- }
2. 参数化测试
- @DataProvider(name = "getBookListParameterProvider")
- public Object[][] getBookListParameterProvider() {
- return new String[][]{
- {"hello", "{"code":1,"msg":"success","data":""}"},
- {"123", "{"code":301,"msg":"parameter error","data":""}"}
- };
- }
- @Test(dataProvider = "getBookListParameterProvider")
- public void testgetBookList4Success(String nick ,String resultCheck) throws Exception {
- new StrictExpectations() {
- {
- new MockUp<CookieUtil>() {
- @Mock
- public boolean isLogined() {
- return true;
- }
- };
- userService.getUserBooksByUserNick(anyString);
- result = null;
- times = 1;
- }
- };
- ResultActions httpResult = this.mockMvc.perform(get("/education/getBookList.do?userNick=" + nick))
- .andDo(print()).andExpect(status().isOk());
- MockHttpServletResponse response = httpResult.andReturn().getResponse();
- String responseStr = response.getContentAsString();
- // 如果存在多版本客户端的情况下,注意返回值向后兼容,此处需要多种格式验证.
- Assert.assertEquals(responseStr, resultCheck);
- }
静态工具类测试示例。1. 静态方法:
java @Test public void testMethod() { new StrictExpectations(CookieUtil) { { CookieUtil.isLogined(); result =
或
java @Test public void testMethod() { new MockUp<CookieUtil>(){ @Mock public boolean isLogined(){ return true;
单元测试永远无法证明代码的正确性!!
一个跑失败的测试可能表明代码有错误,但一个跑成功的测试什么也证明不了。
单元测试最有效的使用场合是在一个较低的层级验证并文档化需求,以及回归测试:开发或重构代码,不会破坏已有功能的正确性。
以上内容就是本篇的全部内容以上内容希望对你有帮助,有被帮助到的朋友欢迎点赞,评论。
如果对软件测试、接口测试、自动化测试、面试经验交流。感兴趣可以关注我的主页,会有同行一起技术交流哦。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。