什么';s模拟一个对象和仅仅设置一个值之间的区别


What's the difference between mocking an object and just setting a value

我知道我问这个问题意味着我可能没有完全理解嘲讽。我知道为什么要使用mocks(隔离),我知道如何使用mock(每个FW及其方式)-但我不明白-如果mock的目的是返回期望的值来隔离我的单元测试,为什么我要模拟一个对象,而不仅仅是自己创建值?

为什么:

    $searchEngine = Mockery::mock('elasticSearch');
    $searchEngine->shouldReceive('setupDataConnection')->once()->andReturn("data connected");
    $insertData = new DataInserter;
    $this->assertEquals("data connected",$insertData->insertData($searchEngine));

而不是这个:

    $knownResult = "data connected";
    $insertData = new DataInserter;
    $this->assertEquals($insertData->insertData($searchEngine) ,$knownResult);

编辑很抱歉出现错误,但意外地没有在第二个示例中包含insertData。

通过使用mock,您可以访问其他信息,从一个行为直接类似于C风格结构(只执行数据存储)的对象来看,没有什么区别(除了对mock的调用进行断言,这通常非常有用,因为你可以确保你的值被设置了3次,而不是7次(可能有一些中间值会导致潜在的问题)

Mock对于测试对象之间的交互非常有用。

现在,如果您的类执行更复杂的操作(例如访问数据库等资源,或从文件中读取/写入数据)。)然后mock就成了救命稻草,因为它们几乎可以让你抽象掉未经过测试的类的内部行为(例如,让我们想象一下,类的第一个版本只是将值存储在内存中,下一个版本将它们存储在数据库中的特定位置,这样一来,您首先可以节省数据库访问的资源损失,其次可以有效地证明受测类工作正常,而不是同时证明数据访问类工作正常如果测试失败,那么您可以立即深入了解问题,而不是试图弄清楚问题是在您的数据提供程序还是数据访问器中。)

因为测试可以并行运行,所以某些资源可能会导致错误的失败。在没有模拟的情况下,极难测试的几个交互示例是:

  1. 与数据库访问层对话的代码(确保查询正确,在连接上调用close(),并发送适当的SQL,此外,您不会修改真实的数据库。)
  2. 文件/套接字IO(再次确保数据一致)
  3. 系统调用(例如对php-exec的调用)
  4. 任何依赖于线程/定时的东西(在PHP中不是一个很大的问题)
  5. GUI代码(同样,在PHP中几乎闻所未闻),但如果我将语言转换为java,那么调用起来就容易多了:when(myDialogBoxMock).showOptionInputDialog().thenReturn("mockInput")然后尝试用子类或临时实现/子类来伪造它
  6. 对必须仅使用特定值调用的特定方法的调用。verify(myMock.setValue(7),times(2));verifyNoOtherInteractions(myMock)

其中很大一部分也是速度,如果你在一个巨大的代码库中从磁盘上读取一个文件,比如说300/400次,那么你肯定会注意到使用mock会提高速度。

如果你在PHP中有一个类似EMMA/jacoco for java的工具,你将能够有一个有效的代码覆盖率报告来显示你的测试没有覆盖代码的地方。在任何非琐碎的应用程序上,你都会发现自己正试图弄清楚如何让被测试的对象进入特定状态来测试特定行为,带DI的Mocks实际上是执行这些任务的工具。

在第一个例子中,您为DataInserter提供了一个假的合作者($searchEngine)。在assert语句中,您正在调用insertData,它将执行DataInserter的逻辑,该逻辑将与假的$searchEngine进行交互。通过提供false,您可以验证DataInserter的逻辑在隔离状态下是否正确工作。

在第二个示例中,您没有测试DataInserter逻辑。您只是在检查常量字符串$knownResult是否等于"data-connected",即您正在测试php的字符串相等性。据我从片段中看到的,您正在构建一个新的DataInserter对象,但您甚至没有在测试中使用它的代码。