specs and ScalaTips and tricks for a friendly DSL syntax
Tuesday, July 27, 2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
About…
About…
About…
     tour
       tourTestSpecificationspecifyexampleexampleexampleexampleexampleexampleexampleexampleexample
       tourimportIncredibleStringReverser._classReverserSpecextends Specification {"a reversed empty string must be empty" in {    reverse("") must_== ""  }"a reversed empty string must really *be empty*" in {    reverse("") must be empty  }"a reversed string must be reversed abc -> cba" in {    reverse("abc") must be_==("cba")  }"a longer string must also be reversed. Whoops!" in {    reverse("abcdef") must be_==("xxxxx")  }}
       tour"a reversed empty string must be empty" in {  reverse("") must be empty}
       tourSpecification "ReverserSpec"+ a reversed empty string must be empty  + a reversed empty string must really *be empty*  + a reversed string must be reversed abc -> cbax a longer string must also be reversed. Whoops!    'fedcba' is not equal to 'xxxxx' (ReverserSpec.scala:17)Total for specification "ReverserSpec":Finished in 0 second, 140 ms4 examples, 4 expectations, 1 failure, 0 error
       tourrepetitionrepetitionrepetitionrepetitionrepetitionrepetitionorganizeSystemUnderSpecification
       tourclass Reverser2Spec extends Specification {"a reversed empty string" should {    val reversed = reverse("")  "be empty" in {      reversed must_== ""    }"really *be empty*" in {      reversed must be empty    }  }"a reversed non-empty string" should {    val reversed = reverse("abc")  "be reversed abc -> cba" in {      reversed must be_==("cba")    }"have the same size as the original string" in {      reversed must have size(3)    }  }}
       tour"a reversed empty string" should {  val reversed = reverse("")    …}"a reversed non-empty string" should {  val reversed = reverse("abc")    …}
       tour
       tourBe specificMatchers
       tourtraitHelloWorldSnippet {def hello(s: String)=           <div class="txt">Hello {s}</div>}"the snippet must output a div element" >> {  hello("Eric") must \\(<div/>)}"it must have an attribute class=txt" >> {  hello("You") must \\(<div/>, "class" -> "txt")}
       tour  hello("Eric") must \\(<div/>)
       tourBe exhaustiveProperties
       tourclass Reverser3Spec extends Specification withScalaCheck {  "reverse must preserve the length of a string" verifies {     s: String => reverse(s).size == s.size  }"reverse applied twice must return the same string" verifies {       s: String => reverse(reverse(s)) == s   }  "reverse 2 concatenated strings must return the reversed second    string concatenated with the reversed first one" verifies {    (s1: String, s2: String) => reverse(s1 + s2) ==                                 reverse(s2) + reverse(s1)   }defcenter(s: String) = s(s.size / 2)  "keep the same 'center' character - Whoops!" verifies {     s: String => s.isEmpty || center(reverse(s)) == center(s)   }}
       tour  "reverse applied twice must return " +  "the same string" verifies {       s: String => reverse(reverse(s)) == s   }
       tourx keep the same 'center' character - Whoops!  A counter-example is 'bc'   (after 1 try – shrinked ('bcab' -> 'bc'))
       tourIsolateMocks
       tour/** * A simple Observable-Observer implementation */trait Observable {privatevar observers: List[Observer] = Nildef add(o: Observer) =             observers = o :: observersdef changed(event: String) =      observers foreach (_.notify(event))}trait Observer {def notify(event: String)}
       tourclassObservableSpecextends Specification withMockito {val observer = mock[Observer]val observable = new Observable { add(observer) }"An observable notifies its observers if changed" >> {observable.changed("event")    there was one(observer).notify("event")  }"Each change event is notified" >> {observable.changed("event1")observable.changed("event2")    there was two(observer).notify(startWith("event"))  }}
       tour"An observable notifies its observers if " +"changed" >> {observable.changed("event")  there was one(observer).notify("event")}
     DSLHow does it work?
     DSLThe  best tool in the box"This is ok" in {  1 + 1}// is the same as"This is ok".in(1 + 1)"In"method  on String ?!
     DSLThe  best tool in the boxclass Example(description: String) {  def in(e: Any) = e}implicitdefforExample(d: String) = {  new Example(d)}
     DSLNamingforExample("this works") in {  1 + 1}
     DSLSome examples"This is true" in {   true must beTrue}3.seconds3 seconds3 times { i => println(i) }
Tuesday, July 27, 2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
     DSLRestrict
     DSLRestrictclass Example(description: String) {  def in(e: Any) = expectations  def tag(t: String) = this}
     DSLRestrict "this is a" tag ("really?")
     DSLRestrictclassExampleDesc(description: String) {  def in(e: Any): Example = new    Example(description, e) }}class Example(d: String, e: Any) {  def tag(t: String): Example = this}
     DSLCombine
     DSLCombinetrait Matcher[-T] {def apply(y: =>T): (Boolean, String, String)def not: Matcher[T]def when(condition: =>Boolean): Matcher[T]def unless(condition: =>Boolean): Matcher[T]deforSkip: Matcher[T]def or[S <: T](m: Matcher[S]): Matcher[S]defxor[S <: T](m: Matcher[S]): Matcher[S]def and[S <: T](m: Matcher[S]): Matcher[S]def ^^[S<: T, U](f: S => U): Matcher[U]deftoIterable: Matcher[Iterable[T]]}
     DSLSome examplesdef beFalse = beTrue.notcondition must beTrue or beFalsecondition must beTrue.unless(condition2)def trimmedSize(i: Int) = size(i) ^^ (_.trim)string must have trimmedSize(3)
     DSLAdd, add, add
     DSLRepeated parametersinclude(spec1)include(spec1, spec2)1 must beOneOf(1, 2, 3)
     DSLUse and pass result + 1    // result = ? result.pp + 1 // print and pass
     DSLUse and passCustomers.findBy(_.age >= 18)   aka "adult customers"  must haveSize(3)> adult customers 'Bob, Lee'     doesn't have size 3
     DSLthis.typeclassBigSpecextends Spec {// I want to "tag" the   // slow spec as slow  include(slowSpec, fastSpec)  }
     DSLthis.typeclassBigSpecextends Spec {  include(slow tag "slow", fast)  }
     DSLthis.typeclass Spec extends Tagged {  def include(other: Spec*) = …}trait Tagged {  def tag(t: String): ?}
     DSLthis.type// store the tag and return thisdef tag(t: String): this.type = this
     DSLConfigurationval prop = forAll { (s: String) =>   reverse(reverse(s)) == s } "With the default configuration" in {  prop must pass}
     DSLConfiguration"With a 3 tests ok" in {  prop must pass(set(minTestsOk -> 3))}"With 3 tests ok – in the console" in {  prop must pass(display(minTestsOk -> 3))}
     DSLImplicit  parametersdef pass(implicit p: Params) = {  new Matcher[Prop] {def apply(prop: =>Prop) = check(prop)(p) }}implicitvaldefaultParams = newParams
     DSLBelazy
     DSLBe lazy"This should not explode" in {  error("boom")}
     DSLBe lazyclassExampleDesc(d: String) {  def in(e: =>Any) =     new Example(description, e)}class Example(d: String, e: =>Any)
     DSLBe lazy  def in(e: =>Any) = …
     DSLBy name  parameters  Evaluate once!classEqualMatcher[T](x: =>T) extends Matcher[T] { def apply(y: =>T) = {val (a, b) = (x, y)    (a == b, a + " is equal to " + b,              a + " is not equal to " + b)  }}
     DSLBy name  parameters  With varargs?// not legal!! def method[T](params: =>T*) method(1., 2., math.pow(100, 100))
     DSLBy name  parameters  With varargs!implicitdef lazyfy[T](value: =>T) =      new LazyParameter(() => value)class LazyParameter[T](value: () => T) {lazyval v = value()def get() = v}
     DSLBy name  parameters  With varargs!def method[T](params: LazyParameter[T]*)
Tuesday, July 27, 2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
     DSLOperatorsclassExampleDesc(d: String) {  def >>(e: =>Any) = new Example(d, e)}"When I setup the system" >> {  "And a customer is entered" >> {"if he has a discount" >> {}"if he doesn't have a discount" >> {}  }}
     DSLOperators// contexts "A full stack" ->-(fullStack) should { … }// matchers  xml must \\(<node/>)// repl matcher > "This is the expected result"
     DSLDataTables/** * Fit-like table in Scala? *  *  | a | b | c | *  | 1 | 2 | 3 | *  | 2 | 2 | 4 | * */   Use ‘!‘  to separate cells and‘|‘  to separate rows
     DSLDataTablesclassDataTablesSpecextends Specification with DataTables {"lots of examples" >> {"a" | "b" | "a + b" |     1  !  2  !    3    |     2  !  2  !    4    |     2  !  3  !    4    |> { (a, b, c) =>       a + b must_== c    }  }}
     DSLDataTables// the ‘play’ operator |> 2  !  3  !  4   |> { (a, b, c) =>
     DSLClass manifests // How to avoid  // throwA(classOf[FailureException]) (1 must_== 2) must         throwA[FailureException]
     DSLClass manifestsimportscala.reflect._defthrowA[T](implicit m: ClassManifest[T]) =   new Matcher[Any] {  // use m.erasure}
     DSLMake a guess"When I setup the system" >> {  "And a customer is entered" >> {"if he has a discount" >> {}"if he doesn't have a discount" >> {}  }}Discover leaves without executing?     DSLMake a guessdef in[T : Manifest](e: =>T) = {expectationsAre(e)if (manifest[T].erasure == getClass)hasNestedExamples = truethis}
Tuesday, July 27, 2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
     DSLhttp://code.google.com/p/specshttp://etorreborre.blogspot.com/oscon-2010Scala 2.8.0 inside !
Oscon 2010 Specs talk

Oscon 2010 Specs talk

  • 1.
    specs and ScalaTipsand tricks for a friendly DSL syntax
  • 2.
    Tuesday, July 27,2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
  • 3.
  • 4.
  • 5.
  • 6.
    tour
  • 7.
    tourTestSpecificationspecifyexampleexampleexampleexampleexampleexampleexampleexampleexample
  • 8.
    tourimportIncredibleStringReverser._classReverserSpecextends Specification {"a reversed empty string must be empty" in { reverse("") must_== "" }"a reversed empty string must really *be empty*" in { reverse("") must be empty }"a reversed string must be reversed abc -> cba" in { reverse("abc") must be_==("cba") }"a longer string must also be reversed. Whoops!" in { reverse("abcdef") must be_==("xxxxx") }}
  • 9.
    tour"a reversed empty string must be empty" in { reverse("") must be empty}
  • 10.
    tourSpecification "ReverserSpec"+ a reversed empty string must be empty + a reversed empty string must really *be empty* + a reversed string must be reversed abc -> cbax a longer string must also be reversed. Whoops! 'fedcba' is not equal to 'xxxxx' (ReverserSpec.scala:17)Total for specification "ReverserSpec":Finished in 0 second, 140 ms4 examples, 4 expectations, 1 failure, 0 error
  • 11.
    tourrepetitionrepetitionrepetitionrepetitionrepetitionrepetitionorganizeSystemUnderSpecification
  • 12.
    tourclass Reverser2Spec extends Specification {"a reversed empty string" should { val reversed = reverse("") "be empty" in { reversed must_== "" }"really *be empty*" in { reversed must be empty } }"a reversed non-empty string" should { val reversed = reverse("abc") "be reversed abc -> cba" in { reversed must be_==("cba") }"have the same size as the original string" in { reversed must have size(3) } }}
  • 13.
    tour"a reversed empty string" should { val reversed = reverse("") …}"a reversed non-empty string" should { val reversed = reverse("abc") …}
  • 14.
    tour
  • 15.
    tourBe specificMatchers
  • 16.
    tourtraitHelloWorldSnippet {def hello(s: String)= <div class="txt">Hello {s}</div>}"the snippet must output a div element" >> { hello("Eric") must \\(<div/>)}"it must have an attribute class=txt" >> { hello("You") must \\(<div/>, "class" -> "txt")}
  • 17.
    tour hello("Eric") must \\(<div/>)
  • 18.
    tourBe exhaustiveProperties
  • 19.
    tourclass Reverser3Spec extends Specification withScalaCheck { "reverse must preserve the length of a string" verifies { s: String => reverse(s).size == s.size }"reverse applied twice must return the same string" verifies { s: String => reverse(reverse(s)) == s } "reverse 2 concatenated strings must return the reversed second string concatenated with the reversed first one" verifies { (s1: String, s2: String) => reverse(s1 + s2) == reverse(s2) + reverse(s1) }defcenter(s: String) = s(s.size / 2) "keep the same 'center' character - Whoops!" verifies { s: String => s.isEmpty || center(reverse(s)) == center(s) }}
  • 20.
    tour "reverse applied twice must return " + "the same string" verifies { s: String => reverse(reverse(s)) == s }
  • 21.
    tourx keep the same 'center' character - Whoops! A counter-example is 'bc' (after 1 try – shrinked ('bcab' -> 'bc'))
  • 22.
    tourIsolateMocks
  • 23.
    tour/** * A simple Observable-Observer implementation */trait Observable {privatevar observers: List[Observer] = Nildef add(o: Observer) = observers = o :: observersdef changed(event: String) = observers foreach (_.notify(event))}trait Observer {def notify(event: String)}
  • 24.
    tourclassObservableSpecextends Specification withMockito {val observer = mock[Observer]val observable = new Observable { add(observer) }"An observable notifies its observers if changed" >> {observable.changed("event") there was one(observer).notify("event") }"Each change event is notified" >> {observable.changed("event1")observable.changed("event2") there was two(observer).notify(startWith("event")) }}
  • 25.
    tour"An observable notifies its observers if " +"changed" >> {observable.changed("event") there was one(observer).notify("event")}
  • 26.
    DSLHow does it work?
  • 27.
    DSLThe best tool in the box"This is ok" in { 1 + 1}// is the same as"This is ok".in(1 + 1)"In"method on String ?!
  • 28.
    DSLThe best tool in the boxclass Example(description: String) { def in(e: Any) = e}implicitdefforExample(d: String) = { new Example(d)}
  • 29.
    DSLNamingforExample("this works") in { 1 + 1}
  • 30.
    DSLSome examples"This is true" in { true must beTrue}3.seconds3 seconds3 times { i => println(i) }
  • 31.
    Tuesday, July 27,2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
  • 32.
    DSLRestrict
  • 33.
    DSLRestrictclass Example(description: String) { def in(e: Any) = expectations def tag(t: String) = this}
  • 34.
    DSLRestrict "this is a" tag ("really?")
  • 35.
    DSLRestrictclassExampleDesc(description: String) { def in(e: Any): Example = new Example(description, e) }}class Example(d: String, e: Any) { def tag(t: String): Example = this}
  • 36.
    DSLCombine
  • 37.
    DSLCombinetrait Matcher[-T] {def apply(y: =>T): (Boolean, String, String)def not: Matcher[T]def when(condition: =>Boolean): Matcher[T]def unless(condition: =>Boolean): Matcher[T]deforSkip: Matcher[T]def or[S <: T](m: Matcher[S]): Matcher[S]defxor[S <: T](m: Matcher[S]): Matcher[S]def and[S <: T](m: Matcher[S]): Matcher[S]def ^^[S<: T, U](f: S => U): Matcher[U]deftoIterable: Matcher[Iterable[T]]}
  • 38.
    DSLSome examplesdef beFalse = beTrue.notcondition must beTrue or beFalsecondition must beTrue.unless(condition2)def trimmedSize(i: Int) = size(i) ^^ (_.trim)string must have trimmedSize(3)
  • 39.
    DSLAdd, add, add
  • 40.
    DSLRepeated parametersinclude(spec1)include(spec1, spec2)1 must beOneOf(1, 2, 3)
  • 41.
    DSLUse and pass result + 1 // result = ? result.pp + 1 // print and pass
  • 42.
    DSLUse and passCustomers.findBy(_.age >= 18) aka "adult customers" must haveSize(3)> adult customers 'Bob, Lee' doesn't have size 3
  • 43.
    DSLthis.typeclassBigSpecextends Spec {// I want to "tag" the // slow spec as slow include(slowSpec, fastSpec) }
  • 44.
    DSLthis.typeclassBigSpecextends Spec { include(slow tag "slow", fast) }
  • 45.
    DSLthis.typeclass Spec extends Tagged { def include(other: Spec*) = …}trait Tagged { def tag(t: String): ?}
  • 46.
    DSLthis.type// store the tag and return thisdef tag(t: String): this.type = this
  • 47.
    DSLConfigurationval prop = forAll { (s: String) => reverse(reverse(s)) == s } "With the default configuration" in { prop must pass}
  • 48.
    DSLConfiguration"With a 3 tests ok" in { prop must pass(set(minTestsOk -> 3))}"With 3 tests ok – in the console" in { prop must pass(display(minTestsOk -> 3))}
  • 49.
    DSLImplicit parametersdef pass(implicit p: Params) = { new Matcher[Prop] {def apply(prop: =>Prop) = check(prop)(p) }}implicitvaldefaultParams = newParams
  • 50.
    DSLBelazy
  • 51.
    DSLBe lazy"This should not explode" in { error("boom")}
  • 52.
    DSLBe lazyclassExampleDesc(d: String) { def in(e: =>Any) = new Example(description, e)}class Example(d: String, e: =>Any)
  • 53.
    DSLBe lazy def in(e: =>Any) = …
  • 54.
    DSLBy name parameters  Evaluate once!classEqualMatcher[T](x: =>T) extends Matcher[T] { def apply(y: =>T) = {val (a, b) = (x, y) (a == b, a + " is equal to " + b, a + " is not equal to " + b) }}
  • 55.
    DSLBy name parameters  With varargs?// not legal!! def method[T](params: =>T*) method(1., 2., math.pow(100, 100))
  • 56.
    DSLBy name parameters  With varargs!implicitdef lazyfy[T](value: =>T) = new LazyParameter(() => value)class LazyParameter[T](value: () => T) {lazyval v = value()def get() = v}
  • 57.
    DSLBy name parameters  With varargs!def method[T](params: LazyParameter[T]*)
  • 58.
    Tuesday, July 27,2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
  • 59.
    DSLOperatorsclassExampleDesc(d: String) { def >>(e: =>Any) = new Example(d, e)}"When I setup the system" >> { "And a customer is entered" >> {"if he has a discount" >> {}"if he doesn't have a discount" >> {} }}
  • 60.
    DSLOperators// contexts "A full stack" ->-(fullStack) should { … }// matchers xml must \\(<node/>)// repl matcher > "This is the expected result"
  • 61.
    DSLDataTables/** * Fit-like table in Scala? * * | a | b | c | * | 1 | 2 | 3 | * | 2 | 2 | 4 | * */ Use ‘!‘ to separate cells and‘|‘ to separate rows
  • 62.
    DSLDataTablesclassDataTablesSpecextends Specification with DataTables {"lots of examples" >> {"a" | "b" | "a + b" | 1 ! 2 ! 3 | 2 ! 2 ! 4 | 2 ! 3 ! 4 |> { (a, b, c) => a + b must_== c } }}
  • 63.
    DSLDataTables// the ‘play’ operator |> 2 ! 3 ! 4 |> { (a, b, c) =>
  • 64.
    DSLClass manifests // How to avoid // throwA(classOf[FailureException]) (1 must_== 2) must throwA[FailureException]
  • 65.
    DSLClass manifestsimportscala.reflect._defthrowA[T](implicit m: ClassManifest[T]) = new Matcher[Any] { // use m.erasure}
  • 66.
    DSLMake a guess"When I setup the system" >> { "And a customer is entered" >> {"if he has a discount" >> {}"if he doesn't have a discount" >> {} }}Discover leaves without executing? DSLMake a guessdef in[T : Manifest](e: =>T) = {expectationsAre(e)if (manifest[T].erasure == getClass)hasNestedExamples = truethis}
  • 67.
    Tuesday, July 27,2010About…specs tourImplicit defRestrictCombineAdd , addBe lazy!&%$#>ManifestsResources
  • 68.
    DSLhttp://code.google.com/p/specshttp://etorreborre.blogspot.com/oscon-2010Scala 2.8.0 inside !