How Scala promotes TDD

Post on 18-May-2015

2.502 views 1 download

Tags:

description

This talk demonstrates why Scala is in my opinion the best language to do TDD on

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?

shaiy@wix.com

http://shaiyallin.wix.com/about