如果你最近或者以前做过单元测试,那么你肯定觉得这玩意有时候还真是挺困难的。可能你会遇到下面的重重挑战:
- 你需要在不深入细节的情况下,确保某些事件的发生。
- 写一个单项测试太费劲了。
- 有些测试根本不在你的掌控之内。
所以我想到了使用mock 对象 来绕过这些单元测试的绊脚石。说白了,mock对象就是可以模拟其他对象行为的一种对象,它可以使单元测试易于编写。
想象一下自己正开始投身于一项新技术...
你们公司正打算在明天发布一个在线汽车交易网站。早上十点,你的老板满脸忧愁地站在你的办公桌旁,原因是他居然忘了加入允许用户在网站发布汽车信息的重要功能。晕!所以你的任务就是在今天结束前把这件事搞定,否则你就得拍屁股走人。
于是,你看了看代码,发现服务的方法已经存在,并且可以使用:
package com.compact.mocking {public class VehicleService {
//发布方法
public function publish ( item: Vehicle) : void {
...
}
}
}
太棒了,现在要做的事就是确保从GUI上能够调用这个方法。
package com.compact.mocking {public class VehicleModel {
public var service: VehicleService;
//发布方法
public function publish ( item: Vehicle) : void {
service.publish ( item) ;
}
}
}
很简单,下一步就要开始单元测试,确保我们调用了正确的方法。这听上去可能有些傻,因为我们已经知道VehicleService能用,所以我们根本没必要测试。我们真正要做的是确保调用了这个方法,其他的我们并不关心。
你不必深入就可以需要确保某些事件的发生。
我们使用flex%2Fwiki%2FHome">mockito-flex 来创建我们服务的mock版本。我们要做的是用mock版本代替真实版本。多亏了mockito-flex ,我们的mock可以自动记录所有接受到的调用。当publish方法被调用时,我们可以检测到这些记录以便校验。
package com.compact.mocking {import org.mockito.MockitoTestCase;
// MockitoTestCase继承标准的FlexUnit TestCase.
public class CustomerModelTest extends MockitoTestCase {
private var _model: VehicleModel;
public function CustomerModelTest( ) {
//告诉mockito哪个类需要被mock.
super ( [ VehicleService] ) ;
}
override public function setUp( ) : void {
_model = new VehicleModel( ) ;
// 用mock版本替换真实版本.
_model.service = mock( VehicleService) ;
}
public function testPublishDelegatesToService( ) : void {
var item: Vehicle = new Vehicle( ) ;
// 调用我们mock服务的publish方法.
_model.publish ( item) ;
// 校验mock服务接受到正确的调用.
verify( ) .that( _model.service.publish ( item) ) ;
}
}
}
你在5分钟内搞定了一切,令老板对你刮目相看!不过很不幸,胜利十分短暂,因为他又想起了什么...没错,在发布前你必须确保将要发布的汽车信息是有效的。
于是,你又看了看代码,发现确认有效的方法已经存在:
package com.compact.mocking {[ Bindable]
public class Vehicle {
public var make: String ;
public var model: String ;
public var price: Number ;
public var ownerName: String ;
public var locationName: String ;
public function isValid( ) : Boolean {
var valid: Boolean = true ;
valid = valid && notEmpty( make) ;
valid = valid && notEmpty( model) ;
valid = valid && notEmpty( ownerName) ;
valid = valid && notEmpty( locationName) ;
valid = valid && price > 0 ;
return valid;
}
private function notEmpty( value : String ) : Boolean {
return value && value .length > 0
}
}
}
所以你修改了模型以便好好利用这个方法。
package com.compact.mocking {public class VehicleModel {
public var service: VehicleService;
public function publish ( item: Vehicle) : void {
if ( item.isValid( ) ) {
service.publish ( item) ;
}
}
}
}
然后很专业的修改了测试。
package com.compact.mocking {import org.mockito.MockitoTestCase;
public class CustomerModelTest extends MockitoTestCase {
private var _model: VehicleModel;
public function CustomerModelTest( ) {
super ( [ VehicleService] ) ;
}
override public function setUp( ) : void {
_model = new VehicleModel( ) ;
_model.service = mock( VehicleService) ;
}
public function testPublishDelegatesToService( ) : void {
var item: Vehicle = new Vehicle( ) ;
item.make = "a" ;
item.model = "b"
item.price = 5 ;
item.ownerName = "fred" ;
item.locationName = "australia"
_model.publish ( item) ;
verify( ) .that( _model.service.publish ( item) ) ;
}
}
}
工作终于完了,但是你感觉做的工作太多了。你突然记起你的同事Fred正在为确认有效方法加了一些条件,这意味着你还得继续补全这部分测试。
你得费很大劲才能完成一个单项测试。
你希望校验汽车信息不必如此的麻烦。如果用mock创建一个一直都有效的汽车信息岂不很好?没问题,当然可以
package com.compact.mocking {import org.mockito.MockitoTestCase;
public class CustomerModelTest extends MockitoTestCase {
private var _model: VehicleModel;
private var _item: Vehicle;
public function CustomerModelTest( ) {
// 同样mock汽车.
super ( [ VehicleService, Vehicle] ) ;
}
override public function setUp( ) : void {
_model = new VehicleModel( ) ;
_model.service = mock( VehicleService) ;
// 创建mock汽车.
_item = mock( Vehicle) ;
}
public function testPublishDelegatesToService( ) : void {
// 更新mock让isValid()isValid()方法永远返回true
given( _item.isValid( ) ) .willReturn( true ) ;
_model.publish ( _item) ;
verify( ) .that( _model.service.publish ( _item) ) ;
}
}
}
现在老板彻底被你折服了... 你只不过修改了5行测试代码,却没让Fred的更新对你产生任何影响。
哈哈,一切顺利,你正筹划着一次大的提交。现在是下午3点,你正准备喝一杯庆祝一下,然而你的老板再次想起了一些事... 你还得记录每次汽车信息发布的日期,因为用户每天在网站发布一次信息就收费5毛钱。
神奇,服务中已经存在此方法了!
package com.compact.mocking {public class VehicleService {
public function publish ( item: Vehicle) : void {
...
}
public function recordPublishTime( item: Vehicle, time : Date ) : void {
...
}
}
}
没问题,我们只需要在模型中加入对此方法的调用。
package com.compact.mocking {public class VehicleModel {
public var service: VehicleService;
public function publish ( item: Vehicle) : void {
if ( item.isValid( ) ) {
service.publish ( item) ;
service.recordPublishTime( item, new Date ( ) ) ;
}
}
}
}
好了,看起来不错,唯一的问题是我们如何使用mockito来验证此次调用呢?
暂且先试试吧:
public function testPublishRecordsPublishTimeUsingService( ) : void {given( _item.isValid( ) ) .willReturn( true ) ;
_model.publish ( _item) ;
verify( ) .that( _model.service.recordPublishTime( _item, new Date ( ) ) ) ;
}
不幸的是,这根本运行不了,因为校验里用到的数据和模型中创建的数据根本就是不同的日期值,那么你该如何控制日期创建流程以便完成测试呢?
有时候你需要测试一些你无法控制的东西。
幸运的是,这并不难,我们需要一个数据工厂。一旦有了这个工厂,我们就可以使用mock来控制日期创建流程了,就是下面的工厂:
package com.compact.mocking {public class TimeFactory {
public function currentTime( ) : Date {
return new Date ( ) ;
}
}
}
然后,我们修改模型,在模型使用此工厂,而不再使用直接日期创建。
package com.compact.mocking {public class VehicleModel {
public var service: VehicleService;
public var timeFactory: TimeFactory = new TimeFactory( ) ;
public function publish ( item: Vehicle) : void {
if ( item.isValid( ) ) {
service.publish ( item) ;
service.recordPublishTime( item, timeFactory.currentTime( ) ) ;
}
}
}
}
现在我们可以轻松的使用mock工厂来完成测试了。
package com.compact.mocking {import org.mockito.MockitoTestCase;
public class CustomerModelTest extends MockitoTestCase {
private var _model: VehicleModel;
private var _item: Vehicle;
public function CustomerModelTest( ) {
super ( [ VehicleService, Vehicle, TimeFactory] ) ;
}
override public function setUp( ) : void {
_model = new VehicleModel( ) ;
_model.service = mock( VehicleService) ;
_model.timeFactory = mock( TimeFactory) ;
_item = mock( Vehicle) ;
}
public function testPublishRecordsPublishTimeUsingService( ) : void {
var publishDate: Date = new Date ( ) ;
given( _item.isValid( ) ) .willReturn( true ) ;
given( _model.timeFactory.currentTime( ) ) .willReturn( publishDate) ;
_model.publish ( _item) ;
verify( ) .that( _model.service.recordPublishTime( _item, publishDate) ) ;
}
}
}
漂亮! 你搞定了。
你刚刚完成了拯救公司的壮举!你成功地在网站中添加了允许用户发布汽车信息的功能,并且使用了mock对象和mockito-flex 框架完成了单元测试。虽然当中遇到了些困难,但问题全都都迎刃而解。现在你得再接再厉,继续完成更多精彩的测试。
taskcity.com将您的技术转化为美金。



已有