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 (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.
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.
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
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
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.
Alt+Enter for å få opp den første menyen
TDD er en god måte å tvinge deg til å tenke funksjonalitet og grensesnitt før du tenker implementasjon.
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));
}
@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
}
public class Example {
File output;
@Before
public void createOutputFile() {
output = new File(...);
}
@Test
public void something() {
...
}
@After
public void deleteOutputFile() {
output.delete();
}
}
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)));
}
import static org.hamcrest.CoreMatchers.*;
En ny metode for mer lesbare tester. Egentlig et eksternt prosjekt, men inkludert med JUnit versjon 4.8
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();
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)