domingo, 13 de septiembre de 2015

Introducción a ScalaTest


Vamos a hacer algunos test con ScalaTest. Empecemos haciendo una clase:

case class Guest(name: String)

case class Room(number: Int, guest: Option[Guest] = None){ room =>

  def isAvailable(): Boolean = ???

  def checkin(guest: Guest): Room = ???

  def checkout(): Room = ???

}

/*
 * We will automatically create 10 rooms
 * if these are not specified.
 */
case class Hotel(
  rooms: List[Room] = (1 to 10).map(n => Room(number=n)).toList){

  def checkin(personName: String): Hotel = ???

}

Hemos hecho una clase Hotel que contiene 10 habitaciones. El ??? significa que el metodo no fue definido, pero me permite compilar. Vamos a trabajar bien TDD.

Antes que nada agregamos el framework al proyecto utilizando sbt:

libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.4" % "test"

Ahora vamos a hacer una clase común para reutilizar métodos comunes:

package org.me.hotel

import org.scalatest.{FlatSpec,Matchers}

abstract class UnitTest(component: String) extends FlatSpec
  with Matchers{

  behavior of component

}

Como se puede ver hicimos una clase padre o general para todos los tests, esta extiende de FlatSpec e implementa Matchers.

A la vez le pasamos por parámetro el nombre del componente a probar. Si quisieramos utilizar esta clase lo hariamos de la siguiente manera:

class MyClassUnitTest extends UnitTest("MyClass")

A la vez utilizamos Matchers dado que nos provee expresiones más cercanas a los humanos como:

(2+2) should equal (4)
(2+2) shouldEqual 5
(2+2) should === (4)
(2+2) should be (4)
(2+2) shouldBe 5

Bueno ahora definimos nuestro test:

package org.me.hotel

class RoomTest extends UnitTest("Room") {

  it should "provide info about its occupation" in {
    Room(1).isFree() shouldEqual true
    Room(1,None).isFree() shouldEqual true
    Room(1,Some(Guest("Bruce"))).isFree() shouldEqual false
  }

  it should "allow registering a new guest if room is free" in {
    val occupiedRoom = Room(1).checkin(Guest("James"))
    occupiedRoom.isFree shouldEqual false
    occupiedRoom.guest shouldEqual(Option(Guest("James")))
  }

  it should "deny registering a new guest if room is already occupied" in {
    an [IllegalArgumentException] should be thrownBy {
      Room(1,Some(Guest("Barbara"))).checkin(Guest("Bruce"))
    }
  }

  it should "deny checking out if room is already free" in {
    an [IllegalArgumentException] should be thrownBy {
      Room(1).checkout()
    }
  }

  it should "allow checking out if room is occupied by someone" in {
    val room = Room(1,Some(Guest("Carmine")))
    val freeRoom = room.checkout()
    freeRoom.isFree shouldEqual true
  }

}

Como se puede ver los test quedan escritos casi en lenguaje natural.

Si corremos los tests nos darán un error:

> test
[info] RoomTest:
[info] Room
[info] - should provide info about its occupation *** FAILED ***
[info]   scala.NotImplementedError: an implementation is missing
[info]   at scala.Predef$.$qmark$qmark$qmark(Predef.scala:252)
[info]   at org.me.hotel.Room.isFree(Room.scala:6)
[info]   at org.me.hotel.RoomTest$$anonfun$1.apply$mcV$sp(RoomTest.scala:6)
[info]   at org.me.hotel.RoomTest$$anonfun$1.apply(RoomTest.scala:5)

...

[info] Run completed in 699 milliseconds.
[info] Total number of tests run: 15
[info] Suites: completed 6, aborted 0
[info] Tests: succeeded 3, failed 12, canceled 0, ignored 0, pending 0
[info] *** 12 TESTS FAILED ***
[error] Failed tests:
[error]   org.me.hotel.RoomTest
[error]   org.me.hotel.HotelTest
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 6 s, completed 12-ago-2015 16:08:47

Ahora a hacer que nuestras clases pasen sus tests, Suerte!!!!