JUnit4单元测试基础篇

引言

JUnit作为Java语言的测试框架,在测试驱动开发(TDD)下扮演重要的角色。众所周知,无论开发大型项目还是一般的小型项目,

单元测试都至关重要。单元测试为软件可发测试维护提供了很大的便利。JUnit 4 作为最新版本,增添了许多新的特性,

结合Hamcrest,可以写出很多灵活的测试。从JUnit 4 开始 jar包放在org.junit包下。代码已经托管在GitHub上。

为了以后测试方便,自定义了一个JUnit的类库,把几个重要的jar包导在一个类库,

这样,以后的项目工程需要写单元测试,直接导入这个类库,不需要重新导入各种jar包。

自定义类库结构如下:

关于如何自定义类库,Build Path -> Configure Build Path... -> Add Library... -> User Library... -> Next ......,

常规的单元测试,是在工程下新建一个Source Folder和src目录下相同的包名结构,测试A类,就在相对应的包下

创建一个测试类ATest,测试fun方法,就相应的创建测试方法testFun()。本文单纯学习JUnit特性,没有采用这种方式。

ok,废话到此为止,开始测试。文章略长,按需阅读。

断言 - Assertions

在JUnit 之前版本,经常会使用assertTrue,assertEquals,assertNull等断言,根据字面意思就很容易知道函数的用途。

JUnit 4 结合Hamcrest退出更强大的断言AssertThat(),相比于之前的assertXXX,assertThat代码风格将变得统一,更容易维护;

配合Hamcrest的Matchers, 可以写出很多灵活的测试,下面会提到;另一个优点更倾向于英语语法,不像"谓宾主"

(如:assertEquals(9, x)) 语法模式拗口,assertThat使用类型"主谓宾"的语法模式(如:assertThat(x, is(9)),可能是受母语的影响,

这条优点感触不是很深刻; 下面要说的这个优点比较实用,AssertThat测试失败会提供一些可读的描述性错误信息,

而assertXXX不会(当然可以手动去写),assertThat会举例说明; Last but not the least(v~v高考残留的英语记忆),

可发人员可以实现Matcher接口,自定义匹配符,这个功能很强大。

注意:测试的时候需要引入一些类,有些类(Assert, Matchers)比较特殊,采用静态引入的方式,

好处在于可以直接使用这些类的静态方法(assertTure, assertThat),而不用使用类名.方法名的方式。

assertXXX

import static org.junit.Assert.*;

import org.junit.Test;

/**
 * AssertXXX测试
 * @author michael
 */
public class AssertTests {

	@Test
	public void testAssertArrayEquals() {
		byte[] expected = "trial".getBytes();
		byte[] actual = "trial".getBytes();
		assertArrayEquals("failure - byte arrays not same", expected, actual);
	}

	@Test
	public void testEquals() {
		assertEquals("failure - strings are not equal", "text", "text");
	}

	@Test
	public void testAssertTrue() {
		assertTrue("failure - should be true", true);
	}

	@Test
	public void testFalse() {
		assertFalse("failure - should be false", false);
	}

	@Test
	public void testAssertNotNull() {
		assertNotNull("should not be null", new Object());
	}

	@Test
	public void testAssetNull() {
		assertNull("should be null", null);
	}

	@Test
	public void testAssertNotSame() {
		assertNotSame("should not be same Object", new Object(), new Object());
	}

	@Test
	public void testAssertSame() {
		Integer aNumber = Integer.valueOf(985);
		assertSame("should be same", aNumber, aNumber);
	}

}

assertThat

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

import java.util.Arrays;

import org.junit.Test;

public class AssertThatTests {

	//JUnit Matchers assertThat
	@Test
	public void testAssertThatBothContainsString() {
		assertThat("albumen", both(containsString("a")).and(containsString("b")));
	}

	@Test
	public void testAssertThathasItemsContainsString() {
		assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
	}

	@Test
	public void testAssertThatEveryItemContainsString() {
		assertThat(Arrays.asList(new String[] {"fun", "ban", "net"}), everyItem(containsString("n")));
	} 

	/**
	 * Core Hamcrest Matchers with assertThat
	 * 组合使用多种匹配符
	 */
	@Test
	public void testAssertThatHamcrestCoreMatchers() {
		assertThat("good", allOf(equalTo("good"), startsWith("good")));
		assertThat("good", not(allOf(equalTo("good"), equalTo("bad"))));
		assertThat("good", anyOf(equalTo("good"), equalTo("bad")));
		assertThat(3, not(either(equalTo(6)).or(equalTo(9))));
		assertThat(new Object(), not(sameInstance(new Object())));
	}

	/**
	 * Readable failure message
	 * assertThat会提供可读性的错误信息,assertTrue不会
	 */
	@Test
	public void testFailureMessage() {
		String s = "coour";
		//assertTrue(s.contains("color") || s.contains("colour"));
		assertThat(s, anyOf(containsString("color"), containsString("colour")));
	}

} 

套件测试 - Aggregating tests in Suites

如果你写了一系列的测试类(十几个甚至几十个),你不可能一个一个的去测试,此时,套件测试就会派上用场。

创建一个空类,不需要为这个类定义任何东西,只需要在这个类的头部添加注解@RunWith(Suite.class)。

@Suite.SuiteClasses。在SuiteClasses里添加需要测试的类。创建的这个类只是作为一个载体,承载上面的注解。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

import assertions.AssertTests;
import assertions.AssertThatTests;

@RunWith(Suite.class)
@Suite.SuiteClasses({
	//添加需要测试的类
	AssertTests.class,
	AssertThatTests.class

	// more test classes
})

public class FutureTestSuite {

	/**
	 * the class remains empty,
	 * used only a holder for the above annotations.
	 */
}

What‘s the difference between failure and error in JUnit

顺序测试 - Test Execution Order

顺序测试的应用场景不是很多,当你想让你写的一系列测试方法按照一定的顺序执行才会用到。

添加@FixMethodOrder注解,有三种常用的运行顺序定义在枚举类MethodSorters内。

DEFAULT、JVM、NAME_ASCENDING。

DEFAULT:默认,比较方法名的hashCode值。

JVM:依赖JVM的实现,不同机器上可能有所不同。

NAME_ASCENDING:按照测试函数方法名升序。

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
//@FixMethodOrder(MethodSorters.DEFAULT)
//@FixMethodOrder(MethodSorters.JVM)
public class TestMethodOrder {

	@Test
	public void testA() {
		System.out.println("first");
	}

	@Test
	public void testB() {
		System.out.println("second");
	}

	@Test
	public void testC() {
		System.out.println("third");
	}

}

异常测试 - Exception Testing

开发者在编写程序时,有些代码可能会抛出异常。

如何测试这些代码是否按照我们预想的抛出异常,此时就需要用到异常测试。

@Test注解有一个可选择的参数expected,用于指定可能抛出的异常。例如:

Expected Exceptions

import java.util.ArrayList;
import org.junit.Test;

public class TestException {

	@SuppressWarnings("unused")
	private double result;

	@Test(expected = ArithmeticException.class)
	public void divide() {
		result = 1/0;
	}

	/**
	 * The expected parameter should be used with care.
	 * The test will pass if any code in the method throws IndexOutOfBoundsException
	 */
	@Test(expected = IndexOutOfBoundsException.class)
	public void empty() {
		new ArrayList<Object>().get(0);
	}

}

不过,expected参数应当谨慎使用,因为,如果测试方法任意一处代码抛出expected指定的异常,

测试都会通过,无法准确定位哪处代码抛出的异常。长远考虑,推荐使用ExpectedException rule。

关于Rule会在另一篇文章中介绍。

ExceptedException rule

上面的测试方法对于测试简单的例子比较适用,但它存在一定的限制,比如开发者无法测试异常信息。

当然JUnit3.x 提供了Try/Catch Idiom可以预测异常和异常信息。JUnit4则提供了ExpectedException rule。

不同的是,JUnit4 expectedMessage除了可以预测可能抛出的异常信息,还可以与Hamcrest的Matchars配合 使用,

编写出更加灵活的测试。例如:

import static org.hamcrest.Matchers.containsString;

import java.util.ArrayList;
import java.util.List;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class TestExceptionRule {

	//Expected Exception Rule
	@Rule
	public ExpectedException thrown =ExpectedException.none();

	/**
	 * This rule lets you indicate not only what exception you are expecting,
	 * but also the exception message you are expecting:
	 */
	@Test
	public void shouldTestExceptionMessage() {
		List<Object> list = new ArrayList<Object>();

		thrown.expect(IndexOutOfBoundsException.class);
		thrown.expectMessage("Index: 0, Size: 0");
		thrown.expectMessage(containsString("Size: 0"));

		list.get(0);
	}

}

测试忽略 - Ignore tests

如果在某次测试中想要跳过某个测试方法,就使用@Ignore注解。

当然直接注释掉@Test注解同样可以跳过该测试方法,不同的是,

注释掉在测试结果中不会显示,而使用@Ignore会显示该方法被忽略。

import org.junit.Ignore;
import org.junit.Test;

public class TestIgnore {

	@Test
	public void testA() {
		System.out.println("executed");
	}

	@Ignore
	@Test
	public void testB() {
		System.out.println("ignored");
	}

}

超时测试 - Timeout for tests

超时测试,顾名思义,测试方法超过指定的时间就会报Errors,注意不是Failures。

Why is JUnit timeout an Error Not Failure

开发人员可以为单独某个方法设置超时时间,也可以为整个测试类设置统一超时时间。

单独为一个方法设置超时时间,单位毫秒

import org.junit.Test;

public class TestTimeout {

	@Test(timeout=100)
	public void testWithTimeOut() {

		for( ; ;) {

		}

	}

}

为整个类设置超时时间,单位毫秒

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;

public class TestTimeoutRule {

	public static int n;

	@Rule
	public Timeout globalTimeout = new Timeout(100);

	@Test
	public void testInfiniteLoop1() {
		n++;
		for( ; ;) {

		}
	}

	@Test
	public void testInfiniteLoop2() {
		n++;
		for( ; ;) {

		}
	}

}

结束语

关于Parameterized tests、Rules、Theories等特性将在另一篇文章介绍。

本文为笔者学习JUnit的学习笔记,不免有误解偏差,还望读者指点修正。

Keeps the bar green to keep the code clean.

时间: 2024-10-14 00:03:47

JUnit4单元测试基础篇的相关文章

在Eclipse中使用JUnit4进行单元測试(0基础篇)

本文绝大部分内容引自这篇文章: http://www.devx.com/Java/Article/31983/0/page/1 我们在编写大型程序的时候,须要写成千上万个方法或函数,这些函数的功能可能非常强大,但我们在程序中仅仅用到该函数的一小部分功能,而且经过调试能够确定,这一小部分功能是正确的.可是,我们同一时候应该确保每个函数都全然正确,由于假设我们今后假设对程序进行扩展,用到了某个函数的其它功能,而这个功能有bug的话,那绝对是一件非常郁闷的事情.所以说,每编写完一个函数之后,都应该对这

Repractise基础篇:Web应用开发七日谈

Repractise基础篇:Web应用开发七日谈 本来想的只是画一个如下的七日图来说说Web开发的,随后又想了想这似乎是一个非常棒的Web开发相关的知识介绍.应用开发是一个很有意思的循环,多数时候我们总会觉得别人的代码写得不好.即使它使用了一种非常好的设计,我们也可能会觉得他很复杂. 而它总结下来就是下面的七天过程里发生的一些事情. 七日谈 其实对于Web开发,有下面这张图就足够了. 第一天:新的开始 我们迎来了我们的新的一个项目,看起来每个人都很兴奋.这一天过得很快,也做了特别多的事. 首先,

SpringBoot图文教程「概念+案例 思维导图」「基础篇上」

有天上飞的概念,就要有落地的实现 概念+代码实现是本文的特点,教程将涵盖完整的图文教程,代码案例 每个知识点配套自测面试题,学完技术自我测试 本文初学向,所以希望文中所有的代码案例都能敲一遍 大哥大姐新年好,点赞转发不要少 **文本已收录至GitHub开源仓库 Lu_JavaNodes 码云仓库地址Lu_JavaNodes ,**包含教程涉及所有思维导图,案例代码和后续讲解视频,欢迎Star增砖添瓦. 前言 庚子鼠年,封村儿,在试过了睡觉,打麻将,做凉皮,做蛋糕之后,我不由的陷入了对人生和社会的

oracle(sql)基础篇系列(五)&mdash;&mdash;PLSQL、游标、存储过程、触发器

  PL/SQL PL/SQL 简介 每一种数据库都有这样的一种语言,PL/SQL 是在Oracle里面的一种编程语言,在Oracle内部使用的编程语言.我们知道SQL语言是没有分支和循环的,而PL语言是为了补充SQL语言的,是带有了分支和循环的语言. PL/SQL 语法 基本数据类型声明 declare v_name varchar2(20); v_temp number(1); v_count binary_integer := 0; v_sal number(7,2) := 4000.00

Hybrid APP基础篇(四)-&gt;JSBridge的原理

说明 JSBridge实现原理 目录 前言 参考来源 前置技术要求 楔子 原理概述 简介 url scheme介绍 实现流程 实现思路 第一步:设计出一个Native与JS交互的全局桥对象 第二步:JS如何调用Native 第三步:Native如何得知api被调用 第四步:分析url-参数和回调的格式 第五步:Native如何调用JS 第六步:H5中api方法的注册以及格式 进一步完善JSBridge方案 思路 实现 注意 完整的JSBridge 完整调用流程图 另外实现:不采用url sche

网络基础篇----计算机网络基本概述(1)

享受生活  热爱挑战                                                                刘明远分享    一   计算机网络基本概述(1) 每章一段话: 不要让自己闲下来,给自己找些事情做.哪怕是看看书. 正文   (提示:本章内容比较无聊,最好当看故事一样来看,不必记下只需了解,内容基础) 1什么是计算机网络 号称新的"电力火花"是以计算机.通信.信息技术为支撑的计算机网络技术. 计算机网络将两台或多台计算机通过电缆或网络设

js调试系列: 源码定位与调试[基础篇]

js调试系列目录: - 如果看了1, 2两篇,你对控制台应该有一个初步了解了,今天我们来个简单的调试.昨天留的三个课后练习,差不多就是今天要讲的内容.我们先来处理第一个问题:1. 查看文章下方 推荐 这个功能所调用的函数源码其实非常简单,点放大镜选中那个推荐即可.这个  votePost(cb_entryId,'Digg')  就是推荐按钮所调用的函数了,是不是非常简单. 第二个问题,定位到函数所在文件位置.其实也是非常简单的,当然,不熟悉控制台的朋友也许不知道怎么看.我在控制台输入 voteP

DOM系列---基础篇

DOM (Document Object Model) 即文档对象模型, 针对 HTML 和 XML 文档的 API (应用程序接口) .DOM 描绘了一个层次化的节点树,运行开发人员添加.移除和修改页面的某一部分.DOM 产生于 网景公司及微软公司创始的 DHTML(动态 HTML) ,但现在它已经成为表现和操作页面标记的真正跨平台.语言中立的方式. DOM 中的三个字母: D(文档)可以理解为整个 Web 加载的网页文档: O(对象)可以理解为类似 window 对象之类的东西,可以调用属性

深入理解iPhone数据持久化(手把手教你iphone开发 – 基础篇)

在所有的移动开发平台数据持久化都是很重要的部分:在j2me中是rms或保存在应用程序的目录中,在symbian中可以保存在相应的磁盘目录中和数据库中.symbian中因为权限认证的原因,在3rd上大多数只能访问应用程序的private目录或其它系统共享目录.在iphone中,apple博采众长,提供了多种数据持久化的方法,下面笔者会逐个进行详细的讲解. iphone提供的数据持久化的方法,从数据保存的方式上讲可以分为三大部分:属性列表.对象归档.嵌入式数据库(SQLite3).其他方法. 一.属