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 }