扩大
缩小

Mock 入门,分析stub . mock区别

面向接口编程的测试难的问题

Mock Framework的用处在于我们可以在不实现具体对象的情况下,即在没有某个类的实例的情况下对该对象的行为进行模拟。这一特征对于面向接口的编程非常有用。因为接口的调用者可以在没有接口的具体实现的情况下使用接口,也就是说调用者可以先于接口的实现者行动。也许有人觉得这好像没什么神奇的,即使没有mock我也一样可以使用接口啊,可是我要问:

  “在没有接口实现的情况下,你能对调用接口的代码进行测试吗?”

“NullReferenceException”相信很多人都碰到过的吧。由于接口不能定义构造函数,也就无法实例化,导致了调用接口的代码无法运行,当然也就是无法测试。

Mocking能干什么?

从mock 的字面意思就可以了解一二了,它的主要工作是模拟出一个被模拟对象的实例,其中包括模拟对该实例的调用行为(比如访问属性、调用方法之类)、模拟方法或属性访问的返回值、模拟方法和索引的参数传递等等,可以说基本上对于一个对象实例的使用它都可以模拟出来。这样一来,我们就可以好像真的有一个我们需要的实例存在一样,正常地使用它,来完成对调用者代码的开发和测试。

Mock object和stub object一样吗?

当然不一样!写过stub测试程序的人应该知道,stub是真是对象的一个模拟,比如调用者需要一个值,那就让stub输出一个值,如果调用者需要传递一个值给stub,那就在stub中定义一个方法接受该参数。但是这与mock的对象存在本质的区别:

stub虽然说也是模拟,但其本质上对真是对象的一个简单实现,而无论它有多简单它都是一种实现,它是真是存在的,它里面包含了我们定义的操作代码;

反观mock的对象,它根本是不存在的,哪怕一句的简单的不能再简单的代码都不存在。

 

在理解其区别之前,需要明白一点,他们都是为了同一个目标而出现的,代替依赖部分,让原先的“整合测试”简化为“单元测试”。       

mock:使用easymock等包,在程序代码中向被测试代码注入“依赖部分”,通过代码可编程的方式模拟出函数调用返回的结果。

stub:自己写代码代替“依赖部分”。它本身就是“依赖部分”的一个简化实现。

     实际上,在能够使用mock的时候,就不应该选择使用stub。但是有时候是必须使用stub的,例如在对遗留代码进行测试时,该部分代码不支持“注入”,那么只能将“替代”这个过程外移,使用stub完成此任务了。

 

应用场景

就以我现在正在开发这个网站代码为例,来说一下如果在测试的使用Mock object.现在有一个需求,我们需要根据给定的搜索关键字和搜索范围来进行项目的搜索,以MVP的方式实现的话我们定义了一个IView接口:

  public interface IView_SearchProject
  {
    void AttachPresenter(Presenter_SearchProject presenterSearchProject);
    SearchRange Range { get;}
    string SearchKey { get;}
    string UrlBase { get;}
    void NavigateTo(string searchUrl);
  }

以及一个Presenter:

public class Presenter_SearchProject
  {
    public Presenter_SearchProject(IView_SearchProject viewSearch)
    {
        view = viewSearch;
        range = view.Range;
        prjNav = new ProjectSearchNavigator(view.UrlBase);
        query = new SearchQuery();
    }

    public string GetDesUrl()
    {
        query.WithDescription = range.WithDescription;
        query.WithName = range.WithName;
        query.WithKey = range.WithKey;
        query.SearchKey = view.SearchKey;
        query.Ids = range.Ids;

        prjNav.Compile(query);
        return prjNav.DestUrl;
    }

    public void Search()
    {
        view.NavigateTo(GetDesUrl());
    }

    private IView_SearchProject view;
    private SearchRange range;
    private ProjectSearchNavigator prjNav;
    private SearchQuery query;
  }

ProjectSearchNavigator是一个实现页面跳转的帮助类,负责根据View(这里是一个aspx的页面)传递的搜索关键字SearchKey和querystring构造出搜索页面的地址。SearchQuery类负责解析Request.QueryString集合,因为其中存储的key/value对,需要据此构造出所有查询条件的一个字符串。
Mocking and Testing

Mocking说到底多试为了测试,否则我们没有必要,因为mocking出来的对象并不能作为的真是的代码运行。先把测试的代码贴出来,再进行解释,希望你不要觉得太多了:)

  [TestFixture]
  public class Presenter_SearchProject_Test
  {
    [SetUp]
    public void SetUp()
    {
        mockRepository=new MockRepository();//1
        mockView = mockRepository.CreateMock<IView_SearchProject>();//2
    }
    [Test]
    public void GetDestUrl()
    {
        SearchRange range = new SearchRange(true, true, false, string.Empty);
        //3
        //
        Expect.Call(mockView.Range).Return(range) ;
        //UrlBase
        Expect.Call(mockView.UrlBase).Return("http://localhost");
        //SearchKey
        Expect.Call(mockView.SearchKey).Return("searchKey");
        
        //4
        mockRepository.ReplayAll();
        //5
        presenter = new Presenter_SearchProject(mockView);

        string destUrlReturned = presenter.GetDesUrl();
        string destUrlExpected = "http://localhost/ProjectPage/ProjectControl.aspx?"
                  +"search=searchKey&name=True&key=True&description=False";
        //6
        Assert.AreEqual(destUrlExpected,destUrlReturned);
    }

    IView_SearchProject mockView;
    MockRepository mockRepository;
    Presenter_SearchProject presenter;

    [TearDown]
    public void TestCleanup()
    {
        mockRepository.ReplayAll();
        mockRepository.VerifyAll();
    }

  }

  1. Rhion.Mock框架中要使用mock的对象都需要从MockRepository 这个对象中产生,它充当一个对象工厂的角色。
  2. 这一步就是创建我们使用的mock的对象了,需要以被mock类的类型作为泛型参数。
  3. 这一步的3行代码是真正mock的部分,它们分别对应着对mockView的三次调用。
    Expect.Call(mockView.Range).Return(range) ;
    Expect.Call表示我们希望调用mockVIew的那个方法,也包括属性。

    .Return的意思我们打算让这个模拟对象返回什么样的值

    综合起来的意思就是:我们希望mockView的调用者在调用MockView的某一个方法(或属性)时返回一个有return标识的值
  4. ReplayAll的调用千万不要忘掉,它的意思可以理解为,让之前设定的模拟行为生效,从此之后我们就可以把这个mock的对象当作是一个真是的对象来使用了。我觉得可以把它想像成CLR为我们自动生成了代码一样,为我们生成了一个对被mock对象的实现。
  5. 这一步是调用者对mock对象的使用。
  6. 测试我们关注的对象的行为是否正常

一个需要注意到地方

presenter = new Presenter_SearchProject(mockView);

像这样的初始化需要注意顺序,必须要等到MockView被真正模拟出来之后,也就是ReplayAll调用之后,因为在presenter 内部需要访问mockView的成员,比如:

range = view.Range;

但是如果你在mock对象调用者初始化的时候没有访问mock对象的成员,那么这样的初始化可以的。因为虽然mock对象的成员还米有mock出来,但是mock对象已经被生成了:

mockView = mockRepository.CreateMock<IView_SearchProject>();

只不过是个空壳:)

posted @ 2017-12-13 16:26  WindyAmy  阅读(4290)  评论(0编辑  收藏  举报