Prawie każdy programista docenia dobrze napisane testy jednostkowe i funkcjonalne, ale z jakiegoś powodu większość z nas nie lubi ich pisać. Test driven development jak wszystko ma swoje plusy i minusy, ale nawet jeśli jesteś przeciwnikiem TDD, testy są świetnym sposobem na zablokowanie wyników już wykonanych i działających funkcjonalności. Mogą one w pewnym stopniu zapobiec zniszczeniu przez programistów ich poprzedniej pracy podczas tworzenia lub naprawiania innych funkcjonalności.

W tym artykule skupimy się na pisaniu testów dla aplikacji serwerowej PlayFramework.

ScalaTest

ScalaTest jest jedną z najczęściej używanych bibliotek testujących w Scali. Oferuje integrację z popularnymi narzędziami testującymi jak Mockito, JUnit, Selenium oraz z popularnymi IDE jak IntelliJ, Eclipse, NetBeans. Cała jego biblioteka została zaprojektowana tak, aby być jak najbardziej „na uboczu”. Pozwala programistom wybrać, jaki styl pisania testów najbardziej im pasuje. Istnieje 8 dostępnych stylów kodu, które są podobne do najpopularniejszych bibliotek takich jak xUnit, Ruby’s RSpec, specs2.

Dzięki swojej popularności ScalaTest posiada bibliotekę integracyjną dla Play scalatestplus-play . Aby go użyć, wystarczy dodać jego konkretną wersję do build.sbt w ten sposób:

libraryDependencies ++= Seq(

„org.scalatestplus.play” %% „scalatestplus-play” % „x.x.x” % „test”

)

 

Gdzie x.x.x oznacza konkretną wersję, która jest kompatybilna z Twoją wersją Play.

Do testowania aplikacji Play zaleca się używanie stylu testowania PlaySpec który używa specyfikacji jak stylu kodu.

Wszystkie klasy testowe i klasy pomocnicze żyją wewnątrz katalogu test w katalogu głównym projektu.

Pierwsza specyfikacja testowa

Powiedzmy, że mamy obiekt Calculation który wygląda tak:

object Calculation {
   def add(x: Int, y: Int) = {
     x + y
   }
    def subtract(x: Int, y: Int) = {
     x - y
   }
}

Aby go przetestować, musimy stworzyć nową klasę, która rozszerza PlaySpec.

class CalculationSpec extends PlaySpec {
   "The add method" must {
       "add two numbers" in {
       Calculation.add(2, 2) mustBe (4)
       }
   }
  
   "The subtract method" must {
       "subtract two numbers" in {
       Calculation.subtract(2, 2) mustBe (0)
       }
   }
}

Powyższy kod przetestuje obie nasze metody Calculation. Jak widać taki styl pisania testów jest łatwy do odczytania, ponieważ w zasadzie budujemy zdania opisujące jak funkcja ma działać. Aby stwierdzić prawdziwość danego stwierdzenia używamy matchera mustBe.

Dodajmy jeszcze jedną metodę do naszego obiektu Calculation.

def divide(x: Int, y: Int) = {
   x / y
}

Teraz dodamy kolejne testy:

"The divide method" must {
   "divide given numbers" in {
       Calculation.divide(2, 2) mustBe (1)
   }
}

it must {
   "throw ArithmeticException when dividing by zero" in {
       assertThrows[ArithmeticException] {
       Calculation.divide(2, 0)
       }
   }
}

Pierwszy wygląda znajomo, ale drugi jest nieco inny. Najpierw używait które kopiuje poprzednią nazwę. Kolejna różnica jest z assertingiem. Ponieważ dzielenie przez 0 jest niemożliwe, nasza metoda powinna rzucić wyjątek, gdy tak się stanie. Aby sprawdzić czy został on rzucony, używamy assertThrows z wyjątkiem, który chcemy złapać.

Mockowanie danych

Jak wcześniej wspomniano ScalaTest obsługuje wyśmiewanie danych, a Play wykorzystuje do tego zadania Mockito. Aby użyć wyśmiewanych danych w naszym teście, musimy stworzyć klasę specyfikacyjną testu, która rozszerza PlaySpec i MockitoSugar . Aby pokazać jak działa wyśmiewanie, poniższy przykład przedstawia dwa testy. Jeden z wyśmiewaną funkcją – zwraca ona określoną wartość, mimo że w aktualnym stanie powinna zwracać inną wartość – oraz drugi bez wyśmiewania. Oba te testy powinny zostać zaliczone.

class TestClassSpec extends PlaySpec with MockitoSugar {
   "A mocked TestClass" must {
       "return a mocked value" in {
       val mockTest = mock[TestClass]
       when(mockTest.someMethod).thenReturn(10)
       mockTest.someMethod mustBe 10
       }
   }

   "A TestClass" must {
       "return a default value" in {
       val test = new TestClass
       test.someMethod mustBe 123
       }
   }
}
  
class TestClass {
   def someMethod = 123
}