Scala testable code with inheritance and mixins
Asked Answered
A

2

12

I've developed a lot of code in Java and dabbled in Groovy and Haskell which has now led me to Scala.

I feel relatively comfortable with the functional side of Scala, but I'm finding myself a bit shaky on object oriented design in Scala, because it feels a bit different to Java, in particular due to traits/mix-ins.

I aim to write code that is as testable as possible, which in my Java development has always translated into focus on

  • Immutability where possible
  • Prefer injection of state by constructors
  • ALWAYS go for composition instead of inheritance (heavily influenced by, and likely an over-reaction to this post on SO)

Now I'm trying to land on my feet in this new Scala territory, and I'm having a hard time figuring out what approach I should go for here, in particular whether I should start using inheritance for some purposes.

Programming Scala (Wampler and Payne; O'Reilly, 2nd Edition) has a section with considerations ("Good Object-Oriented Design: A Digression"), and I've read a number of posts on SO, but I haven't seen explicit mentions of the design consideration of testability. The book offers this advice on using inheritance:

  1. An abstract base class or trait is subclassed one level by concrete classes, including case classes.
  2. Concrete classes are never subclassed, except for two cases:
    • Classes that mix in other behaviors defined in traits (...)
    • Test-only versions to promote automated unit teting.
  3. When subclassing seems like the right approach, consider partitioning behaviors into traits and mix in those traits instead.
  4. Never split logical state across parent-child type boundaries.

Some digging on SO also suggests that sometimes mix-ins are preferable to composition.

So in essence I have two questions:

  1. Are there common cases where it would be better, even considering testability, to use inheritance?

  2. Do mix-ins offer good ways to enhance the testability of my code?

Abernathy answered 14/2, 2015 at 16:0 Comment(0)
G
11

The trait usage in the Q/A you referenced was really dealing with the flexibility provided by mixing in traits.

In example, when you extend a trait explicitly the compiler locks the types of the class and the super-class at compile time. In this example MyService is a LockingFlavorA

trait Locking { // ... }

class LockingFlavorA extends Locking { //... }

class MyService extends LockingFlavorA {

}

When you used a typed self reference (as shown in the Q/A you pointed to):

class MyService {
   this: Locking =>
}

.. the Locking can refer to Locking itself, or any valid subclass of Locking. The author then mixes in the locking implementation at the call site, without explicitly creating a new class for that purpose:

val myService: MyService = new MyService with JDK15Locking

I think when they say you can ease testing, they're really talking about using this functionality to emulate what we Java developers would normally do with composition and mock objects. You simply make a mock Locking implementation and mix that one in during test, and make a real implementation for runtime.

To your question: is this better or worse than using a mocking library and dependency injection? It would be hard to say, but I think in the end a lot of it is going to come down to how well one technique or the other plays with the rest of your codebase.

If you're already using composition and dependency injection to good effect, I would reckon that continuing with that pattern may be a good idea.

If you're just starting out and don't really have the need for all that artillery yet, or haven't philosophically decided that dependency injection is right for you, you can get you a lot of mileage from mixins for a very small cost in runtime complexity.

I think the true answer will prove to be highly situational.

TL;DR below

Question 1) I think it's a situationally useful alternative to composition/dep-inj, but I don't think it provides any major gain other than perhaps simplicity.

Question 2) Yes it can improve testability, largely by emulating mock objects via trait implementations.

Gagne answered 17/2, 2015 at 19:1 Comment(4)
Thank you. I really appreciate this answer as it provides good commentary on my references and direct answers to the questions.Abernathy
No problem, Scala is an interesting language and it's good to understand how some of these more esoteric language features fit in with what people have already been doing out there in the world. It was a good question.Gagne
hmm... judging by its big fat zero score, I suppose it could have been better. But I got my answer, that's the important part.Abernathy
Lol, people are self-interested, me included. I notched it up, i really did think it was a good question.Gagne
F
3

I have made could experience using a combination of mix-ins and composition.

so by example use component to mixin behaviour into a specific trait. The example below shows a structure using multiple dao layer traits in a class.

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))
    }
  }
}

After that you can declare a concrete component and bind it by example to the companion object:

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

object SomeTraitsUsingMixing extends ConreteTraitsComponent

Using this pattern yo could easily create a test component and using mock to test the concrete behaviour of your tait/class:

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

object SomeTraitsUsingMixingMock extends SomeTraitsComponentMock

And in you spec you could declare control the results of the services using ScalaMock http://scalamock.org/

Fatally answered 17/2, 2015 at 6:18 Comment(2)
Thank you for your answer. Can you go into a bit more detail about why you chose to make ConcreteTraitsComponent a trait rather than a class?Abernathy
I prefer to declare a unit of functions as a trait as long as possible because in scala it's only possible to extend from one class but from multiple traits.Fatally

© 2022 - 2024 — McMap. All rights reserved.