zoukankan      html  css  js  c++  java
  • 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>();

    只不过是个空壳:)

  • 相关阅读:
    svn导入项目和部署方面的相关问题
    JDK版本会影响项目部署
    在HTML中限制input 输入框只能输入纯数字
    mui-下拉刷新
    抽象工厂模式(Abstract Factory)C#实例
    C++基础-结构体伪函数与多线程(void operator()(int))
    Android基础-Activity生命周期
    Android基础-Adapter适配器生成对话框
    Android基础-弹窗对话框(popup)
    Android基础-自定义对话框
  • 原文地址:https://www.cnblogs.com/wxc-kingsley/p/8033545.html
Copyright © 2011-2022 走看看