标签:
单元测试是编写测试代码,用来检测特定的、明确的、细颗粒的功能。单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的。
单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复、改进或重构之后的正确性。
一般来说,单元测试任务包括
- 接口功能测试:用来保证接口功能的正确性。
- 局部数据结构测试(不常用):用来保证接口中的数据结构是正确的
- 比如变量有无初始值
- 变量是否溢出
- 边界条件测试
- 变量没有赋值(即为NULL)
- 变量是数值(或字符)
- 主要边界:最小值,最大值,无穷大(对于DOUBLE等)
- 溢出边界(期望异常或拒绝服务):最小值-1,最大值+1
- 临近边界:最小值+1,最大值-1
- 变量是字符串
- 引用“字符变量”的边界
- 空字符串
- 对字符串长度应用“数值变量”的边界
- 变量是集合
- 空集合
- 对集合的大小应用“数值变量”的边界
- 调整次序:升序、降序
- 变量有规律
- 比如对于Math.sqrt,给出n^2-1,和n^2+1的边界
- 所有独立执行通路测试:保证每一条代码,每个分支都经过测试
- 代码覆盖率
- 语句覆盖:保证每一个语句都执行到了
- 判定覆盖(分支覆盖):保证每一个分支都执行到
- 条件覆盖:保证每一个条件都覆盖到true和false(即if、while中的条件语句)
- 路径覆盖:保证每一个路径都覆盖到
- 相关软件
- Cobertura:语句覆盖
- Emma: Eclipse插件Eclemma
- 各条错误处理通路测试:保证每一个异常都经过测试
JUNIT
JUnit是Java单元测试框架,已经在Eclipse中默认安装。目前主流的有JUnit3和JUnit4。JUnit3中,测试用例需要继承TestCase
类。JUnit4中,测试用例无需继承TestCase
类,只需要使用@Test
等注解。
JUnit4
与JUnit3不同,JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有:
@BeforeClass
全局只会执行一次,而且是第一个运行
@Before
在测试方法运行之前运行
@Test
测试方法
@After
在测试方法运行之后允许
@AfterClass
全局只会执行一次,而且是最后一个运行
@Ignore
忽略此方法
下面举一个样例:
- import org.junit.After;
- import org.junit.AfterClass;
- import org.junit.Assert;
- import org.junit.Before;
- import org.junit.BeforeClass;
- import org.junit.Ignore;
- import org.junit.Test;
-
- public class Junit4TestCase {
-
- @BeforeClass
- public static void setUpBeforeClass() {
- System.out.println("Set up before class");
- }
-
- @Before
- public void setUp() throws Exception {
- System.out.println("Set up");
- }
-
- @Test
- public void testMathPow() {
- System.out.println("Test Math.pow");
- Assert.assertEquals(4.0, Math.pow(2.0, 2.0), 0.0);
- }
-
- @Test
- public void testMathMin() {
- System.out.println("Test Math.min");
- Assert.assertEquals(2.0, Math.min(2.0, 4.0), 0.0);
- }
-
-
- @Test(expected = NullPointerException.class)
- public void testException() {
- System.out.println("Test exception");
- Object obj = null;
- obj.toString();
- }
-
-
- @Ignore
- @Test
- public void testMathMax() {
- Assert.fail("没有实现");
- }
-
- @Test
- public void testAssume(){
- System.out.println("Test assume");
-
- Assume.assumeTrue(false);
- Assert.fail("没有实现");
- }
-
- @After
- public void tearDown() throws Exception {
- System.out.println("Tear down");
- }
-
- @AfterClass
- public static void tearDownAfterClass() {
- System.out.println("Tear down After class");
- }
-
- }
如果细心的话,会发现Junit3的package是junit.framework
,而Junit4是org.junit
。
执行此用例后,控制台会输出
写道
Set up before class
Set up
Test Math.pow
Tear down
Set up
Test Math.min
Tear down
Set up
Test exception
Tear down
Set up
Test assume
Tear down
Tear down After class
可以看到,执行次序是@BeforeClass
-> @Before
-> @Test
-> @After
-> @Before
-> @Test
-> @After
-> @AfterClass
。@Ignore
会被忽略。
运行测试方法
与Junit3类似,可以在Eclipse中运行,也可以通过mvn test
命令运行。
Assert
Junit3和Junit4都提供了一个Assert类(虽然package不同,但是大致差不多)。Assert类中定义了很多静态方法来进行断言。列表如下:
- assertTrue(String message, boolean condition) 要求condition == true
- assertFalse(String message, boolean condition) 要求condition == false
- fail(String message) 必然失败,同样要求代码不可达
- assertEquals(String message, XXX expected,XXX actual) 要求expected.equals(actual)
- assertArrayEquals(String message, XXX[] expecteds,XXX [] actuals) 要求expected.equalsArray(actual)
- assertNotNull(String message, Object object) 要求object!=null
- assertNull(String message, Object object) 要求object==null
- assertSame(String message, Object expected, Object actual) 要求expected == actual
- assertNotSame(String message, Object unexpected,Object actual) 要求expected != actual
- assertThat(String reason, T actual, Matcher matcher) 要求matcher.matches(actual) == true
Mock/Stub
Mock和Stub是两种测试代码功能的方法。Mock测重于对功能的模拟。Stub测重于对功能的测试重现。比如对于List接口,Mock会直接对List进行模拟,而Stub会新建一个实现了List的TestList,在其中编写测试的代码。
强烈建议优先选择Mock方式,因为Mock方式下,模拟代码与测试代码放在一起,易读性好,而且扩展性、灵活性都比Stub好。
比较流行的Mock有:
其中EasyMock和Mockito对于Java接口使用接口代理的方式来模拟,对于Java类使用继承的方式来模拟(也即会创建一个新的Class类)。Mockito支持spy方式,可以对实例进行模拟。但它们都不能对静态方法和final类进行模拟,powermock通过修改字节码来支持了此功能。
EasyMock
IBM上有几篇介绍EasyMock使用方法和原理的文章:EasyMock 使用方法与原理剖析,使用 EasyMock 更轻松地进行测试。
EasyMock把测试过程分为三步:录制、运行测试代码、验证期望。
录制过程大概就是:期望method(params)执行times次(默认一次),返回result(可选),抛出exception异常(可选)。
验证期望过程将会检查方法的调用次数。
一个简单的样例是:
- @Test
- public void testListInEasyMock() {
- List list = EasyMock.createMock(List.class);
-
-
-
- expect1: EasyMock.expect(list.set(0, 1)).andReturn(null).times(2);
-
- expect2: EasyMock.expect(list.set(0, 1)).andReturn(1);
-
-
- EasyMock.replay(list);
-
- Assert.assertNull(list.set(0, 1));
-
- Assert.assertNull(list.set(0, 1));
-
- Assert.assertEquals(1, list.set(0, 1));
-
-
- EasyMock.verify(list);
- }
EasyMock还支持严格的检查,要求执行的方法次序与期望的完全一致。
Mockito
Mockito是Google Code上的一个开源项目,Api相对于EasyMock更好友好。与EasyMock不同的是,Mockito没有录制过程,只需要在“运行测试代码”之前对接口进行Stub,也即设置方法的返回值或抛出的异常,然后直接运行测试代码,运行期间调用Mock的方法,会返回预先设置的返回值或抛出异常,最后再对测试代码进行验证。可以查看此文章了解两者的不同。
官方提供了很多样例,基本上包括了所有功能,可以去看看。
这里从官方样例中摘录几个典型的:
- 验证调用行为
- import static org.mockito.Mockito.*;
-
- List mockedList = mock(List.class);
-
- mockedList.add("one");
- mockedList.clear();
-
- verify(mockedList).add("one");
- verify(mockedList).clear();
- 对Mock对象进行Stub
- LinkedList mockedList = mock(LinkedList.class);
-
- when(mockedList.get(0)).thenReturn("first");
- when(mockedList.get(1)).thenThrow(new RuntimeException());
-
- System.out.println(mockedList.get(0));
-
- System.out.println(mockedList.get(1));
-
- System.out.println(mockedList.get(999));
-
- verify(mockedList).get(0);
代码覆盖率
比较流行的工具是Emma和Jacoco,Ecliplse插件有eclemma。eclemma2.0之前采用的是Emma,之后采用的是Jacoco。这里主要介绍一下Jacoco。Eclmama由于是Eclipse插件,所以非常易用,就不多做介绍了。
Jacoco
Jacoco可以嵌入到Ant、Maven中,也可以使用Java Agent技术监控任意Java程序,也可以使用Java Api来定制功能。
Jacoco会监控JVM中的调用,生成监控结果(默认保存在jacoco.exec文件中),然后分析此结果,配合源代码生成覆盖率报告。需要注意的是:监控和分析这两步,必须使用相同的Class文件,否则由于Class不同,而无法定位到具体的方法,导致覆盖率均为0%。
Java Agent嵌入
首先,需要下载jacocoagent.jar文件,然后在Java程序启动参数后面加上 -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]
,具体的options可以在此页面找到。默认会在JVM关闭时(注意不能是kill -9
),输出监控结果到jacoco.exec文件中,也可以通过socket来实时地输出监控报告(可以在Example代码中找到简单实现)。
Java Report
可以使用Ant、Mvn或Eclipse来分析jacoco.exec文件,也可以通过API来分析。
- public void createReport() throws Exception {
-
- final FileInputStream fis = new FileInputStream(new File("jacoco.exec"));
- final ExecutionDataReader executionDataReader = new ExecutionDataReader(fis);
-
- ExecutionDataStore executionDataStore = new ExecutionDataStore();
-
- SessionInfoStore sessionInfoStore = new SessionInfoStore();
-
- executionDataReader.setExecutionDataVisitor(executionDataStore);
- executionDataReader.setSessionInfoVisitor(sessionInfoStore);
-
- while (executionDataReader.read()) {
- }
-
- fis.close();
-
-
- final CoverageBuilder coverageBuilder = new CoverageBuilder();
- final Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder);
-
-
- File classesDirectory = new File("classes");
- analyzer.analyzeAll(classesDirectory);
-
- IBundleCoverage bundleCoverage = coverageBuilder.getBundle("Title");
-
- File reportDirectory = new File("report");
- final HTMLFormatter htmlFormatter = new HTMLFormatter();
- final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(reportDirectory));
-
- visitor.visitInfo(sessionInfoStore.getInfos(), executionDataStore.getContents());
- File sourceDirectory = new File("src");
-
-
- visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(sourceDirectory, "utf-8", 4));
-
- visitor.visitEnd();
- }
Java单元测试(Junit+Mock+代码覆盖率)
标签:
原文地址:http://www.cnblogs.com/hushaojun/p/4308735.html