[JAVA]单元测试:从零开始的JUnit之路(Elicpse/IDEA)

来源:互联网 发布:微信java通用版1.0精简 编辑:程序博客网 时间:2024/06/05 20:02

>什么是单元测试

在工程中,我们可能会有很多类和类的方法。单元测试就是对这些类或方法(最小软件测试单元)进行检查和验证,通过给定的测试样例进行覆盖测试。


一般情况下,很多程序员都不写单元测试,这是因为:

  • 业务逻辑太过于简单,如add(int a, int b){return a+b;},觉得没有写测试的价值;
  • 代码繁琐,如果没有Genenator进行模板的自动书写,那么编写单元测试类将耗费大量时间,耽误工程的开发进度;
  • 不会写或者懒得写;
但随着系统复杂度的提高,把所有单元的检测最终交给集成测试的做法很容易造成错误回溯的时间变长,也就是那些不起眼的小单元造成的错误可能要花费大量时间才能找到,就很烦。所以,为了减少后期维护时debug的痛苦和精力消耗,早期的单元测试是非常有必要的。


一个简单的单元测试结果如下:

从这个结果中,我们可以快速找到系统中出错的类(T3FTest)和出错的方法(Divide)以及实际测试值和期望值、每个测试单元的运行时间。


>什么是JUnit?

JUnit是当下最流行的单元测试平台。JUnit4主要基于JDK5提供的反射机制,因此需要JDK5以上;而JUnit5则结合了JDK8的lambda表达式,因此需要在JDK8以上运行。由于JUnit5只是在JUnit4的基础上加入了一些新特性以及加入、调整了核心包,对于一般用户而言,总的来说差别不大。此处我们将主要利用JUnit4进行样例演示。


1.获取JUnit和简单使用

请从这里获取JUnit的包:http://junit.org/ 。当然,本文样例所用jar包可以到这里打包下载地址:http://download.csdn.net/download/shenpibaipao/10127109

无论是Eclipse或是IDEA,导入相关的包之后,可以通过注解@Test标注一个方法,使其成为一个测试单元。如:

import org.junit.Test;public class T3F {public int add(int x,int y){return x+y;}@Testpublic void testUnit() throws Exception{System.out.println("这是一个测试单元");}public int divide(int x,int y){return x/y;}}
这样,就可以单独执行这个方法,进行一些测试,而无需给出静态main入口去测试:


2.测试类

在原型类中给每一个方法(必须是public void无参方法,暂时略过,详见下)标注@Test显然不是一个好的选择,其一是使得代码更复杂,其二是使得原系统/工程的运行变慢(注解机制消耗机能),且在工程上线发布后,并不需要把这些测试方法一起发布。


因此,在Java等OOP开发中,我们可以编写测试类,专门用于对某些类进行测试。

测试类的命名一般按以下规则处理:

  • 测试类类名用:原类名+Test
  • 测试方法名用:test+原方法名

这些命名规则是非必须的,但为了使得代码更易读,我们最好都这样命名。


对于上面给出的T3F类的add和devide方法,我们可以编写以下测试类:

package test;import org.junit.Ignore;import org.junit.Test;import org.junit.Before; import org.junit.After; /** * test.T3F Tester.* * @author <身披白袍'Blog AT http://blog.csdn.net/shenpibaipao> * @since <pre>十一月 21, 2017</pre> * @version 1.0 */ public class T3FTest { @Beforepublic void before() throws Exception {}@Afterpublic void after() throws Exception {}/**** Method: add(int x, int y)**/@Ignore("Not Ready to Run")@Testpublic void testAdd() throws Exception {//TODO: Test goes here...}/**** Method: divide(int x, int y)**/@Testpublic void testDivide() throws Exception {//TODO: Test goes here...}} 

3.自动生成测试类模板

一定有人会说了,每个方法都要写这么一大堆,烦死了。那么有没有一款插件可以自动帮我们生成这样一堆模板呢?

答案是肯定的。我们以IDEA举例:

  • 点击File-->settings-->Plguins-->Browse repositories-->查找JUnit Generator V2.0并安装-->重启IDEA

这时候,点选我们刚刚的T3F类,利用快捷键Alt+Insert或右键->Generate..,会出现以下窗口:

我们以JUnit4为例,点选该选项后,会自动创建一个test包,并在该包内建立相应的T3FTest.java,该文件的内容如上所示。


注意:打开该T3FTest.java,可能会提示你包名不对、导包不正确等问题,你可以利用alt+enter快捷键手动进行修复、import class、copy junit4 to lib。


>如何进行测试?

JUnit依赖与注解进行测试。在JUnit中测试单元注解主要有以下几种:

  • @Test 被该注解标注的方法必须是public void的(下同),标示着该方法为一个测试用例。
  • @Before 在每个@Test方法前都会先执行该@Before注解标志的方法。
  • @After 与@Before类似,只不过是在@Test方法之后执行。
  • @BeforeClass 标注在静态方法上,将在所有@Test前执行且只执行一次。
  • @AfterClass 与@BeforeClass类似,只不过在所有@Test之后执行一次。
  • @Ignore 被该注解标记的@Test方法,将暂时不进行测试。
我们来举个例子,对于:

package test;import org.junit.Ignore;import org.junit.Test;import org.junit.Before; import org.junit.After; /** * test.T3F Tester.* * @author <身披白袍'Blog AT http://blog.csdn.net/shenpibaipao> * @since <pre>十一月 21, 2017</pre> * @version 1.0 */ public class T3FTest { @Beforepublic void before() throws Exception {System.out.println("测试前");}@Afterpublic void after() throws Exception {System.out.println("测试后");}@Ignore("add被暂时无视")@Testpublic void testAdd() throws Exception {System.out.println("开始测试add()");}@Test(timeout = 1000)public void testDivide() throws Exception {System.out.println("开始测试divide()");}@Testpublic void testMain() throws Exception {System.out.println("开始测试main()");}} 
在运行该测试类后,它的输出为:


上面讲述了如何让标注一个测试单元。要让一个测试单元真正起效,我们还需要用到“断言”。在JUnit4中,断言主要有以下几种:(一些被废弃的方法我们将不再提):

  • assertEquals(expected, actual, delta) 判断两个值是否相等。其中,expected是期待值(正确值)、actual是实际值(测试值)、delta是测试允许误差。
  • assertArrayEquals(String message, Object[] expecteds, Object[] actuals) 判断两个数组是否完全相同,若不相同将返回message作为提示信息。
  • assertTrue 和 assertFalse 判断真值和假值。
  • assertNull 和 assertNotNull 方法测试一个变量是否为空或不为空(null)。
  • assertSame 和 assertNotSame 方法测试两个对象引用指向完全相同的对象(也就是比较引用是否相同)。

由于JUnit还用到了匹配器Hamcrest,可以通过assertThat等方法指定一你的检测方法。关于Hamcrest,可以查看:http://hamcrest.org/JavaHamcrest/。关于断言,还可以查看官方的Demo:https://github.com/junit-team/junit4/wiki/Assertions

断言都是org.junit.Assert类的静态方法,可以通过如org.junit.Assert.assertEquals(5, 2+3, 0);的方法调用。我们来举个例子——如何测试T3F类中的add()方法:

/**** Method: add(int x, int y)**/@Testpublic void testAdd() throws Exception {System.out.println("开始测试add()");T3F t3F = new T3F();org.junit.Assert.assertEquals(5,t3F.add(2,3),0);//一个测试数据 2+3是否等于5}
如果检测不通过,那么将会出现以下结果:



>参数化覆盖测试和套件测试

经过以上步骤,我们已经能对某一组数据和个别类分开测试了。那能不能直接进行多个类和多组数据的测试呢?


1.参数化测试

我们现在添加一个类:

public class TM {public int sub(int a,int b){return a-b;}}
并给这个类加上相应的参数化测试类:

package test;import org.junit.Test;import org.junit.Before; import org.junit.After;import org.junit.runner.RunWith;import org.junit.runners.Parameterized;import java.util.Arrays;import java.util.Collection;/*** ft.TM Tester.** @author <身披白袍'Blog AT http://blog.csdn.net/shenpibaipao>* @since <pre>十一月 21, 2017</pre>* @version 1.0*///1.标注为参数化测试类@RunWith(Parameterized.class)public class TMTest {private int expected;private int a;private int b;/**** Method: sub(int a, int b)**/@Testpublic void testSub() throws Exception {org.junit.Assert.assertEquals(expected,new TM().sub(a,b));}//2.一个构造方法,用于读取测试数据public TMTest(int expectedResult, int firstNumber, int secondNumber) {this.expected = expectedResult;this.a = firstNumber;this.b = secondNumber;}//3.一个静态的测试数据集,并标注为 @Parameters@Parameterized.Parameterspublic static Collection addedNumbers() {return Arrays.asList(new Integer[][] { { 1, 2, 1 }, { 2, 5, 3 },{ 7, 11, 4 } });}} 
测试结果为:



一个参数化测试类需要有以下要素:

  • 标注为参数化测试类:@RunWith(Parameterized.class)
  • 一个构造方法,用于读取测试数据
  • 一个静态的测试数据集,并标注为 @Parameters
2.套件测试

此时我准备同时测试TM和T3F类,该怎么办呢?

我们创建一个套件测试类:

package test;import org.junit.runner.RunWith;import org.junit.runners.Suite;@RunWith(Suite.class)@Suite.SuiteClasses({ T3FTest.class, TMTest.class })//把刚刚写好的两个测试类作为参数public class SuitTest {}
测试结果为:


>一些常见的测试要求

1.超时检测

对于某些具有运行时长要求的单元,可以标注这个单元的最长限时(单位为ms):

@Test(timeout = 1000)public void testMethod() throws Exception {//TODO: Test goes here...}

2.异常免疫

有些方法可能会抛出异常,而我们在做测试时希望对这些异常脱敏(比如在MVC开发中我们故意抛出的运行时异常),不希望这些异常打断我们的测试,我们可以这样标注我们的测试单元:

@Test(expected = YourExceptionName.class)public void testMethod() throws Exception {//TODO TEST...}



阅读全文
1 0
原创粉丝点击