2007年3月16日星期五

MVP实践之:单元测试

MVP模式的应用中很重要一点是“方便了测试”。MVP中的Model层不与界面发生任何关系,按照一般的测试方法进行测试即可。但是Presenter需要view,至少需要一个View的契约,也就是它多少需要对view有一些了解,那么到底如何在没有界面的情况下测试呢?这里借用上一篇中讲到的winform的例子,以测试SearchPictOfComp为目标来实践一下。这里假设Model层的代码都经过了测试。

我们编写一个测试用例,如下:
[Test]
public void SearchArtifactOfCompTest()
{
MockViewListDescPict view;
view=测试用的View
view.ComponentId = "265f1e5d-2c75-4368-9a3a-5da5d82270c5";
ListDescPictPresenter presenter = new ListDescPictPresenter(view);
view.AttachPresenter(presenter);
view执行click button的动作
Assert.IsTrue(((IList)view.descPictList).Count == 6);
}
先解释一下这个测试用例的意思。首先创建一个测试用的view,然后用它初始化presenter,当view执行一个点击button的动作之后,presenter会去响应这个操作,将取出的数据传给view的列表控件显示,如果成功则列表控件的数据源中应该有6条纪录。
现在运行这个测试用例肯定是失败的,因为标记为红色的代码都是没有实现的伪代码。为了让测试通过,我们尝试实现红色标记处所要求的功能。
  • view=测试用的View
    这里实际上是要求一个满足契约IViewListDescPict的view,我们虽然没有真正的view,但是我们可以编写一个模拟的view(mock view)——可以联想在模块化测试时编写桩(stub)模块的情形。MockViewListDescPict是一个模拟的view,它实现了IViewListDescPict接口。那么一个问题就解决了:
    view=new MockViewListDescPict();
  • view执行click button的动作
    上一篇中讲到presenter是采用观察者模式,它监听button的事件。既然只要事件,那我们只要模拟一个事件就行了,并不需要真的用一个button。搞清楚button的click是产生一个普通的event,那我们定义一个:
    public EventHandler searchArtsOfComp_Event
    事件的触发很简单:searchArtsOfComp_Event.Invoke(null, null);
    我们把触发的代码放在一个方法中,以便在测试的时候模拟:
    public void MockClickSearchButton()
    {
    searchArtsOfComp_Event.Invoke(null, null);
    }
    接下来我们要让presenter注册对该事件的监听,以便在invoke触发事件的时候能被presenter捕捉到,我们把这一步放在AttachPresener中进行:
    public void AttachPresenter(ListDescPictPresenter presenter)
    {
    this.presenter = presenter;
    searchArtsOfComp_Event = presenter.SearchPictOfComp_EventHandler;
    }
    其中presenter.SearchPictOfComp_EventHandler是一个指向方法SearchPictOfComp(见应用篇)的委托。
    到此,第二个问题也解决了:
    view.MockClickSearchButton();
现在运行一下测试用例,pass了自然高兴,可是“(IList)view.descPictList”是怎么回事?其实这是为了测试读取的数据是不是正确地传给了view的列表控件,在解释测试用例的意思已经说过了。但是我们现在使用的view都是模拟(Mock)的,button事件也是假的,那么列表也同样可是假的了。我们并不关心列表控件是不是正确的显示了数据,那是控件自己的事情,我们只关心是不是传递了正确的数据,因此这里可以模拟一个接受数据的行为。控件的datasource是object的,那我们就在mock view的内部定义一个objcect的对象来获得对传递数据的引用:
public object descPictList;
public void SetPictListDataSource(IList pictList)
{
descPictList = pictList;
}
当我们模拟了button的click事件之后,presenter根据它的内部逻辑会去调用view的
SetPictListDataSource方法,将数据传递给我们设定的接收对象descPictList,现在我们只需要检查descPictList所获得的数据是否正确就行了。如果你愿意的话可以逐条检查descPictList中的项是否正确,但其实我们只需简单地检查项的个数就可以了,因为在presenter内部未对数据做修改,只要Model层能保证取出的数据无误,这里就不会有错。

总结一下对presenter的测试,一共有三点:
1、不需要真的view,可以使用类似Mock Object这样的技术模拟一个view,只要模拟的对象满足IView的契约即可。
2、如果要从view中获得用户的数据,比如要从textbox中取得用户输入的查询条件,又或者需要将处理的结果数据发回view显示,比如将一个list的集合发送到ListBox显示,这两类问题我们都可以使用对字段或者属性的存取操作来模拟。因为我们不关心显示,只要数据正确传送即可,将显示的任务交给view,可是使得presenter的功能独立于显示层。
3、对于像用户点击button或者选择chechbox这样的交互行为,我们不需要真的用控件来测试。.net允许我们手动触发事件,比如EventHandler.Invoke(),因此我们可以用手动触发事件的方式来模拟用户的交互行为

没有评论: