How Scala promotes TDD
-
Upload
shai-yallin -
Category
Technology
-
view
2.502 -
download
1
description
Transcript of How Scala promotes TDD
How Scala promotes TDD
How Scala allows you to write better and
more testable code
audience.filter(_.usesJava).foreach { member =>
sayHi(member)
}
À La Carte
ApéritifTDD and programing languages
EntréeA short overview of Scala’s features
Plat Principal• Better value objects using Case Classes • Determinism via immutability
À 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
Apéritif
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.
Entrée
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
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”
Plat Principal
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))
Determinism via Immutability
Scala encourages everything to be immutable by default:
• Variables (vals)
• Collections
• Value objects (using Case Classes)
• Composition of collaborators
Determinism via ImmutabilityAs 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) }}
Determinism via ImmutabilityTesting 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)
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
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”)
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
Less exception throwingclass SomeJavaObject { public Bar tryFoo() throws FooException {…}}
val someJavaObject = new SomeJavaObjectval maybeBar = Try(someJavaObject.tryFoo())
maybeBar match { case Success(bar) => println(bar) case _ => reportFailureAndRetry() }
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(…) … }}
Better composition using Traitstrait 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 {…} }}
Cross-cutting concerns using TraitsIn 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.
Cross-cutting concerns using Traitstrait 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() } }}
Cross-cutting concerns using Traitsclass 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(…) } } }}
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].
Declarative asynchronytrait CatVaccinator{ def vaccinate(cat: Cat): Future[VaccinatedCat]}
trait PetStore { def deliver(cats: Seq[VaccinatedCat]): Unit}
class Vet(vaccinator: CatVaccinator, petStore: PetStore){ def deliver(cats: Seq[Cat]) { Future.sequence(cats.map vaccinator.vaccinate) .onSuccess { vaccinatedCats: Seq[VaccinatedCat] => petStore.deliver(vaccinatedCats) } .onFailure { exception => reportAndRetry(cats) // some retry logic } }}
Declarative asynchrony// SameThreadExecutor or DeterministicExecutorval executorService: ExecutorService = …val vaccinator = mock[CatVaccinator]val petStore = mock[PetStore]val vet = new Vet(vaccinator, petStore)
“Vet” should { “call deliver” in { vaccinator.vaccinate(cat1) returns Future(vcat1) vaccinator.vaccinate(cat2) returns Future(vcat2)
vet.deliver(Seq(cat1, cat2))
got { one(petStore).deliver(Seq(vcat1, vcat2)) } }}
Le Dessert
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
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
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
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
Questions?
http://shaiyallin.wix.com/about