2. À La Carte
Apéritif
TDD and programing languages
Entrée
A short overview of Scala’s features
Plat Principal
• Better value objects using Case Classes
• Determinism via immutability
3. À La Carte
Plat Principal (cont’d)
• Better type safety, no nulls and less exception
throwing
• Better composition and cross-cutting concerns
using Traits
• Declarative asynchrony
Le Dessert
• Specs2
• ScalaTest
• Plain old JUnit
• Mocking
5. TDD and programing languages
There’s a set of programming language features that are
necessary in order to grow software using TDD. These
include referential transparency, well-defined types that
are easy to declare, the ability to separate concerns into
individual codes of block and, of course, providing the
means to write short, concise and clear tests.
7. A short overview of Scala’s features
• A functional/OO programming language that runs on
the JVM
• Everything is an object – no primitive types
• Functions are first-class members, every function is a
value, including what is usually an operator
• Scala is statically-typed and supports type inference
8. A short overview of Scala’s features
• Lambda expressions, closures and currying naturally
• Pattern matching
• Multiple inheritance through Traits
• Scala is extensible, allowing you to write your own
“language structures”
10. Case Classes
Good software engineering makes use of value objects.
These need to encapsulate the way they represent their
state, to provide information hiding and to be easy to
maintain.
case class Cat(name: String, age: Int,
kittens: Seq[Cat] = Nil)
val philip = Cat(name = “Philip”, age = 9)
val aKitten = Cat(age = 1, name = “Shraga”)
val withKittens = philip.copy(kittens = Seq(aKitten))
11. Determinism via Immutability
Scala encourages everything to be immutable by default:
• Variables (vals)
• Collections
• Value objects (using Case Classes)
• Composition of collaborators
12. Determinism via Immutability
As a result, we get rid of annoying problems such as
aliasing, concurrent modifications of collections and
objects that have an unknown state
case class Cat(kittens: Seq[Cat] = Nil,
dirty: Boolean = true) extends Pet
class Clinic(val shower: PetShower) {
def wash(cat: Cat): Cat = {
val kittens = cat.kittens.map shower.wash
shower.wash(cat.copy(kittens = kittens)
}
}
13. Determinism via Immutability
Testing Clinic.wash() should prove quite simple
val shower = mock[PetShower]
val clinic = new Clinic(shower)
val kitten1 = Cat(dirty = true)
val kitten2 = Cat(dirty = true)
val mom = Cat(kittens = Seq(kitten1, kitten2)
clinic.wash(mom)
verify(shower).wash(kitten1)
verify(shower).wash(kitten2)
verify(shower).wash(mom)
14. Better type safety
• Scala is statically and strongly typed; type inference
keeps the code lean and mean
• Stricter generics (in comparison to Java) provide better
compile-time checks
• Advanced features include structural types and type
aliases
15. No nulls
Scala urges us to declare a possible return value using the
Option[T] monad; an option can be either Some(value) or
None, and allows us to assume to it can never be null. We
can use collection semantics to consume an Option[T].
def foo: Option[Foo]
foo match {
case Some(Foo(bar)) => println(bar)
case _ => println(“No foo found”)
}
val barOrDefault = foo.map(_.bar).getOrElse(“no foo”)
16. Less exception throwing using Try[T]
• Try[T] is an abstract monad type with two concrete
implementations, Success[T] and Failure[E]
• Represents the result of an operation which may fail
• Automatically translate an exception-throwing clause
to a Try[T] using the Try() apply method
17. Less exception throwing
class SomeJavaObject {
public Bar tryFoo() throws FooException {…}
}
val someJavaObject = new SomeJavaObject
val maybeBar = Try(someJavaObject.tryFoo())
maybeBar match {
case Success(bar) => println(bar)
case _ => reportFailureAndRetry()
}
18. Better composition using Traits
Scala provides the means for multiple inheritance using
Traits; this can be useful for separating related but
independent pieces of logic into separate units of code,
each with its own test suite.
class ComponentFactory extends ImageCreation
with TextCreation with VideoCreation with … {
def create(cd: ComponentDefinition) = cd match {
case Image(url, dimensions) => makeImage(…)
case Text(text, kind) => makeText(…)
…
}
}
19. Better composition using Traits
trait ImageCreation {
def makeImage(url: String, dimensions: Dimensions)
= {…}
}
class ImageCreationTest
extends SpecificationWithJUnit {
val creator = new ImageCreation {… // init code}
“makeImage” should {
“create an image” in {…}
“fail gracefully” in {…}
}
}
20. Cross-cutting concerns using Traits
In the Java world, AOP can be used to add cross-cutting
concerns to existing code without altering it, but has the
downside of being non-transparent or too implicit. This
makes it hard to figure out which aspects are applied at
runtime, and impossible to test that aspects are indeed
being applied properly.
Let’s look at an example using the canonical use case for
AOP – auditing.
21. Cross-cutting concerns using Traits
trait Auditing {
def auditor: Auditor
def audited(f: () => T): T = {
auditor.before(…)
val ret: T = f()
auditor.after(…)
ret
}
}
class Foo(baz: Baz, val auditor: Auditor)
extends Auditing {
def bar() {
audited {
baz.doSomething()
}
}
}
22. Cross-cutting concerns using Traits
class FooTest extends SpecificationWithJUnit {
val auditor = mock[Auditor]
val baz = mock[Baz]
val foo = new Foo(baz, auditor)
“Foo” should {
“call baz” in {
foo.bar()
got {
one(baz).doSomething()
one(auditor).audit(…)
}
}
}
}
23. Declarative asynchrony
Scala 2.10 adds a top notch Promise/Future library with
support for composing and pipelining, using callback or
monadic semantics.
A Future[T] will never throw an exception, it will return a
Try[T].
27. Specs2
• Is somewhat of a de-facto standard in the Scala
community
• Github, active community, frequent releases
• Support for RSpec-style and BDD-style test code
• Rich matcher library
• Mediocre documentation
• Built-in Hamcrest integration
28. ScalaTest
• Somewhat behind Specs2 in terms of adoption
• Supports a myriad of test formats (RSpec, BDD, XUnit,
etc)
• Rich and reliable documentation
• Poor matcher library
• No built-in Hamcrest integration
29. Plain-old JUnit
• Lots of boilerplate
• Hamcrest doesn’t play well with Scala (for instance, for
matching collections)
• Less magic in comparison with Specs2 and ScalaTest
• No namespace collisions and easier to debug if
something weird happens
30. Mocking
• ScalaTest supports ScalaMock, Mockito, JMock and
EasyMock
• Specs2 only supports Mockito out of the box but
writing your own sugar using Mixin traits is easy
• ScalaMock is a native Scala mock objects library. Worth
adopting if you don’t already rely heavily on another
library
This is not a talk about TDD. This is a talk about the language features in Scala that correspond with better software engineering, as required in order to grow software using TDD.
Functional + immutability gives us referential transparency
Extensibility allows us to write our custom, embedded DSLs, and consume DSLs from 3rd party languages. Specs2 is a good example for such DSL for semantic XDD
A case class automatically makes its constructor arguments into vals. It also provides automatic equals, hashCode, toString methods and a very useful copy method which makes use of Scala’s named arguments (with defaults) feature.We also see that case classes with proper default argument values are essentially test object builders, which saves us tons of code for test setup
It's very easy to do this wrong in Java; a Scala object that can't be initialized without its collaborators is the default way you write classes inScala
Shower.wash returns a new instance of the input cat, presumably setting some “isDirty” flag to false. Note how we pass it with a new instance of the cat that gets a new sequence of its kittens, now washed.Note how kittens gets a default value of Nil, meaning an empty list. This means that our unit test will never have to test a case where kittens is nullScala doesn’t need a return statement; the return value of shower.wash is the return value of clinic.wash.
Statically typed – types are checked in compile time verses runtimeStrongly typed - each variable must have a concrete typeYou get the best of both worlds – lean code like in dynamic languages and compile-time checking of adherence to structureType inference can slow down compilation – use with careStructural types – the canonical example is closeable
Tony Hoare, who invented the null reference publicly apologized for it http://qconlondon.com/london-2009/presentation/Null+References:+The+Billion+Dollar+Mistake
Try retains the type safety of checked exceptions (that is missing from Scala) while reducing boilerplate such as cascading throws statementsThis does not directly aid in testing, but decluttering the interfaces makes the resulting product less verbose and easier to understand. Also, eliminating cascaded throws statements simplifies the test cases
The diamond problem is solved by defining an order of precedence calculated by the order of trait declarationConsider, for instance, a ComponentFactory. From the point of view of its consumer, there should be a single entry point through which components need to be created. However, creating a text component and creating an image component have nothing in common. The solution – separate the creation of different component types to different traits, each with its own suite of tests.
Note how it seems like we added a new language construct named audited. This appearance is aided by the fact that a code block surrounded by curly braces is treated as a function with no parameter list, and that a function that takes a single parameter can be called without parentheses.
Test needs to be clearer
Who knows what a Future is?For Scala 2.9.x, you can use Twitter’s util.core package which provides a wonderful alternative for 2.10’s Futures
This code passes a collection of Baz objects to the Foo.bar method, getting a Bar for each instance of Baz. We do this in parallel. After we’re done, we pass the collection of Bars to our listener. Upon failure, we call some method that reports failure and retries, presumably by calling deliver() againUnit is a void
This is a simplistic, sunny-day only test. A proper test would also simulate failure and check that the retry method is calledThe executor service is either a same-thread executor (which guarantees that the call to deliver() will not return until all calls to vaccinator have returned) or an instance of deterministic scheduler (which will need some massage between the call to deliver() and the verification logic)
Has Hamcrest integration in form of a mixin trait that provides implicit conversion from Hamcrest matcher to Specs2 matcher. However, using built-in hamcrest matchers proves difficult due to namespace collision with Specs2’s own matcher library.Composing Specs2’s matcher library is possible the the syntax is so obscure that you’ll probably won’t be able to read your own code 5 minutes after completing it
It’s fairly easy to create a Hamcrest -> ScalaTest matcher adapter, but none is provided built-in. Composing ScalaTest matcher lies between hard and impossible.
I won’t tell you what to use, your millage may vary. If adopting Scala into an existing project with lots of JUnit tests, you may want to stick with what works. For a new project, try to decide between Specs2 and ScalaTest based on documentation and ease of use. Ask your colleagues, experiment with both and decide.