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

时间:2015-02-14 16:00:21

标签: java scala unit-testing oop testability

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

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

我的目标是编写尽可能可测试的代码,这在我的Java开发中始终转化为专注于

  • 尽可能的不变性
  • 首选施工人员注入国家
  • 总是选择合成而不是继承(受this post on SO严重影响,可能过度反应)

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

编程Scala (Wampler和Payne; O'Reilly,第2版)有一节考虑(“面向对象的好设计:一个题外话”),我读了一些关于SO的帖子,但我没有看到关于可测试性的设计考虑的明确提及。本书提供了有关使用继承的建议:

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

对SO的一些挖掘也表明sometimes mix-ins are preferable to composition

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

  1. 是否存在使用继承更好(甚至考虑可测试性)的常见情况?

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

2 个答案:

答案 0 :(得分:11)

您引用的Q / A中的特征用法实际上是处理混合特征所带来的灵活性。

在示例中,当您显式扩展特征时,编译器会在编译时锁定类和超类的类型。在此示例中,MyService是LockingFlavorA

trait Locking { // ... }

class LockingFlavorA extends Locking { //... }

class MyService extends LockingFlavorA {

}

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

class MyService {
   this: Locking =>
}

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

val myService: MyService = new MyService with JDK15Locking

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

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

如果您已经使用合成和依赖注入效果良好,我认为继续使用该模式可能是一个好主意。

如果你刚刚开始并且还没有真正需要所有的炮兵,或者没有哲学上决定依赖注射适合你,你可以得到很多来自mixins的里程数,运行时复杂度成本非常低。

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

TL; DR低于

问题1)我认为它是一种情境上有用的替代组合/注射,但我认为除了简单之外它不会提供任何重大收益。

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

答案 1 :(得分:3)

我已经体验过使用混合和组合的组合。

所以通过示例使用组件将行为混合到特定特征中。下面的示例显示了在类中使用多个dao层特征的结构。

trait ServiceXXX {
  def findAllByXXX(): Future[SomeClass]
}

trait ServiceYYY {
  def findAllByYYY(): Future[AnotherClass]
}

trait SomeTraitsComponent {
  val serviceXXX: ServiceXXX
  val serviceYYY: ServiceYYY
}

trait SomeTraitsUsingMixing { 
  self: SomeTraitsComponent => 

  def getXXX() = Action.async {
    serviceXXX.findAllByXXX() map { results => 
      Ok(Json.toJson(results))
    }
  }

  def getYYY() = Actiona.async {
    serviceYYY.findAllByYYY() map {results => 
      Ok(Json.toJson(results))
    }
  }
}

之后,您可以声明一个具体组件并通过示例将其绑定到配套对象:

trait ConreteTraitsComponent extends SomeTraitsComponent {
  val serviceXXX = new ConcreteServiceXXX
  val serviceYYY = new ConcreteServiceYYY
}

object SomeTraitsUsingMixing extends ConreteTraitsComponent

使用这个模式你可以轻松地创建一个测试组件并使用mock来测试你的tait / class的具体行为:

trait SomeTraitsComponentMock {
  val serviceXXX = mock[ServiceXXX]
  val serviceYYY = mock[ServiceYYY]
}

object SomeTraitsUsingMixingMock extends SomeTraitsComponentMock

在您的规范中,您可以使用ScalaMock声明控制服务的结果http://scalamock.org/