przetestujmy coś – Spock – #02

Potrzebowałam funkcji, która dla podanej daty zwróci mi datę ostatniej soboty (jeśli data jest sobotnia, powinna zwrócić samą siebie). Uznałam, że jest to dobra okazja żeby napisać sobie unit test. Metoda nie powinna być duża, algorytm nie jest złożony. Zróbmy to TDD.

W pracy do pisania testów używam frameworka Spock, który jest prosty do czytania i bardzo potężny jeśli chodzi o parametryzację testów, więc nic dziwnego, że postanowiłam z niego skorzystać tym razem.

Okazało się, że o ile ani metoda, ani algorytm nie nastręczały problemu to wyłożyłam się na połączeniu Androida ze Spockiem. Przeglądałam stackOverflow, githuba, blogi i nie znalazłam nic działającego. W akcie desperacji zajrzałam na slacka na kanał #android. Przeszukałam wpisy pod kątem Spocka i jest – znalazłam, był tam człowiek, któremu udało się to odpalić (Jakub, dzięki jeszcze raz za pomoc).

Po tym przydługim wstępie pokażmy trochę kodu. Na początek konfiguracja

  1. do pliku top-level build.gradle w sekcji dependencies dodajemy linijkę:
     classpath 'org.codehaus.groovy:groovy-android-gradle-plugin:1.1.0'
  2. do modułowego pliku build.gradle dorzucamy:
    apply plugin: 'groovyx.android'
    testOptions {
        unitTests.returnDefaultValues = true
    }
    testCompile 'org.codehaus.groovy:groovy-all:2.4.3'
    testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'

Przy TDD zaczynamy od napisania testu, który ma się wywalić – załóżmy, że zaczynam tak: Inicjalizuję swoją klasę MealListHelper w sekcji setup. Odpalam z niej metodę getSaturdayDate, która przyjmuje parametr date i zwraca mi date resultDate. Odpalmy na razie taką sytuacje, nie podałam daty, ale chcę żeby wynik nie był nullem nawet w takiej sytuacji.

package com.projects.jezinka.conaobiad
import org.spockframework.util.Assert
import spock.lang.Specification
class UnitTests extends Specification {
def "GetSaturdayDate"() {
setup:
MealListHelper mealListHelper = new MealListHelper()
when:
Date resultDate = mealListHelper.getSaturdayDate()
then:
Assert.notNull(resultDate)
}
}

Nie mam jeszcze metody w getSaturdayDate(date), więc nie odpalę. Napiszmy ją. Wiemy tylko jak się nazywa, co przyjmuje i co ma zwracać. Zacznijmy od zwrócenia nulla.

private Date getSaturdayDate(Date date) {
return null;
}

view raw
MealListHelper.java
hosted with ❤ by GitHub

Test się odpalił i wywalił. O to chodziło 😀

W tym momencie zwróćmy coś:

private Date getSaturdayDate(Date date) {
return new Date();
}

Odpalam – mamy zielone światło i pierwszy test. Czas na kolejny test, który się wywali – przekażmy sobotnią datę i oczekujmy, że zwróci ją bez zmian. Wykorzystajmy tu sekcję where i parametryzację zmiennych:

def "GetSaturdayDate"() {
setup:
MealListHelper mealListHelper = new MealListHelper()
when:
Date resultDate = mealListHelper.getSaturdayDate(date)
then:
Assert.notNull(resultDate)
resultDate == expectedDate
where:
date | expectedDate
new Date(117, 2, 4) | new Date(117, 2, 4)
}

Zrzut ekranu z 2017-03-06 14-44-46.png

Test się wywalił, czyli czas na kod. Nie powinno się już korzystać z metod na klasie date, więc żeby sprawdzić, czy podana data jest sobotnią datą stworzę klasę Calendar, której powinno się używać. Przypadek dla “nie-soboty” na razie odłożę.

private Date getSaturdayDate(Date date) {
Calendar calendarInstance = Calendar.getInstance();
calendarInstance.setTime(date);
if (calendarInstance.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
return date;
}
return new Date();
}

Przeszło – idziemy dalej 😉 Pojawia nam się potencjalny problem związany z użyciem Calendar. W linijce nr 3 ustawiamy mu wartość na podstawie parametru przekazanego przez użytkownika – pobawmy się w prawdziwego testera i dorzućmy jeden osobny test:

def "GetSaturdayDate no exception thrown for null parameter"() {
setup:
MealListHelper mealListHelper = new MealListHelper()
when:
mealListHelper.getSaturdayDate()
then:
noExceptionThrown()
}

Odpalamy i widzimy:

no_exception_throw.png

Ustalmy, że jeśli użytkownik nie podał parametru, to bierzemy dzisiejszą datę, zapiszmy to w dokumentacji i napiszmy kawałek kodu, który poprawi naszą sytuację.

private Date getSaturdayDate(Date date) {
if(date == null){
date = new Date();
}
Calendar calendarInstance = Calendar.getInstance();
calendarInstance.setTime(date);
if (calendarInstance.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
return date;
}
return new Date();
}

Powzdychajmy przez chwilę nad tym, że Java nie obsługuje default parameters.

Wracamy do kodu, został nam test pt. daję dowolną datę chcę soboty. Otwieramy kalendarz, patrzymy i wybieramy jako datę wejściową 13 marca, jako oczekiwaną 11 marca.

def "GetSaturdayDate"() {
setup:
MealListHelper mealListHelper = new MealListHelper()
when:
Date resultDate = mealListHelper.getSaturdayDate(date)
then:
Assert.notNull(resultDate)
resultDate == expectedDate
where:
date | expectedDate
new Date(117, 2, 4) | new Date(117, 2, 4)
new Date(117, 2, 13) | new Date(117, 2, 11)
}

Ładne czerwone, bo zwraca nam datę dzisiejszą. Pomyślmy nad tym – dni numerowane są od 1, gdzie 1 jest niedzielą, 7 sobotą. Czyli wystarczy odjąć od daty tyle dni ile zwróci nam

Calendar.DAY_OF_WEEK

Sprawdźmy to:

private Date getSaturdayDate(Date date) {
if(date == null){
date = new Date();
}
Calendar calendarInstance = Calendar.getInstance();
calendarInstance.setTime(date);
if (calendarInstance.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
return date;
}
calendarInstance.add(Calendar.DAY_OF_WEEK, calendarInstance.get(Calendar.DAY_OF_WEEK));
return calendarInstance.getTime();
}

Działa – ale dla ustalenia, czy dla każdego dnia tygodnia działa, co mi szkodzi dorzucić testy dla całego tego tygodnia. Tak to widzę w Spocku:

def "GetSaturdayDate"() {
setup:
MealListHelper mealListHelper = new MealListHelper()
when:
Date resultDate = mealListHelper.getSaturdayDate(date)
then:
Assert.notNull(resultDate)
resultDate == expectedDate
where:
date | expectedDate
new Date(117, 2, 4) | new Date(117, 2, 4)
new Date(117, 2, 13) | new Date(117, 2, 11)
new Date(117, 2, 12) | new Date(117, 2, 11)
new Date(117, 2, 14) | new Date(117, 2, 11)
new Date(117, 2, 15) | new Date(117, 2, 11)
new Date(117, 2, 16) | new Date(117, 2, 11)
new Date(117, 2, 17) | new Date(117, 2, 11)
}

Dorzucę jeszcze test dla soboty w innym miesiącu i innym roku:

def "GetSaturdayDate"() {
setup:
MealListHelper mealListHelper = new MealListHelper()
when:
Date resultDate = mealListHelper.getSaturdayDate(date)
then:
Assert.notNull(resultDate)
resultDate == expectedDate
where:
date | expectedDate
new Date(117, 2, 4) | new Date(117, 2, 4)
new Date(117, 2, 13) | new Date(117, 2, 11)
new Date(117, 2, 11) | new Date(117, 2, 11)
new Date(117, 2, 12) | new Date(117, 2, 11)
new Date(117, 2, 14) | new Date(117, 2, 11)
new Date(117, 2, 15) | new Date(117, 2, 11)
new Date(117, 2, 16) | new Date(117, 2, 11)
new Date(117, 2, 17) | new Date(117, 2, 11)
new Date(117, 2, 3) | new Date(117, 1, 25)
new Date(117, 0, 1) | new Date(116, 11, 31)
}

pass.png

Działa – działa 😉

Jeszcze tylko zamienimy podwójne pobieranie dnia tygodnia z daty i prace nad tą metodą uznaję za zakończone:

private Date getSaturdayDate(Date date) {
if(date == null){
date = new Date();
}
Calendar calendarInstance = Calendar.getInstance();
calendarInstance.setTime(date);
int dayOfWeek = calendarInstance.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek == Calendar.SATURDAY) {
return date;
}
calendarInstance.add(Calendar.DAY_OF_WEEK, dayOfWeek);
return calendarInstance.getTime();
}

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

%d bloggers like this: