Scala: Mocking and the Cake Pattern
我一直在尝试采用蛋糕模式,但我在适应这种编程风格时遇到了困难,尤其是在涉及单元测试时。
假设我有以下业务对象:
1
2 3 4 5 6 7 8 9 10 |
现在,我想在模拟 Vet 的函数时测试 PetStore。如果我使用组合,我会创建一个 mock[Vet] 并将其传递给 PetStore 构造函数,然后像在 Java 世界中那样对 mock 进行编程。但是,我找不到任何关于人们如何使用蛋糕图案来做到这一点的参考。
一种可能的解决方案是根据预期的用法在每个测试用例上实现 vaccinate() ,但这不允许我验证模拟是否被正确调用,不允许我使用匹配器等
那么 – 人们如何将蛋糕模式与模拟对象一起使用?
我在阅读这篇博文后开始使用蛋糕模式:https://github.com/precog/staticsite/blob/master/contents/blog/Existential-Types-FTW/index.md 方法不同于大多数使用存在类型而不是自我类型的蛋糕模式帖子。
我已经使用这种模式几个月了,它似乎工作得很好,因为我可以在我想要的时候指定一个模拟。它确实有更多的依赖注入感觉,但它具有将代码放入特征中所获得的所有好处。
我使用存在类型的问题的混蛋版本将是这样的:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
case class Pet(val name: String)
trait ConfigComponent { type Config def config: Config } trait Vet { trait PetStoreConfig { type Config <: PetStoreConfig def sell(pet: Pet) { |
您可以将所有内容放在您的应用中
1
2 3 4 5 6 7 8 9 10 11 12 13 |
您可以通过创建 VetLike 的实例并创建 VetLike 的模拟来单独测试组件,并使用它进行 PetStore 测试。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//Test VetLike Behavior
scala> val vet = new Vet{} scala> vet.vaccinate(new Pet(“Fido”)) Vaccinate:Pet(Fido) //Test Petstore Behavior class VetMock extends Vet { class PetStoreTest extends PetStore with PetStoreConfig { val vet = new VetMock scala> new PetStoreTest |
- 这很酷 – 但我错过了什么吗?你对 PetStore 中的 Vet 类型做了什么?
- 所以我试图在不使用 ConfigComponent 特征的情况下举一个例子,但我做错了。无论如何,我已经更新了这个例子并添加了 ConfigComponent。希望事情看起来更清楚一点。
这是个好问题。我们得出的结论是无法做到,至少与我们习惯的方式不同。可以使用Stubbing代替模拟,并以蛋糕的方式混合Stubbing。但这比使用模拟更有效。
我们有两个 Scala 团队,一个团队采用了 cake 模式,使用 stubs 而不是 mocks,而另一个团队坚持使用类和依赖注入。现在我都试过了,我更喜欢带模拟的 DI,因为它更容易测试。并且可以说更容易阅读。
- 这是我最初的想法。然而,随着我开始越来越多地使用 Scala,我达到了将相同概念业务对象的不同关注点与不同特征分离的阶段,以实现可测试性和清晰性目的。这里使用 DI 会导致对象图过大和应用程序初始化代码繁琐。
- 正如他们所说的那样。我们的经历正好相反。
我找到了一种将 Scalamock 与 Scalatest 结合使用的方法,用于对”蛋糕模式”模块进行单元测试。
起初,我遇到了很多问题(包括这个),但我相信我在下面提出的解决方案是可以接受的。如果您有任何疑问,请告诉我。
这就是我设计你的例子的方式:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
然后将测试定义如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class TestPetstore extends FlatSpec with ShouldMatchers with MockFactory {
trait PetstoreBehavior extends PetStoreModule with VetModule { object MockWrapper { def fixture = { def t1 { def vet: Vet = MockWrapper.vet val somePetStoreImpl = new PetstoreBehavior with PetStoreModuleImpl |
使用这个设置,你有一个”缺点”,你必须在你编写的每个测试中调用 val vet = fixture。另一方面,可以轻松地创建测试的另一个”实现”,例如
虽然这是一个老问题,但我正在为未来的读者添加我的答案。我相信这篇 SO 帖子 – How to use mocks with the Cake Pattern – 问和回答了同样的事情。
我成功地遵循了 Vladimir Matveev 给出的答案(这是撰写本文时的最佳答案
来源:https://www.codenong.com/16274986/