本文通过一个简单的例子说明单元测试的基本方法和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。
在弹出的向导中设置好配置项。
请选择”New JUnit 3 test”
在Source folder中请通过”Browse”按钮选择源代码的文件夹。如:java.test, src/java/test等。
单击下一步后,选中需要测试的方法或者”单元”。
单击”Finish”按钮后,eclipse就将为我们生成单元测试用例类了。
step 3 编写单元测试用例代码
首先需要声明被测试类,以及JMock的context对象Mockery。
然后在setUp方法中对helloUnitTestService和context进行初始化。
注意,setUp方法继承与Junit框架中的TestCase基类。setUp方法是用来在多个测试方法运行过程中进行初始化,和数据准备之类的一个生命周期方法。
如果不是很清楚,可以学习JUnit的Tutorial,网上有很多这样的教程。
所以setUp方法可以写成这样:
@Override
protected void setUp() throws Exception {
super.setUp();
context = new Mockery();
helloUnitTestService = new HelloUnitTestServiceImpl();
}
对于本例来说被测试的方法为sayHelloToUnitTest,它的正常运行决定与两个方面:
- 两个依赖项必须被正确的注入,否则将抛出空指针异常。在单元测试过程中为了测试的速度和性能,以及测试的粒度等诸多原因不可能使用spring容器的方式注入(有疑问可以和我沟通)。
- 两个传入参数必须满足测试用例的需要,即两个入参必须正确的配置并能模拟真实情况下代码被执行的路劲。
基于以上两点,我们的对策是。
- 对于两个依赖项,我们采用JMock的框架来模拟mock对象。即按照mock的思想将需要的依赖项进行mock,得到mock对象,然后通过setter方法注入到被测试对象中,就可以进行JUnit的断言了。JMock的使用请通过JMock的相关资料进行学习,如JMock的 tutorial和JMock使用FAQ等。
- 对与传入的两个参数,我们进行手动的模拟。如: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向导我们就可以方便的运行用例进行测试并得到测试结果。
此外还可以通过antx test以及 mvn test等方法运行一组测试,以及获取代码覆盖率的报告。
step 5 其他说明
- 若该方法有多个分支,可以在这个测试用例类中增加其他测试分支的方法。如通过 testSayHelloToUnitTestTestWhileDependencyThrowsException的方法名,重新编写代码来测试依赖项抛出异常情况下的逻辑分支。PS:抛出异常可以通过JMock来模拟出来。
- 对于没有依赖关系的方法,可不使用JMock。
- 正确了解被测试类,被测试方法的逻辑是书写单元测试用例代码的关键。
- JUnit的使用,如何断言,以及JMock的使用技巧等可以通过网络途径来学习。
小结
本文通过一个简单例子展示了Junit测试用例的创建方法,以及JMock框架的基本使用方法,意在指引读者对单元测试有一个初步认识。单元测试在实际执行过程中还有很多经验和技巧需要学习和掌握的,希望此文能够抛砖引玉,吸引读者不断学习和掌握单元测试相关的技巧。












最近评论