Skip to content


单元测试HelloWorld

本文通过一个简单的例子说明单元测试的基本方法和jmock的基本技巧。JMOCK的使用请参考JMock使用FAQ,和JMock cookbook.

根据定义单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。所以我们首先来看看需要测试的类和其接口。

step 1 了解被测试代码

被测试类为HelloUnitTestServiceImpl ,它实现了IHelloUnitTestService接口。接口代码如下:


public interface IHelloUnitTestService {
public Object sayHelloToUnitTest(Object argument1,Object argument2);
}

实现类代码如下:


public class HelloUnitTestServiceImpl implements IHelloUnitTestService {
IFirstDependency  firstDependencyObj;
ISecondDependency secondDepedencyObj;

/**
* 需要被测试的方法。该方法有连个参数。实现中依赖到两个对象firstDependencyObj和secondDepedencyObj
*
*/
public Object sayHelloToUnitTest(Object argument1, Object argument2) {
if (argument1 == null) {
throw new IllegalArgumentException("第一个参数不能为空,否则报错");
}

//通过第一个依赖的对象对第一个参数进行运算
Object theFirstResult = firstDependencyObj.doSomeThingWith(argument1);
//通过第二个依赖的对象对第二个参数进行运算
Object theSecondResult = secondDepedencyObj.doSomeOtherThingWith(argument2);
//通过自己的私有方法对两个运算结果进行处理
Object sayHelloResult = doSomeThingPrivately(theFirstResult, theSecondResult);

//返回结果
if (sayHelloResult instanceof String)
return sayHelloResult;
else
return "结果不是String,所以我来代替!";
}

/**
* 该是私有方法对两个参数进行简单的toString相加的运算
* @param theFirstResult
* @param theSecondResult
* @return
*/
private Object doSomeThingPrivately(Object theFirstResult, Object theSecondResult) {
Object result = "Hello, " + theFirstResult.toString() +" "+ theSecondResult.toString()+"!";
return result;
}   //省略setter方法

值得说明的是,实现类需要通过两个依赖的类来处理sayHelloToUnitTest()方法的逻辑。即:


IFirstDependency  firstDependencyObj;
ISecondDependency secondDepedencyObj;

对于基于spring框架的应用来说,我们知道这两个依赖项,将会被spring容器通过setter方法注入到HelloUnitTestServiceImpl类。

step 2 创建JUnit测试用例

首先我们通过eclipse中的Junit的向导,新建一个JunitTestCase。

Wizard

在弹出的向导中设置好配置项。

请选择”New JUnit 3 test”

Wizard2

在Source folder中请通过”Browse”按钮选择源代码的文件夹。如:java.test, src/java/test等。

Wizard3

单击下一步后,选中需要测试的方法或者”单元”。

Wizard4

单击”Finish”按钮后,eclipse就将为我们生成单元测试用例类了。

step 3 编写单元测试用例代码

首先需要声明被测试类,以及JMock的context对象Mockery。

然后在setUp方法中对helloUnitTestService和context进行初始化。

注意,setUp方法继承与Junit框架中的TestCase基类。setUp方法是用来在多个测试方法运行过程中进行初始化,和数据准备之类的一个生命周期方法。

setUp-test-tearDown

如果不是很清楚,可以学习JUnit的Tutorial,网上有很多这样的教程。

所以setUp方法可以写成这样:


@Override
protected void setUp() throws Exception {
super.setUp();
context = new Mockery();
helloUnitTestService = new HelloUnitTestServiceImpl();
}

对于本例来说被测试的方法为sayHelloToUnitTest,它的正常运行决定与两个方面:

  1. 两个依赖项必须被正确的注入,否则将抛出空指针异常。在单元测试过程中为了测试的速度和性能,以及测试的粒度等诸多原因不可能使用spring容器的方式注入(有疑问可以和我沟通)。
  2. 两个传入参数必须满足测试用例的需要,即两个入参必须正确的配置并能模拟真实情况下代码被执行的路劲。

基于以上两点,我们的对策是。

  1. 对于两个依赖项,我们采用JMock的框架来模拟mock对象。即按照mock的思想将需要的依赖项进行mock,得到mock对象,然后通过setter方法注入到被测试对象中,就可以进行JUnit的断言了。JMock的使用请通过JMock的相关资料进行学习,如JMock的 tutorial和JMock使用FAQ等。
  2. 对与传入的两个参数,我们进行手动的模拟。如:new出对象,并进行set设值。

完成上面两步后,我们还需要对mock的依赖项的行为进行模拟(或者设置期望)。我们知道mock出来的对象虽然可被成功的以setter的方式注入,但是mock对象的行为还是未被正确设置,此时若运行测试,只会意外中断。我们可以这样来看,当sayHelloToUnitTest运行时,当运行到firstDependencyObj.doSomeThingWith(argument1)的时候,由于firstDependencyObj对象(即mock对象)并不知道doSomeThingWith该如何处理,或者如何返回期望的返回值。所以测试将被迫在此中断,并会抛出JMock的异常来。

所以sayHelloToUnitTest方法做为被测试方法,在执行过程中对它需要调用的依赖对象的方法(firstDependencyObj.doSomeThingWith(argument1))需要通过JMock的设置期望的方式模拟行为。设置的方法为:


//设置期望
context.checking(new Expectations() {
{
allowing(firstDependencyObj).doSomeThingWith(argument1);
will(returnValue("I Love Beijing"));
allowing(secondDepedencyObj).doSomeOtherThingWith(argument2);
will(returnValue("Tiananmen Square"));
}
});

这里我们设置firstDependencyObj的doSomeThingWith放在遇到argument1( 假定argument1 = “我爱北京”;)参数时,将返回”I Love Beijing”。同样的方法我们设置secondDepedencyObj的doSomeOtherThingWith在argument2时的返回值。

最后测试用例的代码如下:


public class HelloUnitTestServiceImplTest extends TestCase {

public Mockery                  context;
public HelloUnitTestServiceImpl helloUnitTestService;

@Override
protected void setUp() throws Exception {
super.setUp();
context = new Mockery();
helloUnitTestService = new HelloUnitTestServiceImpl();
}

public void testSayHelloToUnitTestTest() {
//准备参数
final Object argument1 = "我爱北京";
final Object argument2 = "天安门";
//mock依赖
final IFirstDependency firstDependencyObj = context.mock(IFirstDependency.class);
final ISecondDependency secondDepedencyObj = context.mock(ISecondDependency.class);

//设置期望
context.checking(new Expectations() {
{
allowing(firstDependencyObj).doSomeThingWith(argument1);
will(returnValue("I Love Beijing"));
allowing(secondDepedencyObj).doSomeOtherThingWith(argument2);
will(returnValue("Tiananmen Square"));
}
});

//依赖注入mock对象
helloUnitTestService.setFirstDependencyObj(firstDependencyObj);
helloUnitTestService.setSecondDepedencyObj(secondDepedencyObj);

//调用被测试的方法
Object actualSayHelloResult = helloUnitTestService.sayHelloToUnitTest(argument1, argument2);

//验证返回值是否为预期
Object expectedSayHelloResult = "Hello, I Love Beijing Tiananmen Square!";
assertEquals(expectedSayHelloResult, actualSayHelloResult);

//验证mock对象的行为是否为期望
context.assertIsSatisfied();
}
step 4 执行用例

通过eclipse向导我们就可以方便的运行用例进行测试并得到测试结果。

run-junit

此外还可以通过antx test以及 mvn test等方法运行一组测试,以及获取代码覆盖率的报告。

step 5 其他说明
  • 若该方法有多个分支,可以在这个测试用例类中增加其他测试分支的方法。如通过 testSayHelloToUnitTestTestWhileDependencyThrowsException的方法名,重新编写代码来测试依赖项抛出异常情况下的逻辑分支。PS:抛出异常可以通过JMock来模拟出来。
  • 对于没有依赖关系的方法,可不使用JMock。
  • 正确了解被测试类,被测试方法的逻辑是书写单元测试用例代码的关键。
  • JUnit的使用,如何断言,以及JMock的使用技巧等可以通过网络途径来学习。
小结

本文通过一个简单例子展示了Junit测试用例的创建方法,以及JMock框架的基本使用方法,意在指引读者对单元测试有一个初步认识。单元测试在实际执行过程中还有很多经验和技巧需要学习和掌握的,希望此文能够抛砖引玉,吸引读者不断学习和掌握单元测试相关的技巧。

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • LinkedIn
  • description
  • TwitThis
  • MySpace
  • StumbleUpon
  • Google
  • FriendFeed
  • Mixx
  • Reddit

Posted in 全部, 开发技术. Tagged with .

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Some HTML is OK

(required)

(required, but never shared)

or, reply to this post via trackback.

使用新浪微博登陆