带有继承和mixins的Scala可测试代码

Eri*_*sen 9 java oop unit-testing scala testability

我用Java开发了很多代码,并涉足Groovy和Haskell,现在我已经把我带到了Scala.

我对Scala的功能方面感到相对舒服,但我发现自己在Scala中的面向对象设计上有些不稳定,因为它感觉与Java有点不同,特别是由于特性/混合.

我的目标是编写尽可能可测试的代码,这在我的Java开发中一直都是关注的

  • 尽可能的不变性
  • 倾向于由构造者注入状态
  • 总是去构成而不是继承(受SO影响很大,可能对此帖子过度反应)

现在我正试图在这个新的Scala领域站起来,我很难弄清楚我应该采取什么方法,特别是我是否应该为某些目的开始使用继承.

编程Scala(Wampler和Payne; O'Reilly,第2版)有一个考虑因素("良好的面向对象设计:一个题外话"),我已经阅读了很多关于SO的帖子,但我还没有看到明确提到可测试性的设计考虑因素.本书提供了有关使用继承的建议:

  1. 抽象基类或特征由具体类(包括案例类)分为一级.
  2. 除了两种情况外,具体类永远不会被子类化:
    • 混合在特征中定义的其他行为的类(...)
    • 仅测试版本,以促进自动化单元投影.
  3. 当子类化似乎是正确的方法时,考虑将行为划分为特征并改为混合这些特征.
  4. 永远不要跨父子类型边界拆分逻辑状态.

对SO的一些挖掘也表明,有时混合比组合更好.

所以本质上我有两个问题:

  1. 是否有一些常见的情况,即使考虑可测试性,使用继承会更好?

  2. 混合是否提供了增强代码可测试性的好方法?

Ric*_*nry 11

您引用的Q/A中的特征用法实际上是处理混合特征所提供的灵活性.

例如,当您显式扩展特征时,编译器会在编译时锁定类和超类的类型.在这个例子中,MyService是一个LockingFlavorA

trait Locking { // ... }

class LockingFlavorA extends Locking { //... }

class MyService extends LockingFlavorA {

}
Run Code Online (Sandbox Code Playgroud)

当您使用类型化的自引用时(如您指向的Q/A中所示):

class MyService {
   this: Locking =>
}
Run Code Online (Sandbox Code Playgroud)

.. Locking可以引用Locking自身,或任何有效的子类Locking.然后,作者在调用站点中混合使用锁定实现,而不是为此目的明确创建新类:

val myService: MyService = new MyService with JDK15Locking
Run Code Online (Sandbox Code Playgroud)

我认为,当他们说你可以轻松进行测试时,他们真的在谈论使用这个功能来模仿我们Java开发人员通常用组合和模拟对象做的事情.您只需创建一个模拟Locking实现并在测试期间将其混合,并为运行时创建一个真正的实现.

对于你的问题:这比使用模拟库和依赖注入更好还是更差?这很难说,但我认为最终很多事情将归结为一种技术或另一种技术与其他代码库的关系.

如果你已经使用组合和依赖注入效果很好,我认为继续使用这种模式可能是一个好主意.

如果你刚开始并且还没有真正需要所有的炮兵,或者没有哲学上决定依赖注射适合你,你可以以非常小的成本从mixins获得很多里程在运行时复杂性.

我认为真正的答案将被证明是高度情境化的.

TL; DR下面

问题1)我认为这是一种情境上有用的替代组合/控制,但我认为除了简单之外它不会带来任何重大收益.

问题2)是的,它可以提高可测试性,主要是通过特征实现模拟模拟对象.