ChatGPT解决这个技术问题 Extra ChatGPT

模拟和存根有什么区别?

我已经阅读了各种关于测试中模拟与存根的文章,包括 Martin Fowler's Mocks Aren't Stubs,但仍然不明白其中的区别。

@OP因为没有区别。这篇文章,尽管受到社区的喜爱,但在所有应有的尊重下 - 通过为易于理解的单词添加额外的含义并使事情变得不必要的复杂化,从而使一切变得不必要的混乱。 Mock 只是一个模拟,运行虚假业务逻辑而不是真实业务逻辑的东西。最终检查行为是您的选择,但它仍然是一个模拟。或者任何你想叫它的东西,但让它成为一个。不要分裂头发。保持简单,这样人们就可以很容易地理解你的概念——上面的文章确实失败了。
“模拟、赝品和存根之间的分类在文献中高度不一致。”有很多引用。仍然是我最喜欢的维基百科引语之一 - 如果存在这样的事情:) en.wikipedia.org/wiki/Mock_object
Martin Fowler 的文章对于初学者来说真的很难理解。

R
Ryszard Dżegan

前言

对象有几种定义,它们是不真实的。通用术语是测试替身。该术语包括:dummy、fake、stub、mock。

参考

根据Martin Fowler's article

虚拟对象被传递但从未实际使用过。通常它们仅用于填充参数列表。假对象实际上有工作实现,但通常采取一些捷径,这使得它们不适合生产(内存数据库就是一个很好的例子)。存根为测试期间拨打的电话提供预设答案,通常根本不响应任何超出测试程序的内容。存根还可以记录有关呼叫的信息,例如记住它“发送”的消息的电子邮件网关存根,或者可能只记录它“发送”的消息的数量。模拟就是我们在这里讨论的内容:预先编程的对象具有期望,这些期望形成了它们期望接收的调用的规范。

风格

模拟 vs 存根 = 行为测试 vs 状态测试

原则

根据Test only one thing per test的原则,一个测试可能有多个stub,但一般只有一个mock。

生命周期

使用存根测试生命周期:

设置 - 准备正在测试的对象及其存根协作者。练习 - 测试功能。验证状态 - 使用断言检查对象的状态。 Teardown - 清理资源。

使用模拟测试生命周期:

设置数据 - 准备正在测试的对象。设置期望 - 在主要对象使用的模拟中准备期望。练习 - 测试功能。验证期望 - 验证是否在模拟中调用了正确的方法。验证状态 - 使用断言检查对象的状态。 Teardown - 清理资源。

概括

模拟和存根测试都给出了这个问题的答案:结果是什么?

使用 mocks 进行测试也很感兴趣:结果是如何实现的?


等等,模拟也返回罐头答案?否则他们为什么要回答这个问题?
从您写的内容中,我可以看出模拟 = 存根 + 期望和验证,因为模拟“为测试期间进行的调用提供预设答案,通常根本不响应任何超出测试编程的内容”(与存根相同)。 Fowler 作为存根示例展示的示例实际上是间谍的示例!这意味着模拟是存根,间谍是存根。存根只是一个具有多种工作方法的对象。这也解释了为什么 Mockito 不推荐使用 stub() 方法。
我对此感到困惑,公认的答案是这个“期望设置”,它甚至意味着什么?通常,在“主代码”中,您会创建您期望的结果。听起来您以某种方式将期望放入模拟对象中,但这对我来说没有意义。此外,您可以轻松地使用一些输入来练习模拟,存储结果,稍后创建“期望”,然后进行比较。你使用了我觉得太抽象和模棱两可的术语。
为什么这个答案会出现在第二页?它应该出现在接受的答案之后(如果不是在它之前)。我在这里看到的最佳答案,详细,准确且易于理解。
我认为“原则”部分不正确。测试替身正在取代“合作者”或“依赖者”。测试中可能有几个这样的,这很好。它是被测系统(SUT),应该只有一个,这里没有区别。模拟和古典风格测试都更喜欢一个 SUT。
E
Elijah

存根

我相信最大的区别是您已经编写了具有预定行为的存根。因此,您将拥有一个实现您为测试目的而伪造的依赖项(最有可能是抽象类或接口)的类,并且这些方法将被设置响应。他们不会做任何花哨的事情,而且您已经在测试之外为其编写了存根代码。

嘲笑

模拟是作为测试的一部分,您必须根据自己的期望进行设置。模拟不是以预先确定的方式设置的,因此您有在测试中执行它的代码。在某种程度上,模拟是在运行时确定的,因为设置期望的代码必须在它们做任何事情之前运行。

模拟和存根之间的区别

使用 mock 编写的测试通常遵循 initialize -> set expectations -> exercise -> verify 模式进行测试。而预先编写的存根将遵循 initialize -> exercise -> verify

模拟和存根之间的相似之处

两者的目的都是为了消除对类或函数的所有依赖项的测试,以便您的测试在他们试图证明的内容上更加集中和简单。


一条令人困惑的线 - A mock is something that as part of your test you have to setup with your expectations. A mock is not setup in a predetermined way so you have code that does it in your test. 那么,它是根据您的期望设置的,但不是以预先确定的方式?这怎么可能?
D
Daniel Holmes

存根是一个简单的假对象。它只是确保测试顺利运行。模拟是更智能的存根。您验证您的测试通过它。


我认为这是最简洁和最准确的答案。要点:模拟 IS-A 存根。 stackoverflow.com/a/17810004/2288628 是此答案的较长版本。
我不认为模拟是存根。模拟用于断言并且不应该返回数据,存根用于返回数据并且不应该断言。
@dave1010 Mocks 绝对可以返回数据甚至抛出异常。他们应该这样做以响应传递给他们的参数。
@trenton 如果一个对象根据传入的数据返回或抛出,那么它是假的,而不是模拟的。存根测试您的 SUT 如何处理接收消息,模拟测试您的 SUT 如何发送消息。混淆 2 可能会导致糟糕的 OO 设计。
我认为这很棒 - 存根返回问题的答案。模拟还返回问题的答案(is-a stub),但它也验证问题是否被提出!
R
Rafael

这是对每一个的描述,然后是真实世界的样本。

Dummy - 只是为了满足 API 的虚假值。示例:如果您正在测试一个类的方法,该方法在构造函数中需要许多强制参数,而这对您的测试没有影响,那么您可以创建虚拟对象来创建类的新实例。

Fake - 创建一个可能依赖于某些外部基础设施的类的测试实现。 (最好的做法是您的单元测试实际上并不与外部基础设施交互。)示例:创建用于访问数据库的假实现,将其替换为内存中的集合。

存根 - 覆盖方法以返回硬编码值,也称为基于状态。示例:您的测试类依赖于Calculate() 方法,需要5 分钟才能完成。您可以用返回硬编码值的存根替换它的实际实现,而不是等待 5 分钟;只占用一小部分时间。

Mock - 与 Stub 非常相似,但基于交互而不是基于状态。这意味着您不期望 Mock 返回一些值,而是假设进行了特定的方法调用顺序。示例:您正在测试用户注册类。调用 Save 后,它应该调用 SendConfirmationEmail。

StubsMocks 实际上是 Mock 的子类型,它们都将实际实现与测试实现互换,但出于不同的具体原因。


1) 阅读答案 2) 阅读blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html 3 ) 再次阅读答案。
我很少对 SO 发表评论,但这个答案显然值得竖起大拇指。清晰、简洁并带有示例。另外,感谢分享帖子@snr
“Stubs 和 Mocks 实际上是 Mock 的子类型” --- 你的意思是 Stubs 和 Fakes 吗?
“存根和模拟实际上是模拟的子类型”。我认为您的意思是“存根和模拟实际上是假的子类型”。
D
Dillon Kearns

codeschool.com 课程 Rails Testing for Zombies 中,他们给出了以下术语定义:

存根

用于用返回指定结果的代码替换方法。

嘲笑

带有方法被调用的断言的存根。

因此,正如 Sean Copenhaver 在他的回答中所描述的那样,不同之处在于模拟设置了期望(即断言,关于它们是否或如何被调用)。


为了补充 Dillon 的帖子,请考虑一下,您有一个名为“MakeACake”的类,它使用了几个库:Milk、Eggs、Sugar、Oven。
b
b4da

存根不会使您的测试失败,模拟可以。


我认为这很好,你知道重构后测试是否具有相同的行为。
@RodriKing 我也有同样的感觉。与 Mock 一样,生产代码的任何更改 - 您都会对测试代码进行相应的更改。哪个是痛!使用 Stubs,感觉就像您一直在测试行为,因此无需对测试代码进行微更改。
O
O'Rooney

看了上面所有的解释,让我试着浓缩一下:

存根:一段让测试运行的虚拟代码,但你不关心它会发生什么。替代实际工作代码。

模拟:您验证的一段虚拟代码作为测试的一部分被正确调用。替代实际工作代码。

Spy:一段虚拟代码,拦截并验证对真实工作代码的一些调用,避免替换所有真实代码的需要。


好答案。根据您的定义,Mock 听起来与 Spy 非常相似。如果您更新答案以包含更多测试替身,那就太好了。
这是一个很好的答案,不会模糊图片。
@O'Rooney 很好的答案。如果您可以更新答案并提供有关 Stub 的更清晰的解释/摘要,那将很有帮助。
感谢您的建议;我认为它也适用于模拟,所以我采纳了你的建议并做了一些其他的调整。
G
Ghini Antonio

我认为 Roy Osherove 在他的书 The art of Unit Testing (page 85) 中给出了关于这个问题的最简单和更清晰的答案

判断我们正在处理存根的最简单方法是注意存根永远不会通过测试。测试使用的断言总是针对被测类。另一方面,测试将使用模拟对象来验证测试是否失败。 [...] 同样,模拟对象是我们用来查看测试是否失败的对象。

存根和模拟都是假的。

如果您对假货进行断言,则意味着您将假货用作模拟,如果您仅使用假货来运行测试而不对其进行断言,则您将假货用作存根。


我希望你的答案能登上顶峰。这里是 R. Osherove 解释这个 youtu.be/fAb_OnooCsQ?t=1006
r
roufamatic

Mock 只是测试行为,确保调用某些方法。存根是特定对象的可测试版本(本身)。

苹果的方式是什么意思?


“你说的苹果方式是什么意思?”使用 Helvetica
以苹果的方式而不是微软的方式:)
h
happygilmore

如果将其与调试进行比较:

Stub 就像确保方法返回正确的值 Mock 就像实际进入方法并确保在返回正确的值之前内部的所有内容都是正确的。


R
R01010010

要非常清楚和实用:

存根:实现要伪造的类/对象的方法并始终返回您想要的内容的类或对象。

JavaScript 中的示例:

var Stub = {
   method_a: function(param_a, param_b){
      return 'This is an static result';
   }
}

模拟:与存根相同,但它添加了一些逻辑来“验证”何时调用方法,因此您可以确定某些实现正在调用该方法。

正如@mLevan 所说,想象一下您正在测试用户注册类。调用 Save 后,它应该调用 SendConfirmationEmail。

一个非常愚蠢的代码示例:

var Mock = {
   calls: {
      method_a: 0
   }

   method_a: function(param_a, param_b){
     this.method_a++; 
     console.log('Mock.method_a its been called!');
   }
}

A
Alireza Rahmani khalili

让我们看看测试双打:

Fake:Fake 是具有工作实现的对象,但与生产对象不同。如:数据访问对象或存储库的内存实现。

存根:存根是一个保存预定义数据并在测试期间使用它来应答呼叫的对象。如:需要从数据库中抓取一些数据来响应方法调用的对象。

模拟:模拟是注册他们收到的调用的对象。在测试断言中,我们可以在 Mocks 上验证所有预期的操作都已执行。如:调用电子邮件发送服务的功能。有关更多信息,请查看此内容。


我认为的最佳答案
A
Aviram Fireberger

这张幻灯片很好地解释了主要区别。

https://i.stack.imgur.com/frwt1.png

*摘自华盛顿大学 CSE 403 第 16 课(由“Marty Stepp”制作的幻灯片)


这是对两者之间差异的更清楚的解释,IMO。对于存根:测试人员获取存根并直接在被测类中使用它。但是对于 Mock,测试人员必须确定如何使用 Mock 对象。在不同的情况下,它的行为会有所不同。相反,存根预计不会有不同的行为,而是按原样使用(意味着无论何时联系都返回相同的数据)
E
Elijah

使用心智模型确实帮助我理解了这一点,而不是所有的解释和文章,这些解释和文章并没有完全“深入”。

想象一下,您的孩子在桌子上有一个玻璃盘子,他开始玩它。现在,您担心它会破裂。所以,你给他一个塑料盘子。那将是一个 Mock (相同的行为,相同的接口,“更软”的实现)。

现在,假设你没有塑料替代品,所以你解释说“如果你继续玩它,它会坏掉的!”。那是一个存根,你预先提供了一个预定义的状态。

一个 Dummy 将是他甚至没有使用的叉子......而 Spy 可能就像提供您已经使用过的相同解释一样有效。


A
Adam Parkin

我认为他们之间最重要的区别是他们的意图。

让我尝试在 WHY stub vs. WHY mock 中解释它

假设我正在为我的 mac twitter 客户端的公共时间线控制器编写测试代码

这是测试示例代码

twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline

STUB: 到 twitter API 的网络连接很慢,这让我的测试很慢。我知道它会返回时间线,所以我做了一个模拟 HTTP twitter API 的存根,这样我的测试就会运行得非常快,即使我离线也可以运行测试。

MOCK:我还没有编写任何 UI 方法,而且我不确定需要为我的 ui 对象编写哪些方法。我希望通过编写测试代码来了解我的控制器将如何与我的 ui 对象协作。

通过编写mock,您通过验证期望是否满足来发现对象的协作关系,而stub只是模拟对象的行为。

如果您想了解更多关于模拟的信息,我建议您阅读这篇文章:http://jmock.org/oopsla2004.pdf


我认为您的想法是正确的,但 Dillon Kearns 解释得更清楚。
n
nitishagar

我喜欢 Roy Osherove 的解释 [video link]

创建的每个类或对象都是假的。如果您验证对它的调用,它就是一个 Mock。否则它是一个存根。


R
Relu Mesaros

存根与模拟 存根为方法调用提供特定答案 例如:myStubbedService.getValues() 只返回被测代码所需的字符串,被测代码用来隔离它不能失败测试例如:myStubbedService.getValues() 只返回存根value 经常实现抽象方法模拟存根的“超集”;可以断言某些方法被调用 例如:验证 myMockedService.getValues() 仅被调用一次用于测试被测代码的行为 测试失败 例如:验证 myMockedService.getValues() 是否被调用一次;验证失败,因为我的测试代码没有调用 myMockedService.getValues() 经常模拟接口

存根为方法调用提供特定的答案 例如:myStubbedService.getValues() 只返回一个被测试代码所需的字符串,被测试代码用来隔离它不能失败测试例如:myStubbedService.getValues() 只返回存根值通常实现抽象方法

为方法调用提供具体答案 例如:myStubbedService.getValues() 只返回被测代码所需的字符串

例如: myStubbedService.getValues() 只返回被测代码所需的字符串

被测试代码用来隔离它

不能失败测试前: myStubbedService.getValues() 只返回存根值

例如: myStubbedService.getValues() 只返回存根值

经常实现抽象方法

模拟存根的“超集”;可以断言某些方法被调用 例如:验证 myMockedService.getValues() 仅被调用一次用于测试被测代码的行为 测试失败 例如:验证 myMockedService.getValues() 是否被调用一次;验证失败,因为我的测试代码没有调用 myMockedService.getValues() 经常模拟接口

存根的“超集”;可以断言某些方法被调用 ex:验证 myMockedService.getValues() 只被调用一次

例如:验证 myMockedService.getValues() 是否只被调用一次

用于测试被测代码的行为

测试失败:验证 myMockedService.getValues() 是否被调用过一次;验证失败,因为我的测试代码没有调用 myMockedService.getValues()

例如:验证 myMockedService.getValues() 是否被调用过一次;验证失败,因为我的测试代码没有调用 myMockedService.getValues()

经常模拟接口


D
DdW

我正在阅读 The Art of Unit Testing,偶然发现了以下定义:

fake 是一个通用术语,可用于描述存根或模拟对象(手写或其他),因为它们看起来都像真实对象。 fake 是 stub 还是 mock 取决于它在当前测试中的使用方式。如果它用于检查交互(断言反对),它是一个模拟对象。否则,它是一个存根。


D
DeeFisher

存根

存根是用于伪造具有预编程行为的方法的对象。您可能希望使用此方法而不是现有方法以避免不必要的副作用(例如,存根可以进行虚假的 fetch 调用,该调用返回预编程的响应而无需实际向服务器发出请求)。

嘲笑

mock 是一个对象,用于伪造具有预编程行为以及预编程期望的方法。如果这些期望没有得到满足,那么模拟将导致测试失败(例如,模拟可能会进行虚假的 fetch 调用,该调用会返回预编程的响应,而不会实际向服务器发出期望的请求例如,第一个参数是 "http://localhost:3008/" 否则测试将失败。)

区别

与模拟不同,存根没有预先编程的期望,可能会使您的测试失败。


P
Premraj

他使用的通用术语是测试替身(想想特技替身)。 Test Double 是一个通用术语,用于替换生产对象以进行测试的任何情况。 Gerard 列出了多种类型的 double:

虚拟对象被传递但从未实际使用过。通常它们仅用于填充参数列表。

假对象实际上有工作实现,但通常采取一些捷径,这使得它们不适合生产(InMemoryTestDatabase 就是一个很好的例子)。

存根为测试期间拨打的电话提供预设答案,通常根本不响应任何超出测试程序的内容。

间谍是存根,它还根据调用方式记录一些信息。其中一种形式可能是电子邮件服务,它记录发送了多少条消息(也称为部分模拟)。

模拟预编程了期望,这些期望形成了他们期望接收的调用的规范。如果他们收到了他们不期望的呼叫,他们可以抛出异常,并在验证过程中进行检查以确保他们得到了他们期望的所有呼叫。

Source


M
Mustafa Ekici

fake 是一个通用术语,可用于描述存根或模拟对象(手写或其他),因为它们看起来都像真实对象。

fake 是 stub 还是 mock 取决于它在当前测试中的使用方式。如果它用于检查交互(断言反对),它是一个模拟对象。否则,它是一个存根。

假货确保测试顺利进行。这意味着您未来测试的读者将了解假对象的行为,而无需阅读其源代码(无需依赖外部资源)。

测试运行顺利是什么意思?例如在下面的代码中:

 public void Analyze(string filename)
        {
            if(filename.Length<8)
            {
                try
                {
                    errorService.LogError("long file entered named:" + filename);
                }
                catch (Exception e)
                {
                    mailService.SendEMail("admin@hotmail.com", "ErrorOnWebService", "someerror");
                }
            }
        }

您想测试 mailService.SendEMail() 方法,为此您需要在测试方法中模拟一个异常,因此您只需要创建一个 Fake Stub errorService 类来模拟该结果,然后您的测试代码就可以测试mailService.SendEMail() 方法。如您所见,您需要模拟来自另一个 External Dependency ErrorService 类的结果。


D
Dimos

来自 jMock 开发人员的论文 Mock Roles, not Objects

存根是返回预设结果的生产代码的虚拟实现。模拟对象充当存根,但也包括断言以检测目标对象与其邻居的交互。

因此,主要区别在于:

在存根上设置的期望通常是通用的,而在模拟上设置的期望可能更“聪明”(例如在第一次调用时返回 this,在第二次调用时返回 this 等)。

存根主要用于设置 SUT 的间接输入,而模拟可用于测试 SUT 的间接输入和间接输出。

总而言之,同时还试图消除来自 Fowler's article 标题的混淆:模拟是存根,但它们不仅仅是存根


我认为你是对的,但这就是福勒文章令人困惑的原因,文章标题是“模拟不是存根”......但它们是?! ¯_(ツ)_/¯
@stonedauwg,确实,我编辑了我的帖子以包含您的双关语和澄清。希望这会有所帮助。
@stonedauwg,模拟不是存根,就像矩形不是正方形一样。 :)
m
mostafa kazemi

模拟:帮助模拟和检查结果交互。这些交互是 SUT 对其依赖项进行的调用以更改其状态。

存根:帮助模拟传入的交互。这些交互是 SUT 对其依赖项进行的调用以获取输入数据。

https://i.stack.imgur.com/jGhHA.png

来源:单元测试原则、实践和模式——曼宁


A
A.I

我看到了 UncleBob The Little Mocker 的这篇有趣的文章。它以非常易于理解的方式解释了所有术语,因此对初学者很有用。 Martin Fowlers 的文章很难读,尤其是对于像我这样的初学者。


K
Kasper

那里有很多有效的答案,但我认为值得一提的是鲍勃叔叔的这张表格:https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html

有史以来最好的解释!


仅链接的答案在 SO 上不被认为有价值。
d
davidxxx

模拟既是技术对象又是功能对象。

模拟是技术性的。由于字节码生成,它确实是由模拟库(EasyMock、JMockit 和最近的 Mockito 以这些库而闻名)创建的。模拟实现是以一种方式生成的,我们可以在调用方法时检测它以返回特定值,但也可以进行其他一些事情,例如验证是否使用某些特定参数(严格检查)或任何参数调用了模拟方法(没有严格的检查)。

实例化一个模拟:

@Mock Foo fooMock

记录行为:

when(fooMock.hello()).thenReturn("hello you!");

验证调用:

verify(fooMock).hello()

这些显然不是实例化/覆盖 Foo 类/行为的自然方式。这就是我提到技术方面的原因。

但是模拟也是功能性的,因为它是我们需要与 SUT 隔离的类的一个实例。有了记录的行为,我们可以在 SUT 中使用它,就像使用存根一样。

存根只是一个功能性对象:它是我们需要与 SUT 隔离的类的一个实例,仅此而已。这意味着我们的单元测试期间需要的存根类和所有行为装置都必须明确定义。
例如,存根 hello() 需要继承 Foo 类(或实现它的接口)并覆盖 hello()

public class HelloStub extends Hello{    
  public String hello { 
      return "hello you!"; 
  }
}

如果另一个测试场景需要另一个值返回,我们可能需要定义一个通用的方法来设置返回:

public class HelloStub extends Hello{    
  public HelloStub(String helloReturn){
       this.helloReturn = helloReturn;
  }
  public String hello { 
      return helloReturn; 
  }
}

其他情况:如果我有一个副作用方法(不返回)并且我会检查该方法是否被调用,我可能应该在存根类中添加一个布尔值或计数器来计算调用该方法的次数。

结论

存根通常需要大量开销/代码来为您的单元测试编写。由于提供了开箱即用的记录/验证功能,mock 可以防止什么。这就是为什么现在随着优秀的模拟库的出现,存根方法在实践中很少使用。

关于 Martin Fowler 文章:当我使用 mock 并且避免使用 stub 时,我不认为自己是一个“mockist”程序员。但是我在真正需要时使用模拟(烦人的依赖项),并且当我测试一个具有依赖项的类时,我更喜欢测试切片和迷你集成测试,而模拟将是一种开销。


a
ahmednabil88

加上有用的答案,使用 Mocks 比 Subs 最强大的一点之一

如果协作者[主要代码依赖它]不受我们控制(例如来自第三方库),在这种情况下,存根比模拟更难编写。


说得通。预期的输出可能会改变。但是,这与 Mock 不一样吗。一旦您编写了模拟和预期的输出更改,您需要更改测试代码。
H
Harry

存根帮助我们运行测试。如何?它提供了有助于运行测试的值。这些值本身不是真实的,我们创建这些值只是为了运行测试。例如,我们创建一个 HashMap 来为我们提供与数据库表中的值相似的值。因此,我们不是直接与数据库交互,而是与 Hashmap 交互。

Mock 是一个运行测试的假对象。我们放置断言的地方。


“因此,我们不是直接与数据库交互,而是与 Hashmap 交互。” ...因为当时还没有时间编写数据库模块,而且如果不使用存根,我们就无法运行测试代码。否则同样的 Hasmap 将是一个模拟!正确的?
A
Adarsh Shah

请参阅下面使用 C# 和 Moq 框架的模拟与存根示例。 Moq 没有 Stub 的特殊关键字,但您也可以使用 Mock 对象来创建存根。

namespace UnitTestProject2
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Moq;
    [TestClass]
    public class UnitTest1
    {
        /// <summary>
        /// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero
        /// </summary>
        [TestMethod]
        public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
        {
            // Arrange 
            var mockEntityRepository = new Mock<IEntityRepository>();
            mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));

            var entity = new EntityClass(mockEntityRepository.Object);
            // Act 
            var name = entity.GetNameWithPrefix(12);
            // Assert
            mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once);
        }
        /// <summary>
        /// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero
        /// </summary>
        [TestMethod]
        public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
        {
            // Arrange 
            var mockEntityRepository = new Mock<IEntityRepository>();
            mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
            var entity = new EntityClass(mockEntityRepository.Object);
            // Act 
            var name = entity.GetNameWithPrefix(0);
            // Assert
            mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never);
        }
        /// <summary>
        /// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix
        /// </summary>
        [TestMethod]
        public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
        {
            // Arrange 
            var stubEntityRepository = new Mock<IEntityRepository>();
            stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
                .Returns("Stub");
            const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
            var entity = new EntityClass(stubEntityRepository.Object);
            // Act 
            var name = entity.GetNameWithPrefix(12);
            // Assert
            Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
        }
    }
    public class EntityClass
    {
        private IEntityRepository _entityRepository;
        public EntityClass(IEntityRepository entityRepository)
        {
            this._entityRepository = entityRepository;
        }
        public string Name { get; set; }
        public string GetNameWithPrefix(int id)
        {
            string name = string.Empty;
            if (id > 0)
            {
                name = this._entityRepository.GetName(id);
            }
            return "Mr. " + name;
        }
    }
    public interface IEntityRepository
    {
        string GetName(int id);
    }
    public class EntityRepository:IEntityRepository
    {
        public string GetName(int id)
        {
            // Code to connect to DB and get name based on Id
            return "NameFromDb";
        }
    }
}

r
radtek

我在回答中使用了 python 示例来说明差异。

Stub - Stubbing 是一种软件开发技术,用于在开发生命周期的早期实现类的方法。它们通常用作实现已知接口的占位符,其中接口已完成或已知,但实现尚不知道或未完成。您从存根开始,这仅意味着您只写下函数的定义,并将实际代码留待以后使用。优点是您不会忘记方法,并且您可以在代码中看到它的同时继续思考您的设计。您还可以让存根返回静态响应,以便代码的其他部分可以立即使用该响应。存根对象提供了一个有效的响应,但无论你传入什么输入,它都是静态的,你总是会得到相同的响应:

class Foo(object):
    def bar1(self):
        pass

    def bar2(self):
        #or ...
        raise NotImplementedError

    def bar3(self):
        #or return dummy data
        return "Dummy Data"

模拟对象用于模拟测试用例,它们验证在这些对象上调用了某些方法。模拟对象是以受控方式模仿真实对象行为的模拟对象。您通常会创建一个模拟对象来测试其他对象的行为。 Mocks 让我们可以模拟不可用或难以进行单元测试的资源。

我的模块.py:

import os
import os.path

def rm(filename):
    if os.path.isfile(filename):
        os.remove(filename)

测试.py:

from mymodule import rm
import mock
import unittest

class RmTestCase(unittest.TestCase):
    @mock.patch('mymodule.os')
    def test_rm(self, mock_os):
        rm("any path")
        # test that rm called os.remove with the right parameters
        mock_os.remove.assert_called_with("any path")

if __name__ == '__main__':
    unittest.main()

这是一个非常基本的示例,它只运行 rm 并声明它被调用的参数。您可以将模拟与对象一起使用,而不仅仅是此处显示的函数,您还可以返回一个值,以便可以使用模拟对象替换存根进行测试。

关于 unittest.mock 的更多信息,注意在 python 2.x 模拟中不包含在 unittest 中,而是一个可下载的模块,可以通过 pip (pip install mock) 下载。

我还阅读了 Roy Osherove 的“单元测试的艺术”,我认为如果使用 Python 和 Python 示例编写类似的书会很棒。如果有人知道这样的书,请分享。干杯:)