Testing

Hvorfor?

Når prosjekter blir store er det ikke alltid like tydelig hvordan endringer man gjør påvirker resten av systemet.

 

Hvis man har et sett med tester kan man kjøre disse på nytt hver gang man endrer noe, og se at ting fungerer som de skal.

 

Å kjøre gamle tester for å sjekke at man ikke mister funksjonalitet kalles regresjonstesting.

Enhetstesting

Enhetstesting (unit testing) tester en og en enhet i systemet separat.

 

I Java er gjerne en klasse en enhet. I tillegg deler vi opp testene slik at hver test tester en spesifikk ting, sånn at vi vet hva akkurat hva som ikke fungerer hvis en test feiler.

JUnit

 - Et testrammeverk

Altså kode vi importerer inn i vår egen kode for å gjøre det lett å skrive testkode.

Ikke et program for å kjøre tester på en praktisk måte.

JUnit

 - Det mest kjente testrammeverket

Laget av Kent Beck (Extreme Programming, Agile, TDD) og Erich Gamma.

Mange andre testrammeverk er inspirert av / basert på JUnit (xUnit)

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}
import org.junit.Test;

import static org.junit.Assert.*;

public class MathUtilsTest {

    @Test
    public void testAdd() throws Exception {
        assertEquals(4, MathUtils.add(2, 2));
    }

}

"@Test" er en merkelapp (annotation) testverktøy bruker for å finne testene dine

Mens "import" importerer klasser importerer "import static" statiske elementer fra en klasse

Vanlig kode

Testkode

javac -cp .:junit-4.12.jar *.java
java -cp .:junit-4.12.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore MathUtilsTest

Kjøre testene

Man kan laste ned JUnit som jar-filer og kjøre manuelt via terminalen:

Men ingen gjør det på denne måten i praksis.

Eksempel på GitHub under JUnit/NoTools

./gradlew test

Kjøre testene med Gradle

Man kan i stedet bruke byggeverktøy som Maven og Gradle.

.
├── build.gradle
└── src
    ├── main
    │   └── java
    │       └── MathUtils.java
    └── test
        └── java
            └── MathUtilsTest.java

Mappestruktur:

Kommando for testing:

Eksempel på GitHub under JUnit/Gradle

Gradle legger en rapport i build/reports/tests/index.html

 

I eksempelet er det lagt til noen linjer i build.gradle for å få info om feil i terminalen i tillegg.

IntelliJ IDEA

IntelliJ IDEA

IntelliJ IDEA

Alt+Enter for å få opp den første menyen

Testdreven utvikling (TDD)

  1. Skriv en test som feiler
  2. Få testen til å passere
  3. Gå til punkt 1

TDD er en god måte å tvinge deg til å tenke funksjonalitet og grensesnitt før du tenker implementasjon.

Prøv selv

Her er et miniprosjekt med en test som feiler:

https://github.com/evestera/inf1010v16/tree/master/Testing/exercise

git clone https://github.com/evestera/inf1010v16
cd inf1010v16/Testing/exercise
./gradlew test

For å laste ned og kjøre testene første gang kan du copy-paste disse linjene:

Etter du har fikset den feilende testen kan du lage et par nye metoder (bruk TDD). Hva med "factorial" og "fibonacci"?

    @Test
    public void testFactorial() throws Exception {
        assertEquals(1, factorial(1));
        assertEquals(6, factorial(3));
        assertEquals(3628800, factorial(10));
        
        assertEquals(1, factorial(0));
    }

Tilbake til koden

@Rule
public ExpectedException exception = ExpectedException.none();

@Test
public void testFactorialValidation() throws Exception {
    exception.expect(IllegalArgumentException.class);
    exception.expectMessage("negative");
    factorial(-1);
    // No more test-code will be excecuted after this
}

Å teste feilmeldinger

 public class Example {
    File output;

    @Before
    public void createOutputFile() {
          output = new File(...);
    }

    @Test
    public void something() {
          ...
    }

    @After
    public void deleteOutputFile() {
          output.delete();
    }
 }

Before / After

Before/After kjører før/etter hver test.

@Test
public void testAddParent() throws Exception {
    Person bob = new Person("Bob");
    Person bobjr = new Person("Bob Jr.");

    bobjr.addParent(bob);

    assertThat(bobjr.getParents(), hasItem(sameInstance(bob)));
    assertThat(bob.getChildren(), hasItem(sameInstance(bobjr)));
}

assertThat

import static org.hamcrest.CoreMatchers.*;

En ny metode for mer lesbare tester. Egentlig et eksternt prosjekt, men inkludert med JUnit versjon 4.8

Mockito

Mockito eller andre "mocking"-biblioteker brukes veldig ofte sammen med JUnit, for å kunne bruke "falske" objekter i testingen.

 

Dette brukes for å kun teste funksjonaliteten i klassen man tester.

import static org.mockito.Mockito.*;

// mock creation
List mockedList = mock(List.class);

// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();

// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();

Integrasjonstesting

Når man tester flere deler av systemet i sammenheng i stedet for å bruke mocking kaller man det vanligvis integrasjonstester i stedet for enhetstester.

 

(Integrasjonstester kan også skrives i JUnit)

Testing

By Erik Vesteraas

Testing

  • 1,586