内容简介:在2010年至2014年期间,我正在开展一个名为我一直在练习它达到了我无法忍受的程度。所有这些问题都指向了模拟,因此我尝试将它们从测试文件中删除。以下是我最终用来删除它们的技术:
在2010年至2014年期间,我正在开展一个名为 http://mes-courses.fr 的辅助项目。这实际上类似“家庭购物”。我希望人们能够在5分钟内通过使用更好的在线百货界面购物。我使用的是Ruby,我刚读过 以测试为导向的面向对象的成长软件 。我对Mock模拟感到有点太兴奋了,而且用得太多了。
我一直在练习 测试驱动开发 超过5年,我期待使用像 Ruby 这样的语言取得很好的成绩。几个月之后,我可以感觉到事情并没有那么好。测试初始化代码越来越长,因为它包含了大量的模拟设置。这使得测试更复杂,更不易读。这也使它们变得不可靠,我的所有单元测试都无法测试出系统异常情况。我越来越习惯于经常进行端到端的测试,我也失去了很多时间来维护模拟设置代码与真实类一致。Mocks还欺骗了我在代码和测试文件之间保持1对1映射的不良做法。当将代码从一个文件移动到另一个文件时,这又增加了我的维护负担。
它达到了我无法忍受的程度。所有这些问题都指向了模拟,因此我尝试将它们从测试文件中删除。以下是我最终用来删除它们的技术:
最终的结果超出了我的希望,因为我的问题几乎神奇地消失了。代码变得更简单,我对单元测试变得更加自信,并且更容易维护。作为一个例子,这里是轨道控制器测试文件的差异的摘录,该文件经历了这种模拟饮食。
其他人已经谈到了模拟的危险:
不可变的值对象如何对抗模拟
过度使用模拟会使测试非常痛苦。如果我们长时间坚持痛苦的模拟,我们最终会放弃单位测试。最终,系统将降级为遗留系统。
不可变的值对象:
- 创建后无法改变状态
- 仅依赖于其他不可变值对象
- 不要以任何方式改变整个系统的状态
- 不要做副作用,例如输入和输出
Eric Evans在 Domain-Driven Design Blue Book中 推广了这个名字。不可变值对象在函数式语言中已经存在了数十年。我们说这些对象是不可变的(它们不能改变)和纯粹的(它们不能做副作用)。以下是值对象Value Objects的两个有趣属性:
- 您可以多次调用方法,而不会有任何改变系统的风险
- 每次在同一个对象上调用相同的方法时,总会得到相同的结果
这些本身在测试时已经很方便了。
让我们反过来看看副作用如何导致嘲弄。每个测试都从设置运行测试的状态开始。副作用使这变得复杂,因为许多对象需要协作来设置此状态。当这变得太痛苦时,人们开始用Mock模拟(模拟相当于一种黑客技术),这反过来使测试更加脆弱:
- 我们没有测试“真实”的情况
- 我们需要将此设置与实际代码保持一致
错综复杂的状态初始化鼓励人们使用模拟。
隔离系统的各个部分
不幸的是,这不是故事的全部!可变状态也诱使我们使用模拟。一旦您的测试处理可变状态,就有可能在“真实”系统中更改此状态,这意味着一些错误可能会“逃避”单元测试并出现在端到端测试或生产中,那是使用Mock模拟的地方!为了在快速反馈循环中检测到这个错误,我们可能会添加更大范围的测试并使用模拟来加速它们......
可变状态和副作用使单元测试效果降低。
但是,不可变值对象帮助我们避免模拟的另一个原因。由于前两个原因我们会尝试越来越多地使用它们,我们需要调整我们的编程风格。随着我们将在不可变值对象中推送越来越多的代码,“命令式”部分将缩小。这种“命令性”部分是副作用发生的地方。这是模拟IO有意义的部分。总而言之,我们使用的不可变值对象越多,IO就越孤立,我们需要的模拟越少。
Javascript专家Eric Elliot也在 这里 写了关于不变性和模拟的文章。
Fizz Buzz示例
举个简单的例子,我将介绍经典的 Fizz Buzz 。我已经使用和不使用不可变值对象来实现和测试它。请记住,这是一个玩具示例,问题很明显且很容易修复。我试图在小范围内突出大型程序的复杂性所隐藏的相同问题。
让我们从典型的FizzBuzz实现开始。
1.upto(100) <b>do</b> |i| <b>if</b> (i%3 == 0 and i%5 == 0) STDOUT.puts(<font>"FizzBuzz\n"</font><font>) elsif (i%3 == 0) STDOUT.puts(</font><font>"Fizz\n"</font><font>) elsif (i%5 == 0) STDOUT.puts(</font><font>"Buzz\n"</font><font>) <b>else</b> STDOUT.puts(</font><font>"#{i}\n"</font><font>) end end </font>
假设您需要在代码周围添加一些测试。最简单的方法是模拟STDOUT:
require 'rspec' def fizzBuzz(max, out) 1.upto(max) <b>do</b> |i| <b>if</b> (i%3 == 0 and i%5 == 0) out.puts(<font>"FizzBuzz\n"</font><font>) elsif (i%3 == 0) out.puts(</font><font>"Fizz\n"</font><font>) elsif (i%5 == 0) out.puts(</font><font>"Buzz\n"</font><font>) <b>else</b> out.puts(</font><font>"#{i}\n"</font><font>) end end end # main fizzBuzz(100,STDOUT) describe 'Mockist Fizz Buzz' <b>do</b> it 'should print numbers, fizz and buzz' <b>do</b> out = <b>double</b>(</font><font>"out"</font><font>) expect(out).to receive(:puts).with(</font><font>"1\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"2\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"Fizz\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"4\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"Buzz\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"Fizz\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"7\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"8\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"Fizz\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"Buzz\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"11\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"Fizz\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"13\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"14\n"</font><font>).ordered expect(out).to receive(:puts).with(</font><font>"FizzBuzz\n"</font><font>).ordered fizzBuzz(15, out) end end </font>
不幸的是,这段代码存在一些问题:
- 使用嵌套逻辑和复杂的模拟设置,代码和测试都不是非常易读
- 他们似乎也违反了单一责任原则
- 这取决于可变输出。在一个更大的程序中,有些东西可能会搞乱这个输出流。这会破坏FizzBuzz。
现在让我们尝试使用尽可能多的不可变值对象,看看模拟会发生什么。
require 'rspec' # We extracted a function to <b>do</b> the fizz buzz on a single number def fizzBuzzN(i) <b>if</b> (i%3 == 0 and i%5 == 0) <font>"FizzBuzz"</font><font> elsif (i%3 == 0) </font><font>"Fizz"</font><font> elsif (i%5 == 0) </font><font>"Buzz"</font><font> <b>else</b> i.to_s end end # We replaced the many calls to STDOUT.puts by building a single # large (and immutable) string def fizzBuzz(max) ((1..max).map {|i| fizzBuzzN(i)}).join(</font><font>"\n"</font><font>) end # main, with a single call to STDOUT.puts STDOUT.puts fizzBuzz(100) describe 'Statist Fizz Buzz' <b>do</b> it 'should print numbers not multiples of 3 or 5' <b>do</b> expect(fizzBuzzN(1)).to eq(</font><font>"1"</font><font>) expect(fizzBuzzN(2)).to eq(</font><font>"2"</font><font>) expect(fizzBuzzN(4)).to eq(</font><font>"4"</font><font>) end it 'should print Fizz <b>for</b> multiples of 3' <b>do</b> expect(fizzBuzzN(3)).to eq(</font><font>"Fizz"</font><font>) expect(fizzBuzzN(6)).to eq(</font><font>"Fizz"</font><font>) end it 'should print Buzz <b>for</b> multiples of 5' <b>do</b> expect(fizzBuzzN(5)).to eq(</font><font>"Buzz"</font><font>) expect(fizzBuzzN(10)).to eq(</font><font>"Buzz"</font><font>) end it 'should print FizzBuzz <b>for</b> multiples of 3 and 5' <b>do</b> expect(fizzBuzzN(15)).to eq(</font><font>"FizzBuzz"</font><font>) expect(fizzBuzzN(30)).to eq(</font><font>"FizzBuzz"</font><font>) end it 'should print numbers, fizz and buzz' <b>do</b> expect(fizzBuzz(15)).to start_with(</font><font>"1\n2\nFizz"</font><font>).and(end_with(</font><font>"14\nFizzBuzz"</font><font>)) end end </font>
正如我们所看到的,使用不可变值对象让我们摆脱了模拟。显然,这个新代码不如原始版本有效,但大多数时候,这并不重要。虽然我们获得了更好的颗粒和更可读的测试作为奖励。
不可变值对象具有与测试相关的其他优点。
- 我们可以直接断言他们的等同,而不必深入了解他们的内部结构
- 我们可以根据需要多次调用方法,而不会有改变任何东西和破坏测试的风险
- 不可变值对象不太可能包含无效状态。这消除了对一系列有效性测试的需要。
为什么说服其他开发人员使用不可变数据结构如此困难?
到目前为止,遇到共享可变状态的错误时,我获得了最大的成功。当这种情况发生时,不变设计的长期利益和安全性赢得了人们的青睐。好消息是,当你说服团队中的更多人时,不变性会像病毒一样传播!
在这种情况之外,您可以尝试以下一些参数来说服其他人员:
以上所述就是小编给大家介绍的《粗心的Mock模拟测试是有害的 - Philippe Bourgau》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Flutter 学习之路 - 测试(单元测试,Widget 测试,集成测试)
- itest(爱测试)接口测试&敏捷测试管理 7.7.7 发布,接口测试重大升级
- 性能测试vs压力测试vs负载测试
- SpringBoot | 第十三章:测试相关(单元测试、性能测试)
- 敏捷测试VS传统测试对比,6招玩转敏捷测试!
- itest(爱测试)接口测试&敏捷测试管理平台 8.1.0 发布
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解C++11
Michael Wong、IBM XL编译器中国开发团队 / 机械工业出版社 / 2013-6 / 69.00元
《深入理解C++11:C++11新特性解析与应用》内容简介:国内首本全面深入解读C++11新标准的专著,由C++标准委员会代表和IBM XL编译器中国开发团队共同撰写。不仅详细阐述了C++11标准的设计原则,而且系统地讲解了C++11新标准中的所有新语言特性、新标准库特性、对原有特性的改进,以及如何应用所有这些新特性。 《深入理解C++11:C++11新特性解析与应用》一共8章:第1章从设计......一起来看看 《深入理解C++11》 这本书的介绍吧!