java单元测试JUnit框架原理与用法实例教程

本文实例讲述了java单元测试JUnit框架原理与用法。分享给大家供大家参考,具体如下:

1 简介

JUnit是一个Java语言的单元测试框架,它由 Kent Beck 和 Erich Gamma 建立,逐渐成为 xUnit 家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈,多数Java的开发环境都已经集成了JUnit作为单元测试的工具。在这里,一个单元可以是一个方法、类、包或者子系统。因此,单元测试是指对代码中的最小可测试单元进行检查和验证,以便确保它们正常工作。例如,我们可以给予一定的输入测试输出是否是所希望得到的结果。在本篇博客中,作者将着重介绍 JUnit 4.X 版本的特性,这也是我们在日常开发中使用最多的版本。

2 特点

JUnit提供了注释以及确定的测试方法;
JUnit提供了断言用于测试预期的结果;
JUnit测试优雅简洁不需要花费太多的时间;
JUnit测试让大家可以更快地编写代码并且提高质量;
JUnit测试可以组织成测试套件包含测试案例,甚至其他测试套件;
Junit显示测试进度,如果测试是没有问题条形是绿色的,测试失败则会变成红色;
JUnit测试可以自动运行,检查自己的结果,并提供即时反馈,没有必要通过测试结果报告来手动梳理。

3 内容

3.1 注解

@Test :该注释表示,用其附着的公共无效方法(即用public修饰的void类型的方法 )可以作为一个测试用例;
@Before :该注释表示,用其附着的方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件;
@BeforeClass :该注释表示,用其附着的静态方法必须执行一次并在类的所有测试之前,发生这种情况时一般是测试计算共享配置方法,如连接到数据库;
@After :该注释表示,用其附着的方法在执行每项测试后执行,如执行每一个测试后重置某些变量,删除临时变量等;
@AfterClass :该注释表示,当需要执行所有的测试在JUnit测试用例类后执行,AfterClass注解可以使用以清理建立方法,如断开数据库连接,注意:附有此批注(类似于BeforeClass)的方法必须定义为静态;
@Ignore :该注释表示,当想暂时禁用特定的测试执行可以使用忽略注释,每个被注解为@Ignore的方法将不被执行。

/**
* JUnit 注解示例
*/
@Test
public void testYeepay(){
  Syetem.out.println("用@Test标示测试方法!");
}
@AfterClass
public static void paylus(){
  Syetem.out.println("用@AfterClass标示的方法在测试用例类执行完之后!");
}

3.2 断言

在这里,作者将介绍一些断言方法,所有这些方法都来自 org.junit.Assert 类,其扩展了 java.lang.Object 类并为它们提供编写测试,以便检测故障。简而言之,我们就是通过断言方法来判断实际结果与我们预期的结果是否相同,如果相同,则测试成功,反之,则测试失败。

void assertEquals([String message], expected value, actual value) :断言两个值相等,值的类型可以为int、short、long、byte、char 或者
java.lang.Object,其中第一个参数是一个可选的字符串消息;
void assertTrue([String message], boolean condition) :断言一个条件为真;
void assertFalse([String message],boolean condition) :断言一个条件为假;
void assertNotNull([String message], java.lang.Object object) :断言一个对象不为空(null);
void assertNull([String message], java.lang.Object object) :断言一个对象为空(null);
void assertSame([String message], java.lang.Object expected, java.lang.Object actual) :断言两个对象引用相同的对象;
void assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual) :断言两个对象不是引用同一个对象;
void assertArrayEquals([String message], expectedArray, resultArray) :断言预期数组和结果数组相等,数组的类型可以为int、long、short、char、byte 或者 java.lang.Object

4 JUnit 3.X 和 JUnit 4.X 的区别

4.1 JUnit 3.X

(1)使用 JUnit 3.X 版本进行单元测试时,测试类必须要继承于 TestCase 父类;
(2)测试方法需要遵循的原则:

① public的;
② void的;
③ 无方法参数;
④方法名称必须以 test 开头;

(3)不同的测试用例之间一定要保持完全的独立性,不能有任何的关联;
(4)要掌握好测试方法的顺序,不能依赖于测试方法自己的执行顺序。

/**
* 用 JUnit 3.X 进行测试
*/
import junit.framework.Assert;
import junit.framework.TestCase;
public class TestOperation extends TestCase {
  private Operation operation;
  public TestOperation(String name) { // 构造函数
    super(name);
  }
  @Override
  public void setUp() throws Exception { // 在每个测试方法执行 [之前] 都会被调用,多用于初始化
    System.out.println("欢迎使用Junit进行单元测试...");
    operation = new Operation();
  }
  @Override
  public void tearDown() throws Exception { // 在每个测试方法执行 [之后] 都会被调用,多用于释放资源
    System.out.println("Junit单元测试结束...");
  }
  public void testDivideByZero() {
    Throwable te = null;
    try {
      operation.divide(6, 0);
      Assert.fail("测试失败"); //断言失败
    } catch (Exception e) {
      e.printStackTrace();
      te = e;
    }
    Assert.assertEquals(Exception.class, te.getClass());
    Assert.assertEquals("除数不能为 0 ", te.getMessage());
  }
}

4.2 JUnit 4.X

(1)使用 JUnit 4.X 版本进行单元测试时,不用测试类继承TestCase父类;
(2)JUnit 4.X 版本,引用了注解的方式进行单元测试;
(3)JUnit 4.X 版本我们常用的注解包括:

@Before 注解:与JUnit 3.X 中的 setUp() 方法功能一样,在每个测试方法之前执行,多用于初始化;
@After 注解:与 JUnit 3.X 中的 tearDown() 方法功能一样,在每个测试方法之后执行,多用于释放资源;
@Test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误;
@Test(expected = Exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class;

此外,我们可以通过阅读上面的第二部分“2 注解”了解更多的注解。

/**
* 用 JUnit 4.X 进行测试
*/
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestOperation {
  private Operation operation;
  @BeforeClass
  public static void globalInit() { // 在所有方法执行之前执行
    System.out.println("@BeforeClass标注的方法,在所有方法执行之前执行...");
  }
  @AfterClass
  public static void globalDestory() { // 在所有方法执行之后执行
    System.out.println("@AfterClass标注的方法,在所有方法执行之后执行...");
  }
  @Before
  public void setUp() { // 在每个测试方法之前执行
    System.out.println("@Before标注的方法,在每个测试方法之前执行...");
    operation = new Operation();
  }
  @After
  public void tearDown() { // 在每个测试方法之后执行
    System.out.println("@After标注的方法,在每个测试方法之后执行...");
  }
  @Test(timeout=600)
  public void testAdd() { // 设置限定测试方法的运行时间 如果超出则返回错误
    System.out.println("测试 add 方法...");
    int result = operation.add(2, 3);
    assertEquals(5, result);
  }
  @Test
  public void testSubtract() {
    System.out.println("测试 subtract 方法...");
    int result = operation.subtract(1, 2);
    assertEquals(-1, result);
  }
  @Test
  public void testMultiply() {
    System.out.println("测试 multiply 方法...");
    int result = operation.multiply(2, 3);
    assertEquals(6, result);
  }
  @Test
  public void testDivide() {
    System.out.println("测试 divide 方法...");
    int result = 0;
    try {
      result = operation.divide(6, 2);
    } catch (Exception e) {
      fail();
    }
    assertEquals(3, result);
  }
  @Test(expected = Exception.class)
  public void testDivideAgain() throws Exception {
    System.out.println("测试 divide 方法,除数为 0 的情况...");
    operation.divide(6, 0);
    fail("test Error");
  }
  public static void main(String[] args) {
  }
}

4.3 特别提醒

通过以上两个例子,我们已经可以大致知道 JUnit 3.X 和 JUnit 4.X 两个版本的区别啦!首先,如果我们使用 JUnit 3.X,那么在我们写的测试类的时候,一定要继承 TestCase 类,但是如果我们使用 JUnit 4.X,则不需继承 TestCase 类,直接使用注解就可以啦!在 JUnit 3.X 中,还强制要求测试方法的命名为“ testXxxx ”这种格式;在 JUnit 4.X 中,则不要求测试方法的命名格式,但作者还是建议测试方法统一命名为“ testXxxx ”这种格式,简洁明了。

此外,在上面的两个示例中,我们只给出了测试类,但是在这之前,还应该有一个被测试类,也就是我们真正要实现功能的类。现在,作者将给出上面示例中被测试的类,即 Operation 类:

/**
* 定义了加减乘除的法则
*/
public class Operation {
  public static void main(String[] args) {
    System.out.println("a + b = " + add(1,2));
    System.out.println("a - b = " + subtract(1,2));
    System.out.println("a * b = " + multiply(1,2));
    System.out.println("a / b = " + divide(4,2));
    System.out.println("a / b = " + divide(1,0));
  }
  public static int add(int a, int b) {
    return a + b;
  }
  public static int subtract(int a, int b) {
    return a - b;
  }
  public static int multiply(int a, int b) {
    return a * b;
  }
  public static int divide(int a, int b) {
    return a / b;
  }
}

5 测试示例

5.1 示例一:简单的 JUnit 3.X 测试

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.util.ArrayList;
import java.util.Collection;
/**
 * 1、创建一个测试类,继承TestCase类
 */
public class SimpleTestDemo extends TestCase {
  public SimpleTestDemo(String name) {
    super(name);
  }
  /**
   * 2、写一个测试方法,断言期望的结果
   */
  public void testEmptyCollection(){
    Collection collection = new ArrayList();
    assertTrue(collection.isEmpty());
  }
  /**
   * 3、写一个suite()方法,它会使用反射动态的创建一个包含所有的testXxxx方法的测试套件
   */
  public static Test suit(){
    return new TestSuite(SimpleTestDemo.class);
  }
  /**
   * 4、写一个main()方法,以文本运行器的方式方便的运行测试
   */
  public static void main(String[] args) {
    junit.textui.TestRunner.run(suit());
  }
}

5.2 示例二:套件测试

首先,介绍一下套件测试,简单来讲,测试套件是指:一些测试不同类的用例,可以使用 @RunWith 和 @Suite 注解把所有的测试类套在一起,从而形成测试套件。如果有很多测试类,想让它们都运行在同一时间,而不是单一地运行每个测试,套件测试是非常有用的。当一个类被注解为 @RunWith, JUnit 将调用其中的注解,以便运行测试类,而不使用内置的 JUnit 运行方法。

/**
* 待测试类
*/
import java.util.Arrays;
public class GotoWork {
  public String[] prepareSkills() {
    String[] skill = { "Java", "MySQL", "JSP" };
    System.out.println("My skills include : " + Arrays.toString(skill));
    return skill;
  }
  public String[] addSkills() {
    String[] skill = { "Java", "MySQL", "JSP", "JUnit" };
    System.out.println("Look, my skills include : " + Arrays.toString(skill));
    return skill;
  }
}
/**
* 测试类 1
*/
import org.junit.Test;
import static org.junit.Assert.*;
public class PrepareSkillsTest {
  GotoWork gotoWork = new GotoWork();
  String[] skill = { "Java", "MySQL", "JSP" };
  @Test
  public void testPrepareSkills() {
    System.out.println("Inside testPrepareSkills()");
    assertArrayEquals(skill, gotoWork.prepareSkills());
  }
}
/**
* 测试类 2
*/
import org.junit.Test;
import static org.junit.Assert.*;
public class AddSkillsTest {
  GotoWork gotoWork = new GotoWork();
  String[] skill = { "Java", "MySQL", "JSP", "JUnit" };
  @Test
  public void testAddSkills() {
    System.out.println("Inside testAddPencils()");
    assertArrayEquals(skill, gotoWork.addSkills());
  }
}
/**
* 套件测试
*/
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({ PrepareSkillsTest.class, AddSkillsTest.class })
public class SuitTest {
}

使用 @Suite.SuiteClasses 注解,可以定义测试类,将被列入执行,并且执行的顺序就是在 @Suite.SuiteClasses 注解中定义的顺序。

5.3 示例三:参数化测试

首先介绍一下参数化测试,一个测试类可以被看作是一个参数化测试类,当其满足下列所有要求:

① 该类被注解为 @RunWith(Parameterized.class);
② 该类有一个构造函数,存储测试数据;
③ 该类有一个静态方法生成并返回测试数据,并标注 @Parameters 注解;
④ 该类有一个测试方法,即用注解 @Test 标注的方法。

/**
* 待测试类
*/
public class Calculate {
  public int sum(int var1, int var2) {
    System.out.println("此方法的参数值分别为 : " + var1 + " + " + var2);
    return var1 + var2;
  }
}
/**
* 参数化测试类
*/
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collection;
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 CalculateTest {
  private int expected;
  private int first;
  private int second;
  public CalculateTest(int expectedResult, int firstNumber, int secondNumber) {
    this.expected = expectedResult;
    this.first = firstNumber;
    this.second = secondNumber;
  }
  @Parameters
  public static Collection addedNumbers() {
    return Arrays.asList(new Integer[][] { { 3, 1, 2 }, { 5, 2, 3 }, { 7, 3, 4 }, { 9, 4, 5 }, });
  }
  @Test
  public void testSum() {
    Calculate add = new Calculate();
    System.out.println("Addition with parameters : " + first + " and " + second);
    assertEquals(expected, add.sum(first, second));
  }
}

观察 CalculateTest 类,它满足上述所有的要求,因此它就可以称为一个参数化测试类。addedNumbers 方法使用注释 @Parameters 返回数组的集合,每个数组包括每个测试执行输入和输出数字,每个数组中的元素数必须相同好与构造参数的个数相匹配。所以,在这种特定的情况下,每个数组包括三个元素,即表示要加入的两个元素和一个结果元素。

6 个人建议

有些童鞋可能会有一些误解,认为写测试代码没有用,而且还会增大自己的压力,浪费时间。但事实上,写测试代码与否,还是有很大区别的,如果是在小的项目中,或许这种区别还不太明显,但如果在大型项目中,一旦出现错误或异常,用人力去排查的话,那将会浪费很多时间,而且还不一定排查的出来,但是如果用测试代码的话,JUnit 就是自动帮我们判断一些代码的结果正确与否,从而节省的时间将会远远超过你写测试代码的时间。

因此,个人建议:要养成编写测试代码的习惯,码一点、测一点;再码一点,再测一点,如此循环。在我们不断编写与测试代码的过程中,我们将会对类的行为有一个更为深入的了解,从而可以有效的提高我们的工作效率。下面,作者就给出一些具体的编写测试代码的技巧和较好的实践方法:

1. 不要用 TestCase 的构造函数初始化 Fixture,而要用 setUp() 和 tearDown() 方法;
2. 不要依赖或假定测试运行的顺序,因为 JUnit 会利用 Vector 保存测试方法,所以不同的平台会按不同的顺序从 Vector 中取出测试方法;
3. 避免编写有副作用的 TestCase,例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据,只需要简单的回滚就可以了;
4. 当继承一个测试类时,记得调用父类的 setUp() 和 tearDown() 方法;
5. 将测试代码和工作代码放在一起,同步编译和更新;
6. 测试类和测试方法应该有一致的命名方案,如在工作类名前加上 test 从而形成测试类名;
7. 确保测试与时间无关,不要使用过期的数据进行测试,以至于导致在随后的维护过程中很难重现测试;
8. 如果编写的软件面向国际市场,那么编写测试时一定要考虑国际化的因素;
9. 尽可能地利用 JUnit 提供地 assert 和 fail 方法以及异常处理的方法,其可以使代码更为简洁;
10. 测试要尽可能地小,执行速度快;
11. 不要硬性规定数据文件的路径;
12. 使用文档生成器做测试文档。

事实上,在 Junit 中使用 try catch 来捕获异常是没有必要的,因为 Junit 会自动捕获异常,那些没有被捕获的异常就会被当成错误处理啦!

更多关于java相关内容感兴趣的读者可查看本站专题:《Java数据结构与算法教程》、《Java字符与字符串操作技巧总结》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

(0)

相关推荐

  • 详解Java单元测试之JUnit篇

    单元测试是编写测试代码,应该准确.快速地保证程序基本模块的正确性. JUnit是Java单元测试框架,已经在Eclipse中默认安装. JUnit4 JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @BeforeClass 全局只会执行一次,而且是第一个运行 @Before 在测试方法运行之前运行 @Test 测试方法 @After 在测试方法运行之后允许 @AfterClass 全局只会执行一次,而且是最后一个运行 @Ignore 忽略此方法 下面基于Eclipse介绍JUn

  • 详解Spring Boot Junit单元测试

    Junit这种老技术,现在又拿出来说,不为别的,某种程度上来说,更是为了要说明它在项目中的重要性. 凭本人的感觉和经验来说,在项目中完全按标准都写Junit用例覆盖大部分业务代码的,应该不会超过一半. 刚好前段时间写了一些关于SpringBoot的帖子,正好现在把Junit再拿出来从几个方面再说一下,也算是给一些新手参考了. 那么先简单说一下为什么要写测试用例 1. 可以避免测试点的遗漏,为了更好的进行测试,可以提高测试效率 2. 可以自动测试,可以在项目打包前进行测试校验 3. 可以及时发现因

  • java编程之单元测试(Junit)实例分析(附实例源码)

    本文实例讲述了java编程之单元测试.分享给大家供大家参考,具体如下: 完整实例代码代码点击此处本站下载. 在有些时候,我们需要对我们自己编写的代码进行单元测试(好处是,减少后期维护的精力和费用),这是一些最基本的模块测试.当然,在进行单元测试的同时也必然得清楚我们测试的代码的内部逻辑实现,这样在测试的时候才能清楚地将我们希望代码逻辑实现得到的结果和测试实际得到的结果进行验证对比. 废话少说,上代码: 首先创建一个java工程,在工程中创建一个被单元测试的Student数据类,如下: packa

  • 浅谈junit4单元测试高级用法

    Junit单元测试框架是Java程序开发必备的测试利器,现在最常用的就是Junit4了,在Junit4中所有的测试用例都使用了注解的形式,这比Junit3更加灵活与方便.之前在公司的关于单元测试的培训课程中,讲师仅仅讲述了Junit4的基本的与生命周期相关的注解的使用,主要包括@BeforeClass.@Before.@Test.@After.@AfterClass这些注解,这些在应付普通简单的单元测试已经足够,然而有很多更加复杂且也会经常遇到的测试需求依靠这些生命周期注解并不能完成!因此这篇分

  • 详解Java单元测试Junit框架实例

    问题: 1.目前测试存在的问题 2.Junit注意的细节 3.Junit使用规范 4.断言 5.案例 junit(单元测试框架) 1.目前存在的问题 1.目前的测试方法如果需要测试,都需要在main方法上调用 2.目前的结果都需要我们人工比对 2.Junit 注意的细节 1.如果使用junit测试一个方法的时候,在junit窗口上显示绿色那么表示测试正确,如果显示了红色,则代表该方法测试出现了异常不通过 2.如果点击方法名.类名.包名.工程名运行junit分别测试的是对于的方法,类.包中的所有类

  • JBuilder2005单元测试之JUnit框架

    简单的框架 JUnit是由Erich Gamma和Kent Beck开发的开源测试框架,JBuilder集成了这个框架并对此做了扩展.JUnit之所以流行并为广大的开发人员所推崇,一是因为它实战性强,功能强大,二是因为它实在简单.一个产品或框架要能有生命力,最好都具备这样的特点. 简单地讲这个框架提供了许多断言(assert)方法,允许你设置测试的规则,如:assertEquals().assertNull().assertNotSame().assertTrue()等方法,一个测试用例包括了多

  • junit4教程junit4.5官方下载

    官方下载地址http://sourceforge.net/project/showfiles.php?group_id=15278&package_id=12472JUnit4是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写. 先简单解释一下什么是Annotation,这个单词一般是翻译成元数据.元数据是什么?元数据就是描述数据的数据.也就是说,这个东西在Java里面可以用来和public.static等关键字一样来修饰类名.方法名.变

  • JUnit中获取测试类及方法的名称实现方法

    在JUnit的测试中,有时候需要获得所属的类(Class)或者方法(Method)的名称,以方便记录日志什么的. 在JUnit中提供了TestName类来做到这一点,在org.junit.rules中: public class TestName extends TestWatcher { private String fName; @Override protected void starting(Description d) { fName = d.getMethodName(); } /*

  • Java中的异常测试框架JUnit使用上手指南

    JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework).Junit测试是程序员测试,即白盒测试.该项目主页:http://www.junit.org/ 使用JUnit时,主要都是通过继承TestCase类别来撰写测试用例,使用testXXX()名称来撰写单元测试. 用JUnit写测试真正所需要的就三件事: 1.  一个import语句引入所有junit.framework.*下的类. 2.  一个exte

  • 详解springboot中junit回滚

    springboot中使用junit编写单元测试,并且测试结果不影响数据库. pom引入依赖 如果是IDE生成的项目,该包已经默认引入. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency&g

  • 详解Junit 测试之 Spring Test

    在做spring相关测试时比较麻烦,如果只用JUnit测试,需要没测有初始化一下applicationContext,效率比较底下,而且也有不足之处. 1.导致多次Spring容器初始化问题 根据JUnit测试方法的调用流程,每执行一个测试方法都会创建一个测试用例的实例并调用setUp()方法.由于一般情况下,我们在setUp()方法 中初始化Spring容器,这意味着如果测试用例有多少个测试方法,Spring容器就会被重复初始化多次.虽然初始化Spring容器的速度并不会太 慢,但由于可能会在

随机推荐