【问题标题】:Need an advice for unit testing using mock object需要使用模拟对象进行单元测试的建议
【发布时间】:2010-06-11 16:56:02
【问题描述】:

我最近刚读到关于单元测试的“模拟对象”,目前我在我的应用程序中实现这种方法时遇到了困难。请让我解释一下我的问题。

我有一个 User 模型类,它依赖于 2 个数据源(数据库和 facebook 网络服务)。控制器类只是将这个User 模型用作访问数据的接口,它并不关心数据的来源。

目前我从未对此User 模型进行任何单元测试,因为它依赖于外部Web 服务。但就在不久前,我读到了关于对象模拟的文章,现在我知道它是一种对依赖于外部资源的类进行单元测试的常用方法(就像我的例子一样)。

现在我想为User 模型创建一个单元测试,但是我遇到了一个设计问题: 为了让User 模型使用模拟的 Facebook SDK,我必须将这个模拟的 Facebook SDK 注入到用户对象(可能使用 setter)。因此,我无法在 User 对象中构造 Facebook SDK。我必须用户对象之外构建它,并将 SDK 注入到用户对象中。

我的User 模型的真正客户端是应用程序的控制器。因此,我必须在控制器内部构建 Facebook SDK 并将其注入用户对象。嗯,这是一个问题,因为我希望我的控制器尽可能干净。我希望我的控制器不知道应用程序的数据源。

我不擅长系统地解释一些事情,所以你可能会在阅读最后一段之前睡觉。但是无论如何,我想问一下这里是否有人遇到过和我一样的问题?你如何解决这个问题?

问候, 安德烈

P.S:我使用的是 Zend 框架,PHP 5.3。

【问题讨论】:

    标签: unit-testing dependency-injection mocking


    【解决方案1】:

    解决这个问题的一种方法是在User 中创建两个构造函数:默认一个实例化现实生活中的数据源,另一个将它们作为参数获取。这样,您的控制器可以使用默认构造函数,而您的测试使用参数化的构造函数来传递模拟数据源。

    由于您没有指定您的语言,我向您展示了一个 Java 示例,希望这有助于您理解:

    class User {
      private DataBase database;
      private WebService webService;
    
      // default constructor
      public User() {
        database = new OracleDataBase();
        webService = new FacebookWebService();
      }
    
      // constructor for unit testing
      public User(DataBase database, WebService webService) {
        this.database = database;
        this.webService = webService;
      }
    }
    

    【讨论】:

    • 是的,这也是我一直在考虑的。简单但有效。顺便说一句,你以前做过吗?这就像重新设计一个类,以便我们可以从外部注入详细的实现(可以模拟)。
    • @Andree,是的,我经常使用它。它对于单元测试遗留代码非常有用。请注意,这不一定是最终和完美的设计 - 但是一旦您的代码被单元测试覆盖,您就可以继续安全地进一步重构它。
    【解决方案2】:

    这实际上不是关于模拟的问题,而是关于依赖关系的问题——尝试单元测试已经迫使这个问题。听起来目前您在 Controller 中创建了 User 对象。

    如果 User 和 Controller 具有相同的生命周期(它们是同时创建的),那么您可以将 User 传递给 Controller 的构造函数,您可以在其中进行替换。

    如果每次调用都有一个用户对象,那么用户对象可能应该由环境传入,或者从某个上下文对象返回。

    【讨论】:

      【解决方案3】:

      如果您将 Facebook SDK 对象设为可公开访问,您的 User 对象仍可在设置时创建它,但您可以在实际对 User 对象执行任何操作之前将其替换为模拟对象。

      [Test]
      public void Test()
      {
          User u = new User(); // let's say that the object on User gets created in the ctor
          u.FacebookObj = new DynamicMock(typeof(FacebookSDK)).MockInstance;
      
          Assert.That(u.Method(), Does.Stuff, "u.Method didn't do stuff");
      }
      

      【讨论】:

        【解决方案4】:

        你没有说你使用的是什么语言,但我使用 Ruby 和 Mocha 来模拟对象,这很容易。

        http://mocha.rubyforge.org/

        这里的第四个例子表明,任何从被测单元对 Product.name 的调用都会被拦截,并返回值 'stubbed_name'。

        我猜Java等也有类似的机制。

        【讨论】:

          【解决方案5】:

          由于您将进行单元测试,您的测试类将扮演控制器的角色,因此不应该被触及。这样就可以保持清洁。

          您正在彻底改造依赖注入。因此,看看 Spring 或 Guice 来帮助您进行管道安装可能是值得的。 (如果您在 Java 土地上)。

          您的测试确实可以使用 setter 或构造函数参数注入 Mocked Facebook SDK 和您的数据库服务。

          您的测试现在将执行控制器(可能还有视图)将执行的操作,并验证在 SDK 中调用了正确的例程以及资源是否已正确获取和处置。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-06-10
            • 1970-01-01
            • 2019-08-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多