Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala testable code with inheritance and mixins

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?

like image 816
Erik Madsen Avatar asked Feb 14 '15 16:02

Erik Madsen


1 Answers

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.

like image 171
Rich Henry Avatar answered Sep 29 '22 20:09

Rich Henry