Junit&Jmock使用简介

Junit&Jmock简介
序言 2
1. 环境配置 2
2.一种比较好的命名方式 3
3. JUnit使用入门 4
3.1一种简单的实现 4
3.2添加初始化和销毁方法的实现 5
3.3对Java异常(Exception)的单元测试 8
3.4 Assert类 9
3.5小结 12
4.Jmock使用入门 12
4.1一种简单的实现 13
4.2 Expectations类 18
4.3小结 21
5.总结 22




 
序言
本文的写作目的旨在帮助大家用最少的时间学会使用Junit和Jmock,并能够使用它们对Java程序进行单元测试。并不拘礼它们的实现原理,所以下面从最佳实践的角度,介绍一种使用Junit和Jmock常用的方式。


Junit从4.0版本开始做了较大的改动,根据Java5.0的新特性(注解、静态导入等)构建而成。Junit最新版本应该是大于等于4.9的。本文 选择相对成熟稳定的版本4.8.2。JUnit4提供了3种机制进行单元测试,大家所熟悉的断言方式,以及新的假设机制和理论机制。假设机制和断言的使用 方式差不多,唯一不同的是假设机制出错时并不中断测试用例的执行,理论机制时对那些需要无穷个测试用例才能正确描述的代码行为的概括性陈述。由于假设机制 和理论机制使用得并不多,本文仅针对断言机制展开介绍。


Jmock当前最新的稳定版本为2.5.1,本文选择该该版本对Jmock 的基本使用情况进行介绍。可以把JMock当做JUnit测试的一个很好的补充,因为有的情况,仅仅使用JUnit很难完成测试,这样的情况有真实对象具 有不可确定的行为,产生不可预测的效果;真实对象很难被创建的;真实对象的某些行为很难被触发;真实对象实际上还不存在的(和其他开发小组或者和新的硬件 打交道) 等等。结合JMock对Java源代码进行JUnit测试,能够基本完成所有单元测试的需求。Jmock是一个利用Mock对象来测试Java代码的轻量 级工具。所谓Mock对象,就是用来模拟真实对象,通过真实对象的行为与被测试对象进行交互,从而实现对被测对象进行测试的目的。一般在被测对象需要与其他真实对象进行交互或者依赖于其他真实对象时需要用到Mock对象。


本文的安排是这样的,第1章介绍Junit和Jmock环境的配置。第2章介绍Junit使用的最佳实践,首先介绍了一下命名方式,接着介绍一种简单的 Junit使用方式,这种方式几乎能够满足大部分需求,2.3节在2.2节使用的基础上,添加初始化和销毁方法的实现。2.4节简要介绍了对抛出异常进行 单元测试的方法。这几种方式几乎能够满足所有使用Junit进行测试的需求。本文最后介绍了Assert类的详细信息。
1. 环境配置
1.1 环境准备
1. Eclipse(版本无特殊规定)
2. Junit4.8.2(版本 >= 4.0,本文的测试版本为4.8.2)
3. JMock2.5.1(当前最新的稳定版本)




Eclipse下载地址:http://www.eclipse.org/downloads/
junit-4.8.2.jar, 下载地址见https://github.com/KentBeck/junit/downloads
JMock2.5.1, 下载地址见http://www.jmock.org/download.html


1.2 环境配置
Junit的配置特别简单,如下所示:
1. 选择Eclipse中Project,单击右键->Buid Path->Add External Archive…
2. 在弹出的文件选择对话框中选择junit-4.8.2.jar即可。


JMock的配置跟JUnit的配置一样,只需要导入相应的jar包即可。需要导入的jar包如下所示:
Jmock-2.5.1.jar
Hamcrest-core-1.1.jar
Hamcrest-library-1.1.jar
Jmock-legacy-2.5.1.jar
Cglib-nodep-2.1_3.jar
Objenesis-1.0.jar
2.一种比较好的命名方式
在Project下建立一个源文件夹,命名为test。在test下面建立要测试Java类的Junit测试类,命名规则为在Java类后面添加 Test,表示该类为src下某个Java类的Junit测试类。显然,test下面这个类的package路径和src下对应类的package路径是 一样的。这样一方面保证测试用例与实际项目分离,另一方面也方便了对测试用例的管理。命名结果如下图所示。
 
针对src中的类,在test中对应的类中添加相应的测试方法,方法基本格 式为:public void testXxx() {}, 其中方法名并不要求必须是以test开头的形式,但以test开头是很好的使用习惯,能够一目了然的发现这个测试用例是测试的哪个方法。并且在Junit 早期的版本中要求必须使用testXxx的形式为测试方法命名,为了兼容性,也应该这样命名。添加了测试方法后的效果图如下所示。
 
这种方式能够通过测试方法一目了然的知道该方法是测试的哪一个业务逻辑类中的哪一个方法。这种方式还有一个优点是可以通过脚本对test目录下的所有类进行自动化测试,当测试用例过多时是很好的一种方式。
3. JUnit使用入门
3.1一种简单的实现


1. 编写Logic1.java类的内容,如下所示:

public class Logic1 {

public int abs(int n) {
return n >= 0 ? n : (-n);
}


}


2. 编写Logic1Test.java类的内容,如下所示:


import org.junit.Assert;
import org.junit.Test;


public class Logic1Test {    //注意,并不需要继承其它类

@Test      //注意,Test注解为必须
public void testAbs() { 
Logic1 logic1 = new Logic1();
int i = -15;

Assert.assertEquals(i, logic1.abs(i));
}
}


在Logic1Test类中单击右键->Run as->Junit Test即可运行,当Assert得到错误时单元测试会不成功,更详细的Assert操作将在后续介绍。


上面的方法将业务逻辑相关的类放在测试方法中进行初始化,当初始化工作较多且多个测试方法需要共用一些逻辑类时,这种方法比较低效。下面介绍一种解决上述问题的方法。
3.2添加初始化和销毁方法的实现
添加初始化和销毁方法可以使用下面几个Java注解实现,它们分别是:@Before、@After、@BeforeClass和@AfterClass,其解释如下表所示:


@Before 测试方法执行前,每一个测试方法执行前均会调用一次
@After 测试方法执行后,每一个测试方法执行后均会调用一次
@BeforeClass 测试方法执行前,每一个类仅执行一次,要求方法为static
@AfterClass 测试方法执行后,每一个类仅执行一次,要求方法为static


 @Before和@After使用案例


public class Logic2 {

public int abs(int n) {
return n >= 0 ? n : (-n);
}


}


import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;


public class Logic2Test {

Logic2 logic2;

@Before
public void init() {
System.out.println("Before Test method...");
logic2 = new Logic2();
}

@After
public void destory() {
System.out.println("After Test method...");
}

@Test
public void testAbs1() {
System.out.println("run Test method abs1...");
Assert.assertEquals(7, logic2.abs(7));
}

@Test
public void testAbs2() {
System.out.println("run Test method abs2...");
Assert.assertEquals(8, logic2.abs(-8));
}
}


运行测试用例后结果如下:
Before Test method...
run Test method abs1...
After Test method...
Before Test method...        //注意:每个方法初始化了一遍
run Test method abs2...
After Test method...




 @BeforeClass和@AfterClass使用案例

public class Logic3 {

public int abs(int n) {
return n >= 0 ? n : (-n);
}


}


import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;


public class Logic3Test {

static Logic3 logic3;         //注意static

@BeforeClass
public static void init() {         //注意static
System.out.println("Before Test method...");
logic3 = new Logic3();
}

@AfterClass
public static void destory() {            //注意static
System.out.println("After Test method...");
}

@Test
public void testAbs1() {
System.out.println("run Test method abs1...");
Assert.assertEquals(7, logic3.abs(7));
}

@Test
public void testAbs2() {
System.out.println("run Test method abs2...");
Assert.assertEquals(8, logic3.abs(-8));
}
}


运行测试用例后结果如下:
Before Test method...               //注意:仅初始化一次
run Test method abs1...
run Test method abs2...
After Test method...


这种方式保证了初始化和销毁操作只进行一次,但要求@BeforeClass和@AfterClass所修饰的方法为static,由此需要相应的类也需要设置为static。


3.3对Java异常(Exception)的单元测试
有时需要测试业务逻辑方法是否按要求抛出了异常,Junit对这个需求也有很好的支持,通过在Test注解中添加expected选项参数指定可能抛出的异常。实际例子如下所示:


public class Logic4 {

static class SpecialException extends Exception 
{
private static final long serialVersionUID = 1L;

public SpecialException() {}

public SpecialException(String msg) {
super(msg);
}
}

public void testException(boolean isTriggerException) throws SpecialException 
{
if (isTriggerException) {
throw new SpecialException("This is a exception for junit test!");
}else {
//do nothing...
}

}


}


import org.junit.Test;


public class Logic4Test {    

@Test(expected=Logic4.SpecialException.class)      
public void testTriggerException() throws Logic4.SpecialException {
Logic4 logic4 = new Logic4();

//logic4.testException(false);    //单元测试失败
logic4.testException(true);    //单元测试成功
}
}

需要注意的是,不能在testTriggerException方法中对可 能抛出的异常进行拦截处理,如try-catch的方法,这使得异常并没有抛到Junit框架里面去,导致单元测试始终为失败,这里不需要处理异常,直接 将异常继续抛出去,Junit框架会感知这个异常,并判断与expected参数设置的异常是否一致,从而判断单元测试是否成功。
3.4 Assert类
Junit中提供了一个Assert类,该类封装了大量断言的方法,如assertEquals、assertNotNull等。Assert在线文档见:http://www.junit.org/apidocs/org/junit/Assert.html
使用该类有两种方式:
1. 直接使用,如:Assert.assertEquals(param1, param2);
2. 先静态导入再使用,如:
Import static org.junit.Assert.assertEquals;
assertEquals(param1, param2);    //直接使用


下面对常用的断言方法进行总结,以方便使用时索引,如下表所示:
assertEquals 断言两个参数是否相等
assertArrayEquals 断言两个参数数组是否相等
assertNotNull 断言参数是否为空
assertSame 断言两个参数是否相等,为对象时,是否为同一对象引用
assertTrue 断言参数是否为真
assertThat 断言参数是否满足某种条件,JUnit4新语法,后续会介绍
fail 断言失败,即测试用例测试失败。当业务逻辑复杂以至于使用上述方法很难实现断言测试时,可自己写业务逻辑,在不匹配时通过fail指定断言失败


值得一提的是,对所有double类型的参数进行断言时,和其他类型的断言方式都不一样,由于浮点数在计算机中存储会存在一定的误差,所以还需要一个误差参数,表明在误差允许的范围之内判断断言两个double类型的数据是否相等。


Assert类的使用案例如下所示:
package example;


import static org.junit.Assert.*;


import org.hamcrest.CoreMatchers;
import org.hamcrest.core.Is;
import org.junit.Test;


public class AssertUseTest {

@Test
public void testAssert() {
long i = 5, j = 5;
assertEquals(i, j);

double m = 5.5, n = 5.6, delta = 0.5;
assertEquals(m, n, delta);

int[] arr1 = {3, 5, 6};
int[] arr2 = {3, 5, 6};
assertArrayEquals(arr1, arr2);

Object o = new Object();
assertNotNull(o);

Object o1 = new Object();
assertNotSame(o, o1);

o1 = o;
assertSame(o, o1);

boolean flag = true;
assertTrue(flag);

boolean hasFailed = true;
if (hasFailed) {
//fail();            //引发断言失败
}

assertThat("123456", Is.is("123456"));
assertThat(1, CoreMatchers.is(1));

//假设出错时,后续代码不会执行,测试用例正确结束,继续执行其他测试用例
//Assume.assumeTrue(false);   //halt
//System.out.println("halt or not?");
}
}
assertThat
assertThat是JUnit4支持的一个新的断言方法。assertThat引入了Hamcrest测试框架,这个框架提供了一套通用的匹配符Matcher,灵活使用这些匹配符定义的规则,能够更加精确的表达测试思想,指定所想设定的测试条件。
assertThat语法如下:
public static <T> void assumeThat(T actual, Matcher<T> matcher);
其中,actual是接下来想要测试的变量值。Matcher是使用Hamcrest匹配符来表达的对前面变量所期望值得声明,如果actual与matcher表达式所表达的期望值相符,则测试成功,否则测试失败。
Matcher在Hamcrest中为一个接口,文档中显示,已经实现该接口的类如下图所示。
 
具体每个类的意义请参考其文档。下面以Is为例,说明assertThat的用法如下:

assertThat("123456", Is.is("123456"));
assertThat("12345", IsNot.not("123456"));
assertThat(o, IsSame.sameInstance(o1));

assertThat(o, 
AllOf.allOf(
IsSame.sameInstance(o1), 
IsNot.not(new Object()), 
Is.is(o)));

List matchers = new ArrayList(); matchers.add(IsSame.sameInstance(new Object()));  //测试为假 matchers.add(Is.is(o1)); assertThat(o, AnyOf.anyOf(matchers)); //自定义Matcher          //其中CoreMatchers实现了上面提到类的所有表达式,可直接使用 assertThat(1, CoreMatchers.is(1)); assertThat(o, CoreMatchers.anyOf(matchers)); 如上所述,如果assertThat用得好,可以完全替代Assert类中的其他断言方法,但对于简单的测试用例,Assert提供的测试方法已经足够使用。 3.5小结 本章主要还是通过实例的方式,从Junit使用的最佳实践入手,介绍了使用Junit的方式。总的来说包括Junit使用的命名方式,这种方式能够 通过测试方法一目了然的知道该方法是测试的哪一个业务逻辑类中的哪一个方法。通过一种简单的Junit实现介绍了如何对Java业务逻辑方法进行单元测 试,通常情况下,这种方式已经能够满足大部分需求。当初始化工作较多且多个测试方法需要共用一些逻辑类时,这种方法比较低效,可以使用2.2节中介绍的 @Before、@After、@BeforeClass、@AfterClass很好的解决这个问题。2.3节简要介绍了对抛出异常进行单元测试的方 法。 4.Jmock使用入门 在某些情况下:如有一个A类,其依赖于B类,然后B类很难被真实创建或者还根本就没有被实现,在这种情况下,使用JUnit对其测试有些力不从心,无从下 手,而Jmock很好的补充了JUnit在这种情况下的缺陷,JMock通过模拟一个Mock对象用于取代真实的B类对象,模拟的Mock对象有着B类同 样的接口,从而使得A类的方法能够执行成功,这样才能对A类进行JUnit测试。 可以把JMock当做JUnit测试的一个很好的补充,结合JMock对Java源代码进行JUnit测试,能够基本完成所有单元测试的需求。 下面来看Jmock如何使用,Jmock的使用也特别简单,基本逻辑分为如下几步: 1. 创建一个Mockery对象,如下所示: Mockery context = new Mockery(); 2. 创建Mock对象,非常简单,如下所示: final T t = context.mock(T.class);    //这里需要final,后面在子类中会用到 3. 对Mock对象添加预定的期望,代码如下所示: Context.checking(new Expectations() { { //Expectations的语法,需要放在一对大括号之内 oneOf(subscriber).receive(message);  will(returnValue(message); inSequence(seq); //其中期望类Expectations将在3.2节详细介绍 } }); 4. 被测试对象的业务逻辑调用 5. 查看Mock是否满足被测结果,代码如下: context.assertIsSatisfied(); 4.1一种简单的实现 下面同样通过一个简单的实例来说明Jmock如何使用。该实例涉及一下几个类,如下图所示。该实例想要测试登录功能是否正确,但由于登录功能 LoginAction类依赖于DatabaseConnection和DatabaseManager两个接口。这两个接口具体的实现与采用的数据库相 关,并且此时并没有实现这两个接口,甚至还没有决定要选择哪一个数据库产品呢?对于这种情况的测试,Jmock能够很好的完成需求,下面就来看一下这几个 类的源代码。   public class LoginAction { DatabaseConnection databaseConnection; DatabaseManager databaseManager; public void setDatabaseConnection(DatabaseConnection databaseConnection) { this.databaseConnection = databaseConnection; } public void setDatabaseManager(DatabaseManager databaseManager) { this.databaseManager = databaseManager; } public String login(String username, String password) throws DatabaseConnException { if (! databaseConnection.isConnectioned()) { throw new DatabaseConnException("database connection error"); } boolean isExistAccount = databaseManager.isExistAccount(username, password); if (true == isExistAccount) { return "login successed"; } return "login failed"; } } DatabaseConnection和DatabaseManager接口源码如下: public interface DatabaseConnection { public boolean isConnectioned(); } public interface DatabaseManager { public boolean isExistAccount(String username, String password); } public class DatabaseConnException extends Exception { private static final long serialVersionUID = 1L; public DatabaseConnException() {} public DatabaseConnException(String message) { super(message); } } LoginAction类的单元测试类为LoginActionTest,其源码如下所示: import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.Sequence; import org.junit.Assert; import org.junit.Test; public class LoginActionTest{     //还记得JUnit单元测试里面的注解吗?? @Test public void testLoginSuccessedExample() throws DatabaseConnException { Mockery context = new Mockery();         //创建两个Mock对象,用于模拟真实对象 final DatabaseConnection databaseConnection =  context.mock(DatabaseConnection.class); final DatabaseManager databaseManager = context.mock(DatabaseManager.class); LoginAction loginAction = new LoginAction(); loginAction.setDatabaseConnection(databaseConnection); loginAction.setDatabaseManager(databaseManager); //创建了一个Expectations类的实例,下一节将详细介绍Expectations类 context.checking(new Expectations() { {                 //对这Mock对象方法调用时的期望,如此处设置希望输入参数为”root”和”root”时,返回true oneOf(databaseManager).isExistAccount("root", "root"); will(returnValue(true)); oneOf(databaseConnection).isConnectioned(); will(returnValue(true)); } });          String s = loginAction.login("root", "root");         //JMock测试,测试Mock对象调用方法时的期望是否满足 context.assertIsSatisfied();         //JUnit单元测试方法,测试登录是否成功(注意Mock对象扮演的角色) Assert.assertSame("login successed", s); } @Test public void testLoginFailedExample() throws DatabaseConnException { Mockery context = new Mockery(); final DatabaseConnection databaseConnection = context.mock(DatabaseConnection.class); final DatabaseManager databaseManager = context.mock(DatabaseManager.class); LoginAction loginAction = new LoginAction(); loginAction.setDatabaseConnection(databaseConnection); loginAction.setDatabaseManager(databaseManager);         //注意顺序的设置,跟login方法中两个方法的调用顺序有要求 final Sequence seq = context.sequence("loginActionTest"); context.checking(new Expectations() { {                 //此处期望先调用isConnectioned,然后isExistAccount方法                 //看看login方法中是否也是这样的呢,不是的话,测试就会失败 oneOf(databaseConnection).isConnectioned(); will(returnValue(true)); inSequence(seq); oneOf(databaseManager).isExistAccount("root", "root"); will(returnValue(false)); inSequence(seq); } }); String s = loginAction.login("root", "root"); context.assertIsSatisfied(); Assert.assertSame("login failed", s); } //测试抛出异常的情况 @Test(expected=DatabaseConnException.class) public void testLoginThrowsDatabaseConnException() throws DatabaseConnException { Mockery context = new Mockery(); final DatabaseConnection databaseConnection = context.mock(DatabaseConnection.class); final DatabaseManager databaseManager = context.mock(DatabaseManager.class); LoginAction loginAction = new LoginAction(); loginAction.setDatabaseConnection(databaseConnection); loginAction.setDatabaseManager(databaseManager); context.checking(new Expectations() { { oneOf(databaseConnection).isConnectioned(); will(returnValue(false)); } }); loginAction.login("root", "root"); context.assertIsSatisfied(); } } 对上述代码的解释,以上对LoginAction类的login方法进行了3个单元测试,这3个单元测试方法能够正常结束。 第一个单元测试方法为testLoginSuccessedExample方法,由于Mock对象模拟了两个,方法均返回true,所以login方法将 返回字符串s,注意最后一行Assert类对这个login方法返回的字符串与“login successed”进行的比较,断言这两个字符串相同,如果不同的话,单元测试方法将失败。 第二个单元测试方法为testLoginFailedExample,Mock对象期望isExistAccount方法返回false,此时login方法调用返回的字符串s与”login failed”比较,相同则单元测试通过。 第三个单元测试方法为 testLoginThrowsDatabaseConnException,用于测试login方法抛出DatabaseConnException异 常,只要Mock对象期望isConnectioned返回false,这是login方法就会引发异常。而在期望表达式的设置时,确实将其返回值设置为 false,因此测试方法将正确执行,因为login方法确实会抛出DatabaseConnException异常。 针对类进行JMock测试 上述例子是针对接口进行Mock测试的例子,这也是通常使用JMock的情 况,但有时也想要针对具体的类进行Mock测试,这种情况的mock测试和针对接口的方式几乎一致,仅在创建Mock对象时有少许不同,需要在创建 mock对象之前添加如下代码:context.setImposteriser(ClassImposteriser.INSTANCE);   //需要添加的代码 final Msg msg = context.mock(Msg.class);    //与接口方法演示的一样 4.2 Expectations类 Expectations API在线文档见: http://www.jmock.org/javadoc/2.0.0/org/jmock/Expectations.html Expectations的语法如下表所示: invocation-count (mock-object).method(argument-constraints);  [必需] inSequence(sequence-name); when(state-machine.is(state-name)); will(action); then(state-machine.is(new-state-name)); 针对Mock对象模拟调用的每一个方法,都应该必须有一个应该必须有一个invocation-count子句,其余子句为可选。 下面对这几个表达式分别作简要的介绍: invocation-count invocation-count表示期望的方法的调用次数。其可选的表达形式如下表所示: oneof / one 期望调用执行一次且仅一次 exactly(n).of 期望调用执行n次 atLeast(n).of 期望调用执行至少n次 atMost(n).of 期望调用执行至多n次 between(min, max).of 期望调用执行至少min次,至多max次 allowing / ignoring 期望调用执行任意多次,包括0次 never 不期望调用执行 具体使用如下所示: exactly(4).of(subscriber).receive(message);  atMost(2).of(subscriber).receive(message);   atLeast(4).of(subscriber).receive(message);  atLeast(2).of(subscriber).receive(message);   argument-constraints表示对传入参数的约束条件,可以使精确匹配的,也可以利用with语句定义模糊匹配条件,如下面的对比所示: one (calculator).add(1, 1);    // allowing (calculator).add(with(any(int.class)), with(any(int.class))) 除了any(Class<T> type)参数约束子句外,jmock还提供了一下约束子句equal(n)、same(o)、a(Class<T> type)、aNull(Class<T> type)、aNonNull(Class<T> type)、not(m)、anyOf(m1, m2, …,mn)、allOf(m1,m2,…,mn)等,具体可参加Expectations文档: http://www.jmock.org/javadoc/2.0.0/org/jmock/Expectations.html inSequence inSequence用于定义多个方法调用的顺序,inSequence子句可以定义多个其在测试代码中出现的执行顺序。使用案例如下所示: context.setImposteriser(ClassImposteriser.INSTANCE); final Msg msg = context.mock(Msg.class); publisher.setMsg(msg); final Sequence sequence = context.sequence("sequence"); context.checking(new Expectations() { { oneOf(msg).method1(message); inSequence(sequence);    //按顺序执行  will(doAll(returnValue("test1"), returnValue(5))); oneOf(msg).method2(message); inSequence(sequence);     //按顺序执行 will(returnValue("yes")); } }); publisher.test(); /*   public void test() { msg.method1("test"); msg.method2("test");   } */ context.assertIsSatisfied(); sequence名称任意,有该行子句修饰的Expectations表达 式表示需要统计其执行顺序,为使用该子句修饰的表示不需要判断其顺序。上述代码中,sequence相当于一个标识,这个标识修饰的所有方法为一组顺序执 行的方法,这组方法的顺序与业务逻辑代码(上述代码中test方法)中被调用的顺序相同时,执行才能成功。 will will表示方法调用返回情况的约束条件。Wil语句还是非常有用的,对于Mock对象模拟调用方法时,就靠will子句返回调用方法返回值,从而驱动JUnit测试的正常执行。Jmock支持的返回约束形式如下表所示: will(returnValue(v)) 期望返回值v will(returnIterator(c)) 期望返回容器c的一个迭代子 will(returnIterator(v1,v2,…,vn)) 期望返回容器c中的元素v1到vn will(throwException(e)) 期望抛出一个异常e will(doAll(a1,a2,…,an)) 与其他will子句组合使用,代表每次调用时执行从a1到an的若干动作,其中任意一个子句返回类型与实际调用内型匹配,则成功 具体使用如下所示:  /*oneOf(msg).print(message); will(returnValue(5));*/ /*oneOf(msg).print(message); will(returnIterator("test1", "what"));*/ /*oneOf(msg).print(message); will(throwException(new IOException("no")));*/ /*oneOf(msg).print(message); will(doAll(returnValue("test1"), returnValue("test2")));*/ when & then 当想要控制某些方法仅当某个条件达到时才能执行的情况时,可以使用when 子句。When子句需要一个状态作为参数,这个状态也就相当于一个开关,当这个状态满足某种情况时,及开关打开时就执行对应的方法,否则不执行。 Expectations使用一个字符串用于初始化一个状态(States),然后使用is(str)或者isNot(str)判断状态十分满足情况,从 而控制方法是否执行。具体使用如下所示: //同样选用上面提到的sequence例子 context.setImposteriser(ClassImposteriser.INSTANCE); final Msg msg = context.mock(Msg.class); publisher.setMsg(msg); final Sequence sequence = context.sequence("sequence"); //字符串”up”为任意取名的,仅states.is(“up”)用于判断是否满足条件 final States states = context.states("states").startsAs("up"); context.checking(new Expectations() { { oneOf(msg).method1(message); inSequence(sequence);    //按顺序执行  when(states.is("up")); will(doAll(returnValue("test1"), returnValue(5))); then(states.is("down")); oneOf(msg).method2(message); inSequence(sequence);     //按顺序执行 when(states.is("up")); will(returnValue("yes")); } }); publisher.test(); context.assertIsSatisfied(); 上述测试用例并不会成功,会报下面的错误,其原因就在于 inSequence()子句规定了两个方法执行的顺序,而最终有一个期望的方法并没有执行。原因就在于then()语句,then子句在方法自行完成 后,会将states中的状态字符串设置为”down”,而在method2执行时,判断其状态字符串是不是”up”时将得到否定的结果,从而该方法不执 行,于是就出现了下面的错误。   when子句所设定的条件满足时执行相应的方法,而then子句是在方法执行完成之后执行。 4.3小结 本章对Jmock做了简要的介绍,通过实例说明了怎样通过Jmock协助JUnit进行特定情况的Java源代码测试,这样的情况还是比较常见的,比如下列情况可能需要使用到Jmock: 真实对象具有不可确定的行为,产生不可预测的效果   真实对象很难被创建的   真实对象的某些行为很难被触发   真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道) 等等 本章接着对Expectations进行了简单的介绍,其中Expectations一节只是给出了各个子句的意义及简单的用法,具体在测试时,还需根据具体的需求设计正确的测试逻辑。 5.总结 本文对JUnit和Jmock进行了简要的介绍,从JUnit和Jmock的环境配置及测试用例的命名方式开始,接着对JUnit和JMock的使用做了 初步介绍,针对JUnit,详细介绍了测试时经常会用到的断言类及其使用方式,而对于JMock,详细介绍了其怎样与JUnit进行配合,完成某些仅仅由 JUnit完成不了的单元测试的使用方式,通过以上的步骤,应该能够轻松实现对Java程序的JUnit测试和JMock测试。

posted @ 2014-08-30 00:08  beyondaotian  阅读(4301)  评论(0编辑  收藏  举报