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