TDD实践之路--泛型和匿名方法在DELPHI单元测试中的妙用

昨天启动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

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...