How Scala promotes TDD

3,848 views

Published on

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

Published in: Technology
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,848
On SlideShare
0
From Embeds
0
Number of Embeds
193
Actions
Shares
0
Downloads
40
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide
  • 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.
  • How Scala promotes TDD

    1. 1. How Scalapromotes TDDaudience.filter(_.usesJava).foreach { member => sayHi(member)} How Scala allows you to write better and more testable code
    2. 2. À La CarteApéritif TDD and programing languagesEntrée A short overview of Scala’s featuresPlat Principal • Better value objects using Case Classes • Determinism via immutability
    3. 3. À La CartePlat Principal (cont’d) • Better type safety, no nulls and less exception throwing • Better composition and cross-cutting concerns using Traits • Declarative asynchronyLe Dessert • Specs2 • ScalaTest • Plain old JUnit • Mocking
    4. 4. Apéritif
    5. 5. TDD and programing languagesThere’s a set of programming language features that arenecessary in order to grow software using TDD. Theseinclude referential transparency, well-defined types thatare easy to declare, the ability to separate concerns intoindividual codes of block and, of course, providing themeans to write short, concise and clear tests.
    6. 6. Entrée
    7. 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. 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”
    9. 9. Plat Principal
    10. 10. Case ClassesGood software engineering makes use of value objects.These need to encapsulate the way they represent theirstate, to provide information hiding and to be easy tomaintain.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. 11. Determinism via ImmutabilityScala encourages everything to be immutable by default:• Variables (vals)• Collections• Value objects (using Case Classes)• Composition of collaborators
    12. 12. Determinism via ImmutabilityAs a result, we get rid of annoying problems such asaliasing, concurrent modifications of collections andobjects that have an unknown statecase class Cat(kittens: Seq[Cat] = Nil, dirty: Boolean = true) extends Petclass Clinic(val shower: PetShower) { def wash(cat: Cat): Cat = { val kittens = cat.kittens.map shower.wash shower.wash(cat.copy(kittens = kittens) }}
    13. 13. Determinism via ImmutabilityTesting Clinic.wash() should prove quite simpleval 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. 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. 15. No nullsScala urges us to declare a possible return value using theOption[T] monad; an option can be either Some(value) orNone, and allows us to assume to it can never be null. Wecan 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. 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. 17. 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()}
    18. 18. Better composition using TraitsScala provides the means for multiple inheritance usingTraits; this can be useful for separating related butindependent 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. 19. 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 {…} }}
    20. 20. Cross-cutting concerns using TraitsIn the Java world, AOP can be used to add cross-cuttingconcerns to existing code without altering it, but has thedownside of being non-transparent or too implicit. Thismakes it hard to figure out which aspects are applied atruntime, and impossible to test that aspects are indeedbeing applied properly.Let’s look at an example using the canonical use case forAOP – auditing.
    21. 21. 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() } }}
    22. 22. 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(…) } } }}
    23. 23. Declarative asynchronyScala 2.10 adds a top notch Promise/Future library withsupport for composing and pipelining, using callback ormonadic semantics.A Future[T] will never throw an exception, it will return aTry[T].
    24. 24. 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 } }}
    25. 25. 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)) } }}
    26. 26. Le Dessert
    27. 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. 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. 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. 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
    31. 31. Questions? shaiy@wix.comhttp://shaiyallin.wix.com/about

    ×