使用 JUnit 进行 Java 代码的单元测试

下载安装 JUnit 的相关文件

  首先我们需要先下载相应的 JUnit 相关的 JAR 包,下载的过程可以去 JUnit 的官方网站,也可以直接通过 Maven 资源仓库来完成,我这里直接通过开源中国社区在国内的Maven 镜像下载了 JUnit-4.8.2.jar 的版本,如下图所示:

  直接搜索关键字"junit"即可,我们可以从搜索结果中找到红色方框1中的对应项,选中之后就会在左下方列出目前可以获得的 junit  的所有版本,这里我选择了 4.8.2 的版本(红色方框2),然后点击右侧的 Download 按钮即可下载这個 JAR 包,不过依我在实践中的经验,最好同时下载对应版本的 javadoc 和 source 两個包,前者是文档,后者是对应的源代码,然后将它们直接引入到我们的 eclipse 的工程中即可。我这里创建了壹個普通的 Java Project ,并给它取名 junit-study ,导入 JAR 包之后目前的样子如下图所示:

  使用简单的 @Test 注解实现我们的测试方法的编写和执行

  准备工作做好之后,接下来我们就可以开始尝试编写壹個简单的测试代码了。首先,我们编写了壹個 Calculator 类,并提供五個方法分别完成加减乘除以及求平方的运算。代码如下:


package net.oschina.bairrfhoinn.main;

public class Calculator {

public void add(int n){

result += n;

}

public void substract(int n){

result -= n;

}

public void multiply(int n){

result *= n;

}

public void divide(int n){

result /= n;

}

public void square(int n){

result = n * n;

}

public int getReuslt(){

return result;

}

public void clear(){

result = 0;

}

private static int result;

}

  在测试类中用到了JUnit4框架,自然要把相应地Package包含进来。最主要地一个Package就是org.junit.*。把它包含进来之后,绝大部分功能就有了。还有一句话也非常地重要“import static org.junit.Assert.*;”,我们在测试的时候使用的壹系列assertEquals()方法就来自这个包。大家注意壹下,这是壹個静态包含(static),是JDK5中新增添的壹個功能。也就是说,assertEquals是Assert类中的壹系列的静态方法,壹般的使用方式是Assert. assertEquals(),但是使用了静态包含后,前面的类名就可以省略了,使用起来更加的方便。

  另外要注意的是,我们的测试类是壹個独立的类,没有任何父类。测试类的名字也可以任意命名,没有任何局限性。所以我们不能通过类的声明来判断它是不是一个测试类,它与普通类的区别在于它内部的方法的声明,我们接着会讲到。在测试类中,并不是每壹個方法都是用于测试的,所以我们必须使用“注解”来明确表明哪些是测试方法。“注解”也是JDK5的壹個新特性,用在此处非常恰当。我们可以看到,在某些方法的前有@Before、@Test、@Ignore等字样,这些就是注解,以壹個“@”作为开头。这些注解都是JUnit4自定义的,熟练掌握这些注解的含义,对于编写恰当的测试类非常重要。

 接下来我们创建壹個测试类 CalculatorTest.java,代码如下:


package net.oschina.bairrfhoinn.test;

import static org.junit.Assert.*;

import org.junit.Test;

import net.oschina.bairrfhoinn.main.Calculator;

public class CalculatorTest {

private static Calculator calculator = new Calculator();

@Test

public void testAdd(){

calculator.add(7);

calculator.add(8);

assertEquals(15, calculator.getReuslt());

}

}

  首先,我们要在方法的前面使用@Test标注,以表明这是壹個测试方法。对于方法的声明也有如下要求:名字可以随便取,没有任何限制,但是返回值必须为void,而且不能有任何参数。如果违反这些规定,会在运行时抛出壹個异常。至于方法内该写些什么,那就要看你需要测试些什么了。比如上述代码中,我们想测试壹下add()方法的功能是否正确,就在测试方法中调用几次add函数,初始值为0,先加7,再加8,我们期待的结果应该是15。如果最终实际结果也是15,则说明add()方法是正确的,反之说明它是错的。assertEquals(15, calculator.getResult());就是用来判断期待结果和实际结果是否相等,其中第壹個参数填写期待结果,第二個参数填写实际结果,也就是通过计算得到的结果。这样写好之后,JUnit 会自动进行测试并把测试结果反馈给用户。

  如果想运行它,可以在 eclipse 的资源管理器中选择该类文件,然后点击右键,选择 Run As->JUnit Test 即可看到运行结果如下图所示:

  使用@Test 的属性 Ignore 指定测试时跳过这個方法

  如果你在写程序前做了很好的规划,那么哪些方法是什么功能都应该实现并且确定下来。因此,即使该方法尚未完成,他的具体功能也是确定的,这也就意味着你可以为他编写测试用例。但是,如果你已经把该方法的测试用例写完,但该方法尚未完成,那么测试的时候无疑是“失败”。这种失败和真正的失败是有区别的,因此 JUnit 提供了壹种方法来区别他们,那就是在这种测试函数的前面加上 @Ignore 标注,这个标注的含义就是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示你有几個测试被忽略,而不是失败。壹旦你完成了相应函数,只需要把@Ignore标注删去,就可以进行正常的测试。

  比如说上面的测试类 Calculator.java 中,假设我们的 Calculator 类的 multiply() 方法没有实现,我们可以在测试类 CalculatorTest 中先写如下测试代码:


package net.oschina.bairrfhoinn.test;

import static org.junit.Assert.*;

import org.junit.Ignore;

import org.junit.Test;

import net.oschina.bairrfhoinn.main.Calculator;

public class CalculatorTest {

private static Calculator calculator = new Calculator();

... //此处代码省略

@Ignore("method square() not implemented, please test this later...")

@Test

public void testSquare(){

calculator.square(3);

assertEquals(9, calculator.getReuslt());

}

}

  我们再运行壹次测试,会看到如下结果,从图中可以很明显的看出,方法testSquare() 上的 @Ignore 注解已经生效了,运行时直接跳过了它,而方法testAdd()仍然正常的运行并通过了测试。

  使用注解 @Before 和 @After 来完成前置工作和后置工作

  前置工作通常是指我们的测试方法在运行之前需要做的壹些准备工作,如数据库的连接、文件的加载、输入数据的准备等需要在运行测试方法之前做的事情,都属于前置工作;类似的,后置工作则是指测试方法在运行之后的壹些要做的事情,如释放数据库连接、输入输出流的关闭等;比如我们上面的测试,由于只声明了壹個 Calculator 对象,他的初始值是0,但是测试完加法操作后,他的值就不是0了;接下来测试减法操作,就必然要考虑上次加法操作的结果。这绝对是壹個很糟糕的设计!我们非常希望每壹個测试方法都是独立的,相互之间没有任何耦合度。因此,我们就很有必要在执行每壹個测试方法之前,对Calculator对象进行壹個“复原”操作,以消除其他测试造成的影响。因此,“在任何壹個测试方法执行之前必须执行的代码”就是壹個前置工作,我们用注解 @Before 来标注它,如下例子所示:


package net.oschina.bairrfhoinn.test;

...

import org.junit.After;

import org.junit.Before;

import org.junit.Ignore;

import org.junit.Test;

public class CalculatorTest {

...//这里省略部分代码

@Before

public void setUp() throws Exception {

calculator.clear();

}

@After

public void tearDown() throws Exception {

System.out.println("will do sth here...");

}

...//这里省略部分代码

}

 另外要说的是,注解 @Before 是定义在 org.junit.Before 这個类中的,因此使用时需要将其引入我们的代码中。这样做了之后,每次我们运行测试方法时,JUnit 都会先运行 setUp() 方法将 result 的值清零。不过要注意的是,这里不再需要 @Test 注解,因为这并不是壹個 test,只是壹個前置工作。同理,如果“在任何测试执行之后需要进行的收尾工作,我们应该使用 @After 来标注,方法与它类似。由于本例比较简单,不需要用到此功能,所以我们只是简单了给它添加了壹個 tearDown() 方法并在收尾时打印壹句话到控制台,并且使用 @After 来注解这個方法。

  使用@BeforeClass 和 @AfterClass 来完成只需要执行壹次的前置工作和后置工作

  上面我们提到了两個注解 @Before 和 @After ,我们来看看他们是否适合完成如下功能:有壹個类负责对大文件(超过500 MB)进行读写,他的每壹個方法都是对文件进行操作。换句话说,在调用每壹個方法之前,我们都要打开壹個大文件并读入文件内容,这绝对是壹個非常耗费时的操作。如果我们使用 @Before 和 @After ,那么每次测试都要读取壹次文件,效率及其低下。所以我们希望的是,在所有测试壹开始读壹次文件,所有测试结束之后释放文件,而不是每次测试都读文件。JUnit的作者显然也考虑到了这个问题,它给出了@BeforeClass 和 @AfterClass 两個注解来帮我们实现这个功能。从名字上就可以看出,用这两個注解标注的函数,只在测试用例初始化时执行 @BeforeClass 方法,当所有测试执行完毕之后,执行 @AfterClass 进行收尾工作。在这里要注意壹下,每個测试类只能有壹個方法被标注为 @BeforeClass 或 @AfterClass,而且该方法必须是 public static 类型的。

  使用@Test 的属性 timeout 来完成限时测试,以检测代码中的死循环

  现在假设我们的 Calculator 类中的 square() 方法是個死循环,那应该怎么办呢,比如说像下面这样:


public void square(int n){

for(;;){}

}

  如果测试的时候遇到死循环,你的脸上绝对不会露出笑容的。因此,对于那些逻辑很复杂,循环嵌套比较深的、有可能出现死循环的程序,因此壹定要采取壹些预防措施。限时测试是壹個很好的解决方案。我们给这些测试函数设定壹個预期的执行时间,超过了这壹时间,他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些 Bug 了。要实现这壹功能,只需要给 @Test 标注加壹個参数timeout即可,代码如下:


@Test(timeout=2000L)

public void testSquare() {

calculator.square(3);

assertEquals(9, calculator.getReuslt());

}

  timeout参数表明了你预计该方法运行的时长,单位为毫秒,因此2000就代表2秒。现在我们让这個测试方法运行壹下,看看失败时是什么效果。

  使用@Test 的属性expected来监控测试方法中可能会抛出的某些异常

  JAVA中的异常处理也是壹個重点,因此你经常会编写壹些需要抛出异常的函数。如果你觉得壹個函数应该抛出异常,但是它没抛出,这算不算 Bug 呢?这当然是Bug,JUnit 也考虑到了这壹点,并且可以帮助我们找到这种 Bug。例如,我们写的计算器类有除法功能,如果除数是壹個0,那么必然要抛出“除0异常”。因此,我们很有必要对这些进行测试。代码如下:


@Test(expected=java.lang.ArithmeticException.class)

public void testDivide(){

calculator.divide(0);

}

  如上述代码所示,我们需要使用@Test注解中的expected属性,将我们要检验的异常(这里是 java.lang.ArithmeticException)传递给他,这样 JUnit 框架就能自动帮我们检测是否抛出了我们指定的异常。

  指定 JUnit 运行测试用例时的 Runner

  大家有没有想过这個问题,当你把测试代码提交给JUnit框架后,框架是如何来运行你的代码的呢?答案就是Runner。在JUnit中有很多个Runner,他们负责调用你的测试代码,每壹個Runner都有其各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。可能你会觉得奇怪,前面我们写了那么多测试,并没有明确指定壹個Runner啊?这是因为JUnit中有壹個默认的Runner,如果你没有指定,那么系统会自动使用默认Runner来运行你的代码。换句话说,下面两段代码含义是完全壹样的:


import org.junit.runner.RunWith;

import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)

public class CalculatorTest {

...//省略此处代码

}

//用了系统默认的JUnit4.class,运行效果完全壹样

public class CalculatorTest {

...//省略此处代码

}

 从上述例子可以看出:

  1、要想指定壹個 Runner ,需要使用 @RunWith 标注,并且把你所指定的 Runner 类名作为参数传递给它,在JUnit4.8.2的版本中,系统提供了若干可以直接使用的Runner类型,它们的定义都在包org.junit.runners下面。

  2、注解 @RunWith 是用来修饰类的,而不是用来修饰函数的。只要对壹個类指定了 Runner ,那么这个类中的所有函数都被这个 Runner 来调用。

  3、在使用注解@RunWith时,要在头部包含相应的包名,上面的例子对这壹点写的很清楚了。

  接下来,我会向你们展示其他 Runner 的特有功能。

  使用参数化测试完成需要录入大量数据的测试

  你可能遇到过这样的函数,它的参数有许多特殊值,或者说他的参数分为很多个区域。比如,壹個对考试分数进行评价的函数,返回值分别为“优秀,良好,壹般,及格,不及格”,因此你在编写测试的时候,至少要写5個测试,把这五种情况都包含了,这确实是壹件很麻烦的事情。这里我们仍然使用先前的例子,测试壹下square()这个函数,暂且分三类:正数、0、负数。测试代码如下:


package net.oschina.bairrfhoinn.test;

import static org.junit.Assert.*;

import net.oschina.bairrfhoinn.main.Calculator;

import org.junit.Before;

import org.junit.Test;

public class AdvancedTest {

private static Calculator calculator = new Calculator();

@Before

public void setUp() throws Exception {

calculator.clear();

}

@Test

public void testSquare1(){

calculator.square(2);

assertEquals(4, calculator.getReuslt());

}

@Test

public void testSquare2(){

calculator.square(0);

assertEquals(0, calculator.getReuslt());

}

@Test

public void testSquare3(){

calculator.square(-3);

assertEquals(9, calculator.getReuslt());

}

}

  为了简化类似的测试,JUnit4提出了“参数化测试”的概念,只写壹個测试函数,把这若干种情况的输入参数和预期的运行结果放在集合中,然后将这個集合作为参数传递进去,壹次性的完成测试。代码如下:


package net.oschina.bairrfhoinn.test;

import static org.junit.Assert.*;

import java.util.Arrays;

import java.util.Collection;

import net.oschina.bairrfhoinn.main.Calculator;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.junit.runners.Parameterized;

import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)

public class SquareTest {

@Parameters

public static Collection prepareData(){

return Arrays.asList(new Object[][]{{2,4},{0, 0},{-3, 9}});

}

public SquareTest(int param, int result){

this.param = param;

this.result = result;

}

@Test

public void square(){

calculator.square(param);

assertEquals(result, calculator.getReuslt());

}

private int param;

private int result;

private static Calculator calculator = new Calculator();

}

   

最新内容请见作者的GitHub页:http://qaseven.github.io/

  

时间: 2025-01-21 10:59:21

使用 JUnit 进行 Java 代码的单元测试的相关文章

诊断Java代码: “杀手组合”― mixin、Jam和单元测试

在 Java 语言中获得单继承编程的安全性需要付出极大的代价:有时必须沿着继承层次结构中的多条路径复制代码.要重新获得单继承 Java 代码中所失去的大多数表示,我们可以将 mixin集成为一个扩展.本月,Eric Allen 解释了 mixin(那些由它们的父类参数化的类)的概念,以及它们如何能协助单元测试.他还描述了基于 mixin 编程的工具,并讨论了将 mixin 添加到您的 Java 代码中的可能方法. 自从面向对象的编程出现以来,OO 语言设计中一直存在着一个困扰人的基本问题.一方面

使用JUnit来测试Java代码中的异常

使用JUnit来测试Java代码中的异常有很多种方式,你知道几种? 给定这样一个class. Person.java public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; }

诊断Java代码: 进行记录器测试以正确调用方法

用 JUnit进行单元测试是一个功能强大的方法,它可以确保您的代码基础的完整性,但是一些不变量比其他(方法调用序列是其中一种)更难测试.在诊断Java 代码这一部分,Eric Allen描述了怎样在您的单元测试中使用记录器(一种特殊的侦听器),来确保一个方法调用序列按恰当的顺序发生.请点击文章顶部和底部的 讨论,与作者和其他读者在论坛上分享您关于本文的看法. 随着时间的推移,当系统开发人员,维护人员甚至是系统详细说明改变时,JUnit 框架提供一个很好的方法来改善系统的坚固性.通过测试,您可以检

分析JUnit框架的代码实现

简介:本文细致地描述了 JUnit 的代码实现,在展示代码流程 UML 图的基础上,详细分析 JUnit 的 内部实现代码的功能与机制,并在涉及相关设计模式的地方结合代码予以说明.另外,分析过程还涉及 Reflection 等 Java 语言的高级特征. 概述 在测试驱动的开发理念深入人心的今天,JUnit 在测试开发领域的核心地位日渐稳定.不仅 Eclipse 将 JUnit 作为默认的 IDE 集成组件,而且基于 JUnit 的各种测试框架也在业内被广泛应用,并获得了 一致好评.目前介绍 J

《精通移动App测试实战:技术、工具和案例》一2.3 应用JUnit对Android项目进行单元测试

2.3 应用JUnit对Android项目进行单元测试 2.3.1 JUnit基于Android项目TestCase的应用 如果我们对基于Android系统项目开发有一定了解的话,相信一定能看出来,其核心代码是计算两个整数相加的函数.我们做单元测试当然也挑选其最核心的函数来进行测试.可以按照如下的步骤来创建一个基于Android项目的测试用例(TestCase). 第一步:选中"CalculatorOfTwoNum"项目下的"src"目录中的"com.yu

实现Apache XML-RPC开发客户端及服务端Java代码

本文将会首先介绍一下 XML-RPC 协议规范,然后介绍如何实现 Apache XML-RPC 开发客户端及服务端 Java 代码.最后对 Apache XML-RPC 的高级特性进行了介绍. 在新技术.新概念甚至新思维层见叠出的 IT 行业,XML-RPC 绝对不是最新的热门技术,但它自诞生之日起,一直在 IT 行业占有一席之地.XML-RPC 具有简单且易于实现,可以高效跨越不同的软硬件平台上应用系统等优点.相对于庞大.复杂的分布式计算机制,它一直是中小型应用的最佳选择之一.比如,流行于软件

诊断 Java 代码:设计轻松的代码维护

设计 本月,Eric Allen 解释了在使代码更易于维护的同时,避免和控制无理由的变化怎么会是保持代码健壮性的关键.他集中讨论了诸如函数样式代码编写之类的概念,以及标记字段.方法和类的方法来处理并防止可变性.Eric 还解释了本任务中单元测试和重构的角色,并提供了协助实现重构的两个工具.在相关论坛中与作者和其他读者分享您对本文的看法.(您也可以单击本文顶部或底部的"讨论",访问该论坛.)有效调试源自良好的编程.设计易于维护的程序是程序员面临的最困难挑战之一,其部分原因在于程序通常并不

诊断Java代码

诊断Java代码: Broken Dispatch错误模式 诊断Java代码: Double Descent错误模式 诊断Java代码: Impostor Type错误模式 诊断Java代码: Java编程中的断言和时态逻辑 诊断Java代码: Liar View错误模式 诊断Java代码: Repl提供交互式评价 诊断Java代码: 单元测试与自动化代码分析协同工作 诊断Java代码: 将时态逻辑用于错误模式 诊断Java代码: 进行记录器测试以正确调用方法 诊断Java代码: 空标志错误模式

敏捷开发中高质量Java代码开发实践

概述 Java 项目开发过程中,由于开发人员的经验.代码风格各不相同,以及缺乏 统一的标准和管理流程,往往导致整个项目的代码质量较差,难于维护,需要较 大的测试投入和周期等问题.这些问题在一个项目组初建.需求和设计均具有不 完全可预期性和完备性的全新项目中将尤为突出.本文将结合敏捷开发周期短, 变化快等特点,介绍如何通过在开发过程中采取一系列步骤来保证和提高整个开 发团队的代码质量,并阐述了每一步可以利用的工具和最佳实践,从而使开发过 程更加规范化,成就高质量的代码,减少测试的投入,并促进整个团