Software Engineering 3. JUnit und ANT

Transcription

Software Engineering 3. JUnit und ANT
Software Engineering
3. JUnit und ANT
Franz-Josef Elmer, Universität Basel, HS 2012
Software Engineering: 3. JUnit und ANT
2
Unit Testing
–
–
–
–
Unit Test: Automatischer Test welcher eine Einheit (z.B. Modul,
Klasse, Komponente etc.) testet.
Unit Testing: Erstellen, Verwalten und Ausführen aller Unit Tests.
Unit Tests werden gleichzeitig mit dem produktiven Code
geschrieben.
Motto: „Code a little, test a little.“
Produktiver Code
Test Code
Zeit
–
Gebrochene Unit Tests werden sofort geflickt.
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
3
JUnit 4: Unit Testing Framework für Java
–
–
–
–
–
Unterstützung durch alle gängigen Java IDEs wie z.B. Eclipse.
Ein Test ist eine mit @Test annotierte parameterlose public void
deklarierte Methode einer Testklasse.
Namenskonventionen:
● Testmethode: test<short description> ()
● Testklasse: <Class to be tested>Test
Ein Test Runner führt alle Testmethoden der Testklasse in
unbestimmter Reihenfolge aus. Dabei wird jedesmal eine neue
Instanz der Testklasse erzeugt.
Ein Test ist erfolgreich wenn die Testmethode kein Throwable wirft.
–
Die Klasse Assert hat statische Methoden, die mit assert beginnen.
Sie prüfen einen zu erwarteten Wert mit dem aktuellen Wert und
werfen ein AssertionFailedError falls beide nicht übereinstimmen.
–
Die Methode Assert.fail wirft immer ein AssertionFailedError.
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
4
Beispiel TemperaturConverterTest
/**
* Temperature converter between Fahrenheit and Celcius. Conversion is based on
* the formula
*
* <pre>
* Fahrenheit = 9 * Celcius / 5 + 32
* </pre>
*/
public class TemperatureConverter {
/** Converts the specified temperature from Celsius to Fahrenheit. */
public double convertToFahrenheit(double temperature) {
return 1.8 * temperature + 32;
}
}
/** Converts the specified temperature from Fahrenheit to Celsius. */
public double convertToCelcius(double temperature) {
return (temperature - 32) / 1.8;
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
5
TemperatureConverterTest
import org.junit.Assert;
import org.junit.Test;
public class TemperatureConverterTest {
private static final double TOL = 1e-6;
@Test
public void testConvertToFahrenheit() {
TemperatureConverter converter = new TemperatureConverter();
Assert.assertEquals(32, converter.convertToFahrenheit(0), TOL);
Assert.assertEquals(86, converter.convertToFahrenheit(30), TOL);
}
@Test
public void testConvertToCelcius() {
TemperatureConverter converter = new TemperatureConverter();
Assert.assertEquals(0, converter.convertToCelcius(32), TOL);
Assert.assertEquals(30, converter.convertToCelcius(86), TOL);
}
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
6
Erwartete Exceptions testen
–
Es sollten auch Tests geschrieben werden, die das korrekte
Verhalten auf Verletzung der Vorbedingungen (z.B. keine null als
Methodenargument) überprüfen.
–
Test Code:
●
●
Die zuerwartende Exception Klasse in Testannotation deklarieren.
Beispiel:
@Test(expected = NumberFormatException.class)
public void testParseInvalidInteger() {
Integer.parseInt("blabla");
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
7
Beispiel: Stack
import java.util.ArrayList;
import java.util.List;
public class Stack<E> {
private final List<E> _stack = new ArrayList<E>();
/**
* Pushes the specified element onto the stack.
* @param element Any object of type E. <code>null</code> is allowed.
*/
public void push(E element) {
_stack.add(element);
}
/** Returns <code>true</code> if the stack is empty. */
public boolean isEmpty() {
return _stack.isEmpty();
}
}
/**
* Removes and returns the element on the top of the stack.
* @throws IllegalStateException if the stack is empty.
*/
public E pop() {
if (isEmpty()) throw new IllegalStateException("Can not pop from an empty stack.");
return _stack.remove(_stack.size() - 1);
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
8
StackTest
import org.junit.Assert;
import org.junit.Test;
public class StackTest {
@Test
public void testIsEmpty() {
Stack<String> stack = new Stack<String>();
Assert.assertTrue(stack.isEmpty());
stack.push("hello");
Assert.assertFalse(stack.isEmpty());
stack.pop();
Assert.assertTrue(stack.isEmpty());
}
@Test
public void testPushPop() {
Stack<String> stack = new Stack<String>();
stack.push("hello");
stack.push(null);
stack.push("world");
Assert.assertEquals("world", stack.pop());
Assert.assertNull(stack.pop());
Assert.assertEquals("hello", stack.pop());
}
@Test(expected = IllegalStateException.class)
public void testPopFromEmptyStack() {
new Stack<String>().pop();
}
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
9
Unit Testing Konventionen für Java
–
Die Testklasse ist im selben Paket wie die
zu testende Klasse:
●
–
Vorteil: Testklasse hat Zugriff auf packageprotected Attribute und Methoden.
Produktiver Code und Testcode sind in
verschiedenen Verzeichnissen.
●
Grund: Beim Build wird in der Regel nur der
produktive Code benötigt.
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
10
Vorurteile
–
Tests schreiben ist minderwertiges Programmieren, dass kann
man ruhig den Testern oder Junior-Programmierern überlassen.
●
●
–
Tests schreiben ist eine langweilige und stupide Tätigkeit.
●
–
Unit Tests schreiben ist so anspruchsvoll wie produktiven Code
schreiben.
Auch Testcode sollte qualitative guter Code sein. D.h. insbesondere
seine Wartbarkeit sollte hoch sein.
Unit Tests programmieren ist genau so kreative und macht genauso viel
Spass wie produktiven Code schreiben.
Unit Tests sind Zeitverschwendung.
●
●
Unit Tests bilden ein Sicherheitsnetz, welches hilft älteren Code vor
Fehlern zu schützen, die unbeabsichtigt durch neuen Code entstehen.
Unit Tests verbessern das Design des produktiven Codes.
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
11
Die Kunst des Unit Testing I
–
Falls ein Test fehl schlägt, sollte zur Fehlersuche so viel
Informationen gegeben werden, wie möglich.
●
–
Unit Testing hat Einfluss auf das Design.
●
–
Bespiel: Statt Assert.assertEquals(expectedList, actualList) besser
Assert.assertEquals(expectedList.toString(), actualList.toString()).
Der zu testende Code muss testbar sein, d.h. es ist möglich
automatische Tests zu schreiben.
Wenn ein Bug gefunden wurde:
1.Finde die Ursache.
2.Schreibe einen Unit Test, der wegen des Bugs scheitert.
3.Fixe den Bug bis der Unit Test nicht mehr fehlschlägt.
–
Unit Tests sind Test Cases und sollten deshalb so leicht lesbar
sein wie manuelle Test Cases.
●
●
●
●
Keine Verzweigungen in der Test Methode.
Klare Trennung von Testdaten und Testcode.
Komplexere Überprüfungen in eigene assert Methoden auslagern.
Beispiel: CommandLineTest
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
12
Beispiel CommandLineTest
import java.util.*;
public class CommandLine {
private final Set<String> _options;
private final List<String> _arguments;
public CommandLine(String[] args) {
Set<String> options = new HashSet<String>();
List<String> arguments = new ArrayList<String>();
for (String arg : args) {
if (arg.startsWith("-")) {
options.add(arg.substring(1));
} else {
arguments.add(arg);
}
}
_options = Collections.unmodifiableSet(options);
_arguments = Collections.unmodifiableList(arguments);
}
public List<String> getArguments() {
return _arguments;
}
public Set<String> getOptions() {
return _options;
}
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
13
CommandLineTest
import java.util.Arrays;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
public class CommandLineTest {
@Test
public void testWithoutOptions() {
assertNoOptions("hello", "world");
}
@Test
public void testWithOptions() {
assertOptionsAndArguments(Arrays.asList("b", "c"), Arrays.asList("hi"), "-b", "hi", "-c" );
}
private void assertNoOptions(String... args) {
assertOptionsAndArguments(Arrays.<String>asList(), Arrays.asList(args), args);
}
private void assertOptionsAndArguments(List<String> expectedOptions,
List<String> expectedArguments, String... args) {
CommandLine commandLine = new CommandLine(args);
Assert.assertEquals(expectedOptions.toString(), commandLine.getOptions().toString());
Assert.assertEquals(expectedArguments.toString(), commandLine.getArguments().toString());
}
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
14
Die Kunst des Unit Testing II
–
–
Unit Tests müssen reproduzierbar sein. Dazu braucht es eine
wohldefinierte Testumgebung (Testfixture).
Ein Unit Test sollte den Zustand seiner Umgebung vor dem Test
wieder herstellen.
●
●
●
Vermeidet Seiteneffekte.
Tests sind reproduzierbar unabhängig der Reihenfolge ihrer
Ausführung.
Problem: Statische Attribute von Klassen, die ihren Zustand ändern.
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
15
JUnit 4: Die Annotationen @Before und @After
–
Parameterlose public void Methoden einer Testklasse werden
vor/nach jeder Ausführung einer Testmethode ausgeführt, falls sie
mit @Before bzw. @After annotiert wurden.
Traditionelle Name dieser Methoden sind: setUp() bzw. tearDown().
–
Zweck dieser Annotationen:
–
●
●
Bereitstellung bzw. Freigabe von externen Resourcen.
– Z.B.: Temporäre Dateien, Datenbankverbindungen.
Erzeugung bzw. Entfernung von Testfixtures.
– Z.B.: setUp() spielt Testdaten in eine Datenbank ein und
tearDown() löscht diese wieder.
– Beispiel: LineCounterTest
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
16
Beispiel LineCounterTest
–
Die Klasse LineCounter zählt die Zeilen einer Textdatei:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class LineCounter {
public int countNumberOfLines(File file) throws IOException {
FileReader reader = null;
try {
reader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(reader);
int count = 0;
while (bufferedReader.readLine() != null) {
count++;
}
return count;
} finally {
if (reader != null) {
reader.close();
}
}
}
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
17
LineCounterTest
–
Die Testklasse muss eine Beispieldatei erzeugen und wieder
wegräumen:
import java.io.*;
import org.junit.*;
public class LineCounterTest {
private static final File TEMP_FILE = new File("temp.txt");
@Before
public void setUp() throws Exception {
FileWriter writer = null;
try {
writer = new FileWriter(TEMP_FILE);
writer.write("Hello\nworld");
} finally {
writer.close();
}
}
@After
public void tearDown() throws Exception {
TEMP_FILE.delete();
}
@Test
public void test() throws IOException {
Assert.assertEquals(2, new LineCounter().countNumberOfLines(TEMP_FILE));
}
}
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
18
ANT = Another Neat Tool
–
ANT ist Build Tool
●
●
●
●
●
●
–
Kompilieren des Quellcodes
Erzeugung einer lauffähigen Software
Erzeugung einer Distribution (lauffähige Software, Dokumentation,
Installationsanleitung oder -programm, etc.)
Platformunabhängig da in Java geschrieben
Skriptsprache in XML
Unterstützung durch alle gängigen Java IDEs wie z.B. Eclipse
Aufruf:
prompt> ant <target>
oder (wenn das Build File nicht 'build.xml' heisst)
prompt> ant -f <build-file> <target>
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
19
Build Skript: Beispiel
<project name="My Project" default="dist" basedir=".">
<property name="src" location="src"/>
<property name="srcTest" location="srcTest"/>
<property name="classes" location="classes"/>
<property name="dist" location="dist"/>
<path id="junitLib">
<pathelement location="lib/junit.jar"/>
</path>
<target name="init" description="Init" >
<delete dir="${classes}"/>
<mkdir dir="${classes}"/>
<mkdir dir="${dist}"/>
<tstamp/>
</target>
<target name="compile" depends="init"
description="Compile sources">
<javac srcdir="${src}" destdir="${classes}"/>
</target>
<target name="jar" depends="compile"
description="Generate JAR">
<jar jarfile="${dist}/MyProject-${DSTAMP}.jar"
basedir="${classes}"/>
</target>
Universität Basel, HS 2012
<target name="compile-test" depends="compile"
description="Compile test sources.">
<javac srcdir="${srcTest}"
destdir="${classes}"
classpathref="junitLib"/>
</target>
<target name="test" depends="compile-test"
description="Run all tests">
<junit haltonfailure="true" >
<classpath refid="junitLib"/>
<classpath path="${classes}"/>
<formatter type="brief"
usefile="false"/>
<batchtest>
<fileset dir="${srcTest}">
<include name="**/*"/>
</fileset>
</batchtest>
</junit>
</target>
<target name="dist" depends="jar, test"/>
</project>
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
20
ANT: Targets und Properties
–
Targets werden durch das <target> Element definiert
●
Wichtigste Attribute:
–
–
–
●
●
name: Name des Targets. (pflicht)
depends: Kommaseparierte Liste aller Targets, die vor diesem Target auszuführen
sind. (optional)
description: Kurze Beschreibung. (optional)
Kindelemente: Tasks wie z.B. <javac>, <mkdir>
Beispiel:
<target name="compile" depends="init" description="Compile sources">
<javac srcdir="${src}" destdir="${classes}"/>
</target>
–
Properties werden durch <property> Elemente definiert.
●
●
Können nur einmal definiert werden.
Wichtigste Attribute:
–
–
–
●
name: Name der Property. Eine Property wird durch ${<name>} referenziert wobei
<name> der Wert des Attributs name ist.
value: Wert der Property.
location: Wert definiert durch absoluten Pfad einer Datei.
Beispiel:
<property name="dist" location="dist"/>
Universität Basel, HS 2012
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
21
ANT: Abhängigkeiten der Targets
–
Regeln:
●
●
–
Bevor die Tasks eines Targets ausgeführt werden, werden die
abhängigen Targets in der angegebenen Reihenfolge ausgeführt.
Jedes Target wird höchstens einmal ausgeführt.
Beispiel: Bei der Ausführung von Target A wird D vor B ausgeführt
<project name="example" default="A"
basedir=".">
<target name="A" depends="B, D">
<echo message="Target A executed"/>
</target>
<target name="B" depends="C, D">
<echo message="Target B executed"/>
</target>
<target name="C">
<echo message="Target C executed"/>
</target>
<target name="D">
<echo message="Target D executed"/>
</target>
</project>
Universität Basel, HS 2012
prompt> ant
Buildfile: build.xml
C:
D:
B:
A:
[echo] Target C executed
[echo] Target D executed
[echo] Target B executed
[echo] Target A executed
BUILD SUCCESSFUL
Total time: 0 seconds
© Franz-Josef Elmer 2012
Software Engineering: 3. JUnit und ANT
22
Links
JUnit Home Page:
http://www.junit.org/
ANT Home Page
http://ant.apache.org
Universität Basel, HS 2012
© Franz-Josef Elmer 2012