Flex单元测试 – Mocking

如果你最近或者以前做过单元测试,那么你肯定觉得这玩意有时候还真是挺困难的。可能你会遇到下面的重重挑战:

  • 你需要在不深入细节的情况下,确保某些事件的发生。
  • 写一个单项测试太费劲了。
  • 有些测试根本不在你的掌控之内。

所以我想到了使用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能用,所以我们根本没必要测试。我们真正要做的是确保调用了这个方法,其他的我们并不关心。

你不必深入就可以需要确保某些事件的发生。

我们使用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 框架完成了单元测试。虽然当中遇到了些困难,但问题全都都迎刃而解。现在你得再接再厉,继续完成更多精彩的测试。

 

www.taskcity.com

taskcity.com将您的技术转化为美金。

你可能感兴趣的:(单元测试,mocking,flex,function,class,测试)