Java 带有继承和混合的Scala可测试代码
我已经用Java开发了很多代码,并且涉猎了Groovy和Haskell,这使我现在接触到了Scala 我对Scala的功能方面感觉比较舒服,但我发现自己对Scala中的面向对象设计有点动摇,因为它感觉与Java有点不同,特别是由于traits/mix-ins 我的目标是编写尽可能可测试的代码,这在我的Java开发中一直转化为关注Java 带有继承和混合的Scala可测试代码,java,scala,unit-testing,oop,testability,Java,Scala,Unit Testing,Oop,Testability,我已经用Java开发了很多代码,并且涉猎了Groovy和Haskell,这使我现在接触到了Scala 我对Scala的功能方面感觉比较舒服,但我发现自己对Scala中的面向对象设计有点动摇,因为它感觉与Java有点不同,特别是由于traits/mix-ins 我的目标是编写尽可能可测试的代码,这在我的Java开发中一直转化为关注 尽可能不可变 更喜欢由构造函数注入状态 总是选择合成而不是继承(严重受其影响,并且可能对其反应过度) 现在我正试图在这个新的Scala领域站稳脚跟,我很难弄清楚我应
- 尽可能不可变
- 更喜欢由构造函数注入状态
- 总是选择合成而不是继承(严重受其影响,并且可能对其反应过度)
- 与traits(…)中定义的其他行为混合的类
- 仅测试版本以促进自动单元测试
我已经做了一个可以体验使用混合插件和组成的组合 所以,举例来说,使用组件将行为混合到特定的特征中。下面的示例显示了一个类中使用多个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/类的具体行为:
trait SomeTraitsComponentMock {
val serviceXXX = mock[ServiceXXX]
val serviceYYY = mock[ServiceYYY]
}
object SomeTraitsUsingMixingMock extends SomeTraitsComponentMock
在您的规范中,您可以使用ScalaMock声明控制服务的结果,您引用的Q/A中的trait用法实际上是处理混合trait所提供的灵活性 在本例中,当您显式扩展一个trait时,编译器会在编译时锁定类和超类的类型。在本例中,MyService是一个
LockingFlavorA
trait Locking { // ... }
class LockingFlavorA extends Locking { //... }
class MyService extends LockingFlavorA {
}
当您使用键入的自引用时(如您所指的Q/a所示):
。。锁定
可以指锁定
本身,或锁定
的任何有效子类。然后作者在调用站点混合了锁定实现,而没有为此明确创建新类:
val myService: MyService = new MyService with JDK15Locking
我认为,当他们说您可以简化测试时,他们实际上是在谈论使用此功能来模拟我们Java开发人员通常对组合和模拟对象所做的事情。您只需制作一个模拟的锁定
实现,并在测试期间将其混合,然后为运行时制作一个真正的实现
对于您的问题:这比使用模拟库和依赖项注入更好还是更差?这很难说,但我认为最终很大程度上取决于一种技术或另一种技术与代码库其余部分的配合程度
如果您已经在使用组合和依赖注入来获得良好的效果,我认为继续使用这种模式可能是一个好主意
如果您刚刚起步,还没有真正需要所有这些功能,或者还没有从哲学上确定依赖项注入适合您,那么您可以在运行时复杂性方面以很小的成本从混合中获得很多好处
我认为真正的答案将被证明是高度情境化的
TL;DR低于
问题1)我认为这是一个情境上有用的替代合成/副合成的方法,但我不认为它提供了除了简单性以外的任何主要好处
问题2)是的,它可以提高可测试性,主要是通过trait实现来模拟模拟对象。谢谢您的回答。你能更详细地解释一下为什么你选择将
concreteTraitComponent
作为一个特征而不是一个类吗?我更喜欢尽可能长地将一个函数单元声明为一个特征,因为在scala中,只能从多个特征中进行。谢谢。我真的很欣赏这个答案,因为它为我的参考文献提供了很好的注释,并直接回答了问题。没问题,Scala是一种有趣的语言,很高兴了解这些更深奥的语言特性如何与世界上人们已经在做的事情相适应。这是个好问题。嗯。。。从它巨大的零分来看,我想它本可以更好。但是我得到了我的答案,这是最重要的部分。哈哈,人们都是自私自利的,包括我在内。我把它记下来了,我真的认为这是一个好问题。
val myService: MyService = new MyService with JDK15Locking