昨天启动TDD之旅。开始一切都很顺利,按照“故事情节”,遵守PF,对方法的调用测试很顺利。接着麻烦来了。由于PascalMock没有事件引发的处理机制(特别是我的事件委托都是私有的)造成在测试事件引发时,出现问题。经过一夜的冥思苦想(呵呵),终于找到一个比较合理的处理方法。下一步有时间的话就准备修改PascalMock。由于在PascalMock开发当时,Delphi(WIN32)尚没有引进泛型和匿名方法。所以它使用Variant来处理的(比较土哦)。
思路: 1、把事件封装
2、把通常的事件方法(如:procedure of object)改为匿名方法(如: reference to procedure). 好处多多。最大的好处就是把UNSAFE的POINTER变为了SAFE的REFERENCE.
3、事件封装类引入泛型机制。但有一个问题还不知道怎么处理:就是匿名方法没有一个BASE TYPE(CLASS)。所以泛型无法设定约束(如T: constrain).这时好羡慕C#啊。
代码保留(是以前几篇的PUZZLE为例来做的)
unit TestPuzzlePresenter; interface uses TestFrameWork,PascalMock; type TEventDelegateRef = reference to procedure;
TEventDelegateClass<T> = class sealed private FEventHandler: T; public property EventHandler: T read FEventHandler; constructor Create(Handler: T); end; IPuzzleModel = interface procedure SubscribeUpdateImages(Listener: TEventDelegateClass<TEventDelegateRef>); procedure LoadImageRequest; end; IPuzzleView = interface procedure SubscribeLoadImageRequest(Listener: TEventDelegateClass<TEventDelegateRef>); end; TPuzzleModelMock = class(TMock,IPuzzleModel) public procedure SubscribeUpdateImages(Listener: TEventDelegateClass<TEventDelegateRef>); procedure LoadImageRequest; end; TPuzzleViewMock = class(TMock,IPuzzleView) public procedure SubscribeLoadImageRequest(Listener: TEventDelegateClass<TEventDelegateRef>); end; TPuzzlePresenter = class private FModel: IPuzzleModel; FView: IPuzzleView; public procedure AfterConstruction; override; constructor Create(Model: IPuzzleModel; View: IPuzzleView); end; TPuzzlePresenterTest = class(TTestCase) private FSubscribeLoadImageRequest: TEventDelegateClass<TEventDelegateRef>; private FModelMock: TPuzzleModelMock; FViewMock: TPuzzleViewMock; protected procedure SetUp; override; procedure TearDown; override; published procedure TestLoadImage; end; implementation { TEventDelegateClass } constructor TEventDelegateClass<T>.Create(Handler: T); begin inherited Create; FEventHandler := Handler; end; { TPuzzleModelMock } procedure TPuzzleModelMock.LoadImageRequest; begin AddCall('LoadImageRequest'); end; procedure TPuzzleModelMock.SubscribeUpdateImages(Listener: TEventDelegateClass<TEventDelegateRef>); begin AddCall('SubscribeUpdateImages').WithParams([Listener]); end; { TPuzzleViewMock } procedure TPuzzleViewMock.SubscribeLoadImageRequest(Listener: TEventDelegateClass<TEventDelegateRef>); begin AddCall('SubscribeLoadImageRequest').Returns(Listener); end; { TPuzzlePresenter } procedure TPuzzlePresenter.AfterConstruction; begin inherited; FModel.SubscribeUpdateImages(TEventDelegateClass<TEventDelegateref>.Create(procedure begin //Dummy end)); FView.SubscribeLoadImageRequest(TEventDelegateClass<TEventDelegateRef>.Create(procedure begin FModel.LoadImageRequest; end)); end; constructor TPuzzlePresenter.Create(Model: IPuzzleModel; View: IPuzzleView); begin inherited Create; FModel := Model; FView := View; end; { TPuzzlePresenterTest } procedure TPuzzlePresenterTest.SetUp; begin inherited; FModelMock := TPuzzleModelMock.Create; FModelMock.Expects('SubscribeUpdateImages').WithParams([Ignored]); FViewMock := TPuzzleViewMock.Create; FViewMock.Expects('SubscribeLoadImageRequest').Returns(Ignored); TPuzzlePresenter.Create(IPuzzleModel(FModelMock),IPuzzleView(FViewMock)); FSubscribeLoadImageRequest := TEventDelegateClass<TEventDelegateRef>(FViewMock.CalledMethodByName('SubscribeLoadImageRequest').ReturnValueAsObject); end; procedure TPuzzlePresenterTest.TearDown; begin FModelMock.Verify('Model Call Ok?'); FViewMock.Verify('View Call Ok?'); inherited; end; procedure TPuzzlePresenterTest.TestLoadImage; var Trigger: TEventDelegateRef; begin FModelMock.Expects('LoadImageRequest'); Trigger := FSubscribeLoadImageRequest.EventHandler; Trigger(); end; initialization RegisterTest('Puzzle',TPuzzlePresenterTest.Suite);
参考:ms-help://embarcadero.rs_xe/rad/Anonymous_Methods_in_Delphi.html