在进行单元测试时,为了测试一个简单的方法,往往需要构造一堆复杂的相当对象,还有的情况是有的方法没有返回值,直接在函数内做了某些事情,这时如果用黑盒测试,你无法通过输入输出判断方法是否正确。所以,通常都会通过mock对象的方法来进行unit test. 有机会再总结软件测试一些理论知识,今天先把这几天写unit test中常用的powerMock使用方法写一下(所有例子都是直接在网页内敲出来的,不能直接运行)。
1. mock 一个测试时不方便创建的对象,并设定public方法的预期行为。
例如MediaManager类中的方法:
public int getSystemVolume()
{
context.setAudioEnable(true);
Audio audio = context.getSystemAudio(Context.AUDIO);
return audio.getStreamVolume();
}
其中的context对象是平台相关的,在测试程序中根本无法创建些Context的对象,在测试时我认为只要程序能去执行平台相关的这个对象的这个方法,这个方法就是正确的。
测试如下
@RunWith(PowerMockRunner.class)
@PrepareForTest(MediaManager.class)
public class MediaManagerTest
{
private Context context;
private MediaManager mediaManager;
@Before
public void setUp() throws Exception
{
context = PowerMock.createMock(Context.class);
mediaManager = new MediaManager(context);
}
@Test
public void testGetSystemVolume()
{
PowerMock.resetAll();
Audio audio = PowerMock.createMock(Audio.class);
context.setAudioEnable(true);//Mock对象的方法返回值为void时,这样写表示预期被调用一次
EasyMock.expect(context.getSystemAudio(Context.AUDIO).andReturn(audio);//表示context对象的getSystemAudio方法将会调用一次,参数是Context.AUDIO,设定它的返回值为audio.
EasyMock.expect(audio.getStreamVolume()).andReturn(20);//audio的getStreamVolume()方法将会调用一次,设定它的返回值为20
PowerMock.replayAll(); //录制
int volume = mediaManager.getSystemVolume();
PowerMock.verifyAll();//验证上面录制的对象中的方法有没有按预期被调用
assertEquals(volume, 20);
}
}
2.Mock 静态的方法.
如果对一个Mock对象的静态方法进行预期设定,还需要做一些工作才能实现(1)在PrepareForTest里添加此方法的类名.(2)在Mock静态方法前声明PowerMock.mockStatic(Class.class),如果上例中的Context的setAudioEnable为静态方法,测试程序如下:
@RunWith(PowerMockRunner.class)
@PrepareForTest({MediaManager.class,Context.class})
public class MediaManagerTest
{
private Context context;
private MediaManager mediaManager;
...............
@Test
public void testGetSystemVolume()
{
PowerMock.resetAll();
PowerMock.mockStatic(Context.class);
Context.setAudioEnable(true);//Mock对象的方法返回值为void时,这样写表示预期被调用一次
Audio audio = PowerMock.createMock(Audio.class);
EasyMock.expect(context.getSystemAudio(Context.AUDIO).andReturn(audio);//表示context对象的getSystemAudio方法将会调用一次,参数是Context.AUDIO,设定它的返回值为audio.
EasyMock.expect(audio.getStreamVolume()).andReturn(20);//audio的getStreamVolume()方法将会调用一次,设定它的返回值为20
PowerMock.replayAll(); //录制
int volume = mediaManager.getSystemVolume();
PowerMock.verifyAll();//验证上面录制的对象中的方法有没有按预期被调用
assertEquals(volume, 20);
在有些特殊情况下,如果Context为子类,还需要将其基类名加入到类名的@PrepareForTest中的列表中。
}
}
3. Mock 创建新对象 和 参数匹配
如果在你被测试的方法内部有这样两句话
Camera camera = new Camera( int length);
camera.open("thePath"); // path 是前面已有的某个String类对象
camera.setFileName(new String[ ]{ "id"}, 20);
因为Camera是设备相关的,在测试环境内无法创建此种类型对象,而此时又无法自己Mock一个Camera传进来,所以只能在它 new Camera()时给它替换一个自己Mock的对象,这种情况可以用PowerMock.expectNew()来实现。针对上面代码可以用如下实现Mock:
Camera camera = PowerMock.createMock(Camera.class); //先Mock出一个用于替换的对象
PowerMock.expectNew(Camera.class, EasyMock.anyInt()).andReturn(camera );
camera.open(EasyMock.anyObject(String.class)); //此处表示检验程序时open()方法中传入的只要是String类对象
//就满足条件,对具体对象不敏感
camera.setFileName(EasyMock.aryEq(new String[]{"id"}), EasyMock.eq(20) );
上面的EasyMock.anyInt()表示无论被测试方法中的new Camera()中实际传的参数是什么值,只要是个Int值,都满足被Mock对象替换掉的条件,使用EasyMock.anyObject(Class.class),可以匹配类类型,这样可以使检验时不用指定严格的参数值。
上面对setFileName()进行mock时,直接new 一个同样的String数组传进去,并不能通过验证,因为数组的内容相等但它的值不相等,所以你传的值和它预期的值不相同,故通不过测试,而EasyMock.aryEq(new String[]{"id"}则可以通过验证,验证时它会验证数组内容而不数组对象本身。需要注意的是如果方法有多个参数,其中一个使用了这种匹配的验证方法,其它的也得使用匹配验证方法。