Poster taggade med ‘tdd’

När ett test skrivs för en klass som har beroenden till en datakälla, en extern service eller bara en annan klass är mockning ofta väldigt användbart. Ibland kan detta leda till att produktionskod anpassas för att det ska gå att skriva dessa tester. Nedan är exempel på ett test som testar en service som använder en entity manager.

package se.cygni.blog;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import javax.persistence.EntityManager;

import org.junit.Before;
import org.junit.Test;

public class BlogServiceTest {

    private BlogService service;
    private EntityManager entityManager;

    @Before
    public void setup() {
        entityManager = mock(EntityManager.class);
        service = new BlogService(entityManager);
    }

    @Test
    public void addEntry() {
        Entry entry = new Entry("title", "text");

        service.addEntry(entry);

        verify(entityManager).persist(entry);
    }

}

Testet i exemplet är relativt enkelt och verifierar endast att persist anropas i addEntry och ger oss följande kod för ett Spring-baserat projekt:

package se.cygni.blog;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BlogService {

    @PersistenceContext
    private EntityManager entityManager;

    public BlogService() {
    }

    protected BlogService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Transactional
    public void addEntry(Entry entry) {
        entityManager.persist(entry);
    }

}

För att kunna använda en mockad entityManager har vi lagt till en konstruktor som tar en EntityManager som BlogService sedan kan använda. Använder vi dessutom ”field injection” är vi tvugna att lägga till en no-args-konstruktor. Alternativt hade vi kunnat lägga till en setter istället.

    protected void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

Det är väl antagligen inte så illa att behöva lägga till konstruktorer alternativt setters även om det blir värre när en klass har flera beroenden. Däremot känns det ju lite onödigt när både Spring och Java EE numera stödjer field injection. Det går ju att köra enhetstesterna med till exempel Spring och låta Spring injicera alla beroenden. Detta anses dock inte helt lämpligt för enhetstester utan lämpar sig bättre för integrationstester. Istället kan Mockito ta hand om injicering, genom att köra testerna med MockitoJUnitRunner och använda annotationerna @Mock och @InjectMocks. Mockito fungerar då i princip som en enkel IOC-container och några extra konstruktorer eller setters behövs inte.

package se.cygni.blog;

import static org.mockito.Mockito.verify;

import javax.persistence.EntityManager;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class BlogServiceTest {

    @Mock
    private EntityManager entityManager;

    @InjectMocks
    private BlogService service = new BlogService();

    @Test
    public void addEntry() {
        Entry entry = new Entry("title", "text");

        service.addEntry(entry);

        verify(entityManager).persist(entry);
    }
}

Efter att du har läst Martins artikel om Scala och SBT så kanske du känner dig lite sugen att börja koda i Scala, men inte riktigt vet var du skall börja. Jonas Bonér var hos Cygni och talade för ett tag sedan och nämnde då att det kan vara en bra början att skriva sina testfall i Scala för att komma igång och lära sig språket. Scala erbjuder dessutom väldigt trevliga testramverk, som kan underlätta din vardag. Jag kommer förutsätta att du idag har ett mavenprojekt med javakod och eventuella befintliga tester i java.

Första steget är att lyfta in Scalas API, JUnit 4 (om du inte redan kör det) och ett testramverk. Jag tänkte använda ScalaTest. För att göra det så lägger vi till följande beroende i pom.xml

<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <version>2.9.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.scalatest</groupId>
    <artifactId>scalatest_2.9.1</artifactId>
    <version>1.6.1</version>
    <scope>test</scope>
</dependency>

Sedan behöver vi säga till Maven att också kompilera och köra testfall som ligger i biblioteket src/test/scala, så då lägger vi till tre plugins.

maven-scala-plugin ansvarar för att kompilera scalakoden till bytekod.

<plugin>
    <groupId>org.scala-tools</groupId>
    <artifactId>maven-scala-plugin</artifactId>
    <version>2.15.0</version>
    <executions>
        <execution>
            <goals>
                <goal>testCompile</goal>
            </goals>
            <configuration>
                <args>
                    <arg>-make:transitivenocp</arg>
                    <arg>-dependencyfile</arg>
                    <arg>${project.build.directory}/.scala_dependencies</arg>
                </args>
            </configuration>
        </execution>
    </executions>
</plugin>

maven-surefire-plugin är den som kör testerna – om du redan har tester i projektet så har du redan lagt till den här pluginen och behöver då bara utöka den att också inkludera filer som heter **/*Spec.*.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <useFile>false</useFile>
        <disableXmlReport>true</disableXmlReport>
        <includes>
            <include>**/*Spec.*</include>
            <!-- För att hantera existerande tester -->
            <include>**/*Test.*</include>
        </includes>
    </configuration>
</plugin>

build-helper-maven-plugin lägger till källkodsmappar till kompilering, eftersom Maven från början bara hanterar en källkodsmapp för implementationskod (src/main/java som standard) och en för test (src/test/java som standard).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>generate-test-sources</id>
            <phase>generate-test-sources</phase>
            <goals>
                <goal>add-test-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>src/test/scala</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

Ok, nu behöver vi bara skapa mappen src/test/scala och sedan kan du börja koda!

Om du vill kan du stanna här. Nu kan du skriva testklasser i Scala, annotera metoderna med JUnits @Test och köra på som vanligt. Det du vinner är att du får tillgång till Scalas syntax och konstruktioner och att du kan lära dig i en bekant miljö.

Det andra alternativet är att du också börjar titta på vad Scalas testramverk kan erbjuda. I grunden är det inte så stor skillnad, men rent syntatiskt och kanske också tankemässigt så är det lite annorlunda. ScalaTest har stöd för BDD – Behaviour Driven Development. Tanken är att varje test är en beskrivning av en funktion/ett beteende som en roll skall uppfylla. Rollen tar sedan formen av en eller möjligtvis flera klasser. Tack vare att Scalas syntax går att det skriva ganska koncisa specifikationer, nästan som rena texttester för att beskriva beteendet som förväntas.

Vi börjar med att skriva en första specifikation, som vår applikation skall uppfylla.

@RunWith(classOf[JUnitRunner])
class MyApplicationSpec extends FlatSpec with MustMatchers {

  "My application" must "convert a string to hex values" in {
  }

}

Det här är ett komplett test, även om det inte gör något just nu – kroppen på specifikationen är tom. Om jag vill kan jag lämna det tomt och fylla på senare, eller så gör jag det lite tydligare att jag inte är klar med testet. Då skriver vi om det såhär

@RunWith(classOf[JUnitRunner])
class MyApplicationSpec extends FlatSpec with MustMatchers {

  "My application" must "convert a string to hex values" in (pending)

}

Väldigt tydligt och förklarande, eller hur? Ponera att jag skapar en klass som kan konvertera en sträng till hexadecimala värden (wow!). Då kan testkoden se ut som följande

@RunWith(classOf[JUnitRunner])
class MyApplicationSpec extends FlatSpec with MustMatchers {

  "My application" must "convert a string to hex values" in {
    val app = new MyApplication()
    app.convert("The donkey makes a left turn. I observe.") must equal ("54686520646f6e6b6579206d616b65732061206c656674207475726e2e2049206f6273657276652e")
  }

}

Klassen MyApplication är fortfarande skriven i Java, men jag testar den från Scala. Nyckelordet ”must” kommer från en trait i ScalaTest som heter MustMatchers. Det går också att använda ShouldMatchers, enda skillnaden är terminologin – must eller should, vilket du tycker om bäst.

Genom att fylla på med test av det här slaget så får du en ganska tydligt dokumentation vad du faktiskt förväntar dig av dina roller/klasser. Då slipper du en massa kommentarer i koden och/eller testmetoder som döpts ”testShouldConvertStringToHexValue” eller liknande. Enkelt och snyggt!

Vill du använda mockobjekt i din scalakod så kan du antingen köra på någon av de vanliga javabiblioteken (JMock, EasyMock, Mockito), eller så kan du prova Borachio som är skrivet i Scala.

På gårdagens konsultmöte berättade Mårten Gustavsson från hitta.se om deras arkitektur vilket var otroligt intressant.

Han pushar bland annat för följande:

I veckan föreläste Tommy om Java på nya kunden WSP. Han följde sedan upp det genom att tillsammans med Emil Stridfeldt snacka om Java Power Tools, TDD och designskuld på Com Hem. Och imorgon ska Nathalie föreläsa om Python på Geek Girl Meetup!