TESTEN OBJEKTORIENTIERTER PROGRAMME MIT JUNIT

Ähnliche Dokumente
Das Test-Framework JUnit ETIS SS04

Software-Engineering Software-Management

JUnit. Unit testing unter Java

Swp08-6 Verantwortliche: Yundensuren, Baigalmaa. Testkonzept

Testen mit JUnit. Motivation

Das automatisierte Testen mit JUnit

Unit Tests. Programmiermethodik. Eva Zangerle Universität Innsbruck

Web-Testen mit JUnit und HttpUnit. Kai Schmitz-Hofbauer Lehrstuhl für Software-Technik Ruhr-Universität Bochum

Programmieren I. Übersicht. Vorlesung 12. Handout S. 1. Martin Schultheiß. Hochschule Darmstadt Wintersemester 2010/2011

Java Schulung. Objektorientierte Programmierung in Java Teil IV: Testen mit JUnit. Prof. Dr. Nikolaus Wulff

Unit Tests in der Testgetriebenen Entwicklung

JUnit - Test Driven Development. Bernhard Frey, Thorsten Stratmann, Jackson Takam, Michel Müller 1

Programmiertechnik II

Testen mit JUnit. Martin Wirsing. Ziele. in Zusammenarbeit mit Michael Barth, Philipp Meier und Gefei Zhang

Unit Tests mit Junit 4. Dario Borchers

Markus Wichmann. Testen von Java Code mit. JUnit

Thema: Testen von objektorientierter Software

Einstieg in die Informatik mit Java

Fortgeschrittenes Programmieren mit Java. Test Driven Development

Allgemein: Klassen testbar machen. 5. Mocking. Mocks programmieren. Zusammenspiel von Klassen testen

Probeklausur: Programmierung WS04/05

Übung zur Vorlesung Einführung in Software Engineering

Testen mit JUnit. Martin Wirsing. in Zusammenarbeit mit Matthias Hölzl, Nora Koch 05/03

Programmierpraktikum Java Entdecken. Merkblatt 3

Kurzanleitung JUnit. 1 Grundlagen. 1.1 Begriffsdefinitionen. 1.2 Empfehlungen

Java: Vererbung. Teil 3: super()

Beuth Hochschule Java-Testprogramme mit JUnit schreiben WS11/12

Systematisches Testen

Komponententest. Testen von Software Systemen. Übung 02 SS 2009 Version:

Info: Standard DO-178B. 5. Mocking. Zusammenspiel von Klassen testen. Allgemein: Klassen testbar machen

Unit-Test Theorie und Praxis. Stephan Seefeld, INGTES AG

Programmieren in Java

Unit Tests und Fehlersuche

Javakurs FSS Lehrstuhl Stuckenschmidt. Tag 3 - Objektorientierung

Diplomarbeit. Konzeption und Implementierung einer automatisierten Testumgebung. Thomas Wehrspann. 10. Dezember 2008

Algorithmen und Datenstrukturen

Einführung in die. objektorientierte Programmierung

Probeklausur: Programmierung WS04/05

Java I Vorlesung Vererbung und Sichtbarkeit

1. Einführung und Unit Testing Programmieren / Algorithmen und Datenstrukturen 2

Kapitel 6. Vererbung

Musterlösungen zur Klausur Informatik 3

Kapitel 6. Vererbung

Repetitorium Informatik (Java)

Java-Vorkurs Wintersemester 15/16

Überblick. Einleitung. Unit-Tests. JUnit. HelloWorld Beispiel. Praktische Beispiele. Referenzen

Test zu Grundlagen der Programmierung Leitung: Michael Hahsler. 21. November 2003

Testen von grafischen Benutzeroberflächen

Test-Driven Design: Ein einfaches Beispiel

Kapitel 8. Programmierkurs. Methoden. 8.1 Methoden

JAVA - Methoden

JAVA - Methoden - Rekursion

Übersicht. Informatik 2 Teil 3 Anwendungsbeispiel für objektorientierte Programmierung

4 Testgetriebene Entwicklung mit PHPUnit

Unit Testing, SUnit & You

Testphase. Das Testen

Aufgaben Objektentwurf

SOFTWARE ENGINEERING 3 TESTVORBEREITUNGEN UND UNIT-TEST

Prüfungszeuch im Fach Objektorientierte Programmierung WS 2000

Programmieren I. Martin Schultheiß. Hochschule Darmstadt Wintersemester 2010/2011

Systematisches Testen der Funktionalität von Softwaresystemen. 17. Juni 2015

Die Programmiersprache C Eine Einführung

Programmieren in Java

Grundzüge der Programmierung. Wiederverwendung VERERBUNG

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 14/15. Kapitel 11. Fehler und Ausnahmen 1

Java-Schulung Grundlagen

Programmierprojekt. Anne0e Bieniusa Sommersemester 2014

Praktische Übung 'JUnit-Test'

Komponenten-basierte Entwicklung Teil 6: Einführung in JUnit

1 Polymorphie (Vielgestaltigkeit)

6. Iteration (Schleifenanweisungen)

7. Schnittstellen Grundlagen zu Schnittstellen. 7. Schnittstellen

Automatisierte Regressionstests per Knopfdruck sparen Zeit und Ressourcen sichern die Qualität schonen die Nerven

Kapitel 10b Test-Automatisierung

SEP 114. Design by Contract

Höhere Programmierkonzepte Testklausur

Einführung in die Programmierung 1

Der EMF-generierte Code. 7. November 2012

Variablen manipulieren per JDI

3. Anweisungen und Kontrollstrukturen

Kapitel 6. Vererbung

Java Einführung Abstrakte Klassen und Interfaces

Einstieg in die Informatik mit Java

Java Vererbung. Inhalt

CS1005 Objektorientierte Programmierung Bachelor of Science (Informatik)

5. Tutorium zu Programmieren

Testen und Debugging

Code verifizieren mittels

Programmieren in Java

Programmieren mit Java

Einstieg in die Informatik mit Java

Teil 1: Grundeigenschaften von Rechnern und Software

Einführung in die Programmierung

Transkript:

TESTEN OBJEKTORIENTIERTER PROGRAMME MIT JUNIT Praktikumsaufgabe zur Lehrveranstaltung Softwaretechnologie II (WS 00/01) Professur Softwaretechnologie TU Dresden, Fakultät Informatik DANIEL SCHUSTER 29.01.01

AUFGABENSTELLUNG Erstellen Sie eine frei gewählte Anwendung (möglichst dir Lösung numerischer Probleme) in Java und verwenden Sie bei der Erstellung systematisch das Test-Framework JUnit. Wählen Sie dabei geeignete Testfälle und bewerten Sie das Framework. PROBLEMATIK DES TESTENS Software weist wie jedes industriell entwickelte Produkt Fehler auf. Nur ein Teil dieser Fehler wird durch den Compiler bzw. die IDE erkannt. Trotzdem ist der syntaktisch korrekte Quellcode meist noch sehr fehlerhaft. Ein beträchtlicher Teil der Implementationszeit wird für das Debuggen verwendet. Solche Fehler können z.b. bei Copy and Paste von Quellcode entstehen, wie folgendes Java-Fragment zeigt: private String dirname; public String getdirname return dirname; private String cfgname; public String getcfgname return dirname; In diesem Beispiel war die Variable dirname mit der zugehörigen get-methode schon vorhanden, für cfgname wurde einfach dieses Fragment kopiert und dabei das Ändern der return-anweisung vergessen. Frühzeitiges Testen kann helfen, solche Fehler aufzuspüren und ein Gesamtsystem inkrementell aus einwandfrei funktionierenden Komponenten aufzubauen. Man unterscheidet hierbei die Unit-Tests einzelner Klassen oder Packages, die Integrations-Tests für das Zusammenspiel der Komponenten, sowie den abschließenden System-Test. Das Problem fehlerhafter Software lässt sich nur lösen, wenn Test und Entwicklung eine verzahnte Einheit bilden. ( test a little, code a little, test... ) Bei konsequentem Testen des

entwickelten Codes wird der Quellcode für das Testen ebenso umfangreich werden wie das Programm selbst. Da aber jeder Softwareentwickler meist unter Leistungsdruck steht, wird er nicht die nötige Zeit aufbringen, um die Unit-Tests wirklich vollständig durchzuführen, dies resultiert in unstabilerem Code und somit geringerer Produktivität, was den Leistungsdruck weiter erhöht. Um aus diesem Teufelskreis zu entkommen, braucht der Programmierer äußere Beeinflussung, die durch die Hilfe eines Test-Frameworks geleistet werden kann. Das Test- Framework stellt nicht nur Methoden für ein einheitliches Testen bereit, es erzieht auch den Programmierer zum konsequenten Testen. Ein solches Framework für die Programmiersprache Java ist JUnit von Kent Beck und Erich Gamma. Ich habe Version 3.4 vom 2.11.00 verwendet. BENUTZUNG DES FRAMEWORKS Für jede im Programm vorhandene Klasse wird eine Testklasse angelegt. So z.b. für eine Klasse Article die Klasse TestArticle usw. Hier werden Methoden eingefügt, die über assert() oder assertequals() entsprechende Soll-Ist-Vergleiche durchführen. Der eigentliche Test verläuft in 3 Phasen: 1. Initialisierung von Testwerten und variablen über setup() 2. Durchführung des Tests 3. Ressourcenfreigabe mit teardown() Der TestCase bündelt alle Testmethoden, in der TestSuite können die Testfälle zusammengeführt werden. So lässt sich ein Baum von Testfällen aufbauen. Die Methoden im TestCase müssen mit test... beginnen und beinhalten typischerweise assert-anweisungen. Die TestSuites können entweder über eine main()-methode von Hand gestartet werden, oder sie werden aus einer mitgelieferten Umgebung heraus aufgerufen. Hier sind weitere wichtige Methoden des Frameworks aufgelistet: public int counttestcases(); liefert Zahl der Test Cases, die von diesem Objekt erfasst werden.

public void run ( TestResult result ); führt Test aus und sammelt Ergebnisse in result result wird framework-intern ausgewertet assert([string msg,] boolean condition); assertequals([string msg,] double expected, double actual,double delta); assertequals([string msg,] long expected, long actual); assertequals([string msg,] Object expected, Object actual); assert[not]null([string msg,] Object object); assertsame([string msg,] Object expected, Object actual); fail([string msg]); Die folgende Übersicht zeigt die Verwendung von verschiedenen Entwurfsmustern im Framework. Dabei ist vor allem das Composite Muster bedeutsam, es ermöglicht, eine baumartige Struktur von Testklassen aufzubauen, die der Organisationsstruktur der zu testenden Anwendung entsprechen.

TESTANWENDUNG: POLYNOMOPERATIONEN Um die Arbeit mit JUnit zu demonstrieren, habe ich als Bespielanwendung ein Mini- Framework zur Polynomverarbeitung implementiert. Zuerst brauche ich die Basisklasse Polynom und definiere mir einfache Konstruktoren: public class Polynom public static final int MAX_POWER=10; private double[] fpoly; public Polynom() fpoly=new double[max_power+1]; clear(); public Polynom(double InitialValue) this(); fpoly[0]=initialvalue; public Polynom(double[] InitialArray) this(); System.arraycopy(InitialArray,0,fPoly,0, fpoly.length>initialarray.length?initialarray.length:fpoly.length); public void clear() if(fpoly!=null) for(int i=0;i<fpoly.length;i++) fpoly[i]=0.0; Zusätzlich benötigt man noch eine einfache get-methode und eine equals-methode, dies soll als minimalster Klassenprototyp genügen: public boolean equals(polynom p) boolean result=true; for(int i=0;i<fpoly.length;i++) if(fpoly[i]!=p.get(i))result=false; return result; public double get(int index) if((index>=fpoly.length) (index<0)) return -1.0; return fpoly[index]; Nach der Devise code a little, test a little ist als nächstes gleich eine entsprechende Testklasse zu schreiben. Normalerweise würde man hier für jede Klasse eine Testklasse

schreiben und dann in Suites zusammenfügen, um in großen Projekten den Überblick zu behalten. Für dieses kleine Beispiel reicht es aus, eine Testklasse zu erstellen, die Tests für alle Klassen durchführt. package junit.swt; import junit.framework.*; public class JunitTest extends TestCase private double[] p0=0.0,0.0,0.0,0.0; private double[] p2=2.0,0.0,0.0,0.0; private double[] px=0.0,1.0,0.0,0.0; private double[] psquare=0.0,0.0,1.0,0.0; private Polynom P0; private Polynom P1; private Polynom PX; public JunitTest(String name) super(name); public static void main(string args[]) junit.textui.testrunner.run(junittest.class); protected void setup() double[] Xar=0.0,1.0; P0=new Polynom(); P1=new Polynom(1.0); PX=new Polynom(Xar); public void testequals() assertequals(p0,p0); public void testconstructors() assertequals(p0,new Polynom()); assertequals(p2,new Polynom(2.0)); Dieser TestCase kann nun direkt über die Main- Methode ausgeführt werden und führt die angegebenen Tests aus, die alle mit test.. beginnen. Komfortabler ist allerdings junit.swingui.testrunner, eine swing-basierte grafische Oberfläche.

Der TestRunner fängt alle Exceptions und Errors ab und prüft zusätzlich noch die definierten assertions. Die oben angezeigten Fehler liegen in der Test-Klasse selbst, hier liegt ein wesentlicher Stolperstein des Frameworks, da man aus einer fehlgeschlagenen assertion nicht immer schlussfolgern kann, ob nun die Testklasse oder die getestete Klasse fehlerhaft ist. Die bereinigte Testklasse, für die dann auch der TestRunner grün gibt, sieht so aus: package junit.swt; import junit.framework.*; public class JunitTest extends TestCase private double[] p0=0.0; private double[] p2=2.0; private double[] px=0.0,1.0; private double[] psquare=0.0,0.0,1.0; private Polynom P0; private Polynom P1; private Polynom PX; public JunitTest(String name) super(name); public static void main(string args[]) junit.textui.testrunner.run(junittest.class); protected void setup() double[] Xar=0.0,1.0; P0=new Polynom(); P1=new Polynom(1.0); PX=new Polynom(Xar); public void testequals() assert(p0.equals(new Polynom(p0))); assert(px.equals(new Polynom(pX))); public void testconstructors() assert((new Polynom(p0)).equals(new Polynom())); assert((new Polynom(p2)).equals(new Polynom(2.0))); Als nächstes soll eine Addition und Subtraktion hinzugefügt werden, bei solchen einfachen Methoden ist die entsprechende Testmethode aufwendiger zu schreiben als die Methode selbst. Der Nutzen erschließt sich hier erst mit zunehmender Komplexität des Systems.

Polynom.java: public void add(polynom p) for(int i=0;i<=max_power;i++) fpoly[i]+=p.get(i); public void neg() for(int i=0;i<=max_power;i++) fpoly[i]=-fpoly[i]; public void sub(polynom p) for(int i=0;i<=max_power;i++) fpoly[i]-=p.get(i); JUnitTest.java public void testneg() Polynom p=new Polynom(-1.0); p.neg(); assert(p.equals(p1)); public void testadd() Polynom p=new Polynom(new double[] 3.0,2.0); // 2x+3 p.add(p1); // +1 p.add(px); // +x assert(p.equals(new Polynom(new double[] 4.0,3.0))); // 3x+4 public void testsub() Polynom p=new Polynom(new double[] 3.0,2.0); // 2x+3 p.sub(p1); // -1 p.sub(px); // -x assert(p.equals(new Polynom(new double[] 2.0,1.0))); // x+2 Alle Tests zeigen erwartungsgemäß OK. Die neuen Klassen können nach dem Compilieren in der IDE im TestRunner wieder neu geladen werden. Bei älteren Versionen von JUnit musste hier der TestRunner für jeden Test neu gestartet werden. Nun soll der Klasse eine echte Funktionalität hinzugefügt werden, die Polynomdivision und Multiplikation, gleichzeitig wird die tostring()-methode überschrieben. public void mul(polynom p) Polynom result=new Polynom(); int i,l; for(i=0;i<=max_power;i++) for(l=0;i<=max_power;l++) // <- Fehler!!!! Endlosschleife // richtig: for(l=0;l<=max_power;l++)

result.set(i+l,result.get(i+l)+get(i)*p.get(l)); for(i=0;i<=max_power;i++) set(i,result.get(i)); public void testmul() Polynom p=new Polynom(new double[] 3.0,4.0,2.0); // 2x^2+4x+3 p.mul(new Polynom(new double[] 4.0,3.0)); // *3x+4 assert(p.equals(new Polynom(new double[] 12.0,25.0,20.0,6.0))); // 6x^3+20x^2+25x+12 Prompt zeigt sich auch hier wieder der Wert des sofortigen Tests. In die Methode mul hat sich ein typischer Copy-and-Paste Fehler eingeschlichen, der zu einer Endlosschleife führt. Dies wird zwar durch JUnit nicht explizit angezeigt, da aber der neue Test der letzte in der Reihe ist und der TestRunner nicht zum Ende kommt, kann man darauf schließen. Leider funktioniert Stop Test nur zwischen zwei Tests. Es folgt nun die Polynomdivision: public Polynom div(polynom p) int i; if(p.getmaxpower()>getmaxpower()) return new Polynom(); Polynom result=new Polynom(); Polynom base=new Polynom(); for(i=0;i<=max_power;i++) base.set(i,get(i)); Polynom modulo=new Polynom(); int maxa=getmaxpower(); int maxb=p.getmaxpower(); for(i=maxa;i>=0;i--) result.set(i-maxb,base.get(i)/p.get(maxb)); maxa=base.getmaxpower(); if(maxa<maxb) break; modulo.clear(); for(int l=0;l<=maxb;l++) modulo.set(maxa-maxb+l,result.get(maxa-maxb)*p.get(l)); // zuerst Fehler: modulo.set(maxa-maxb+l,result.get(maxa-maxb)); base.sub(modulo); for(i=0;i<=max_power;i++) set(i,result.get(i)); return base; In der anfänglichen Version dieser Methode war ein Fehler (s.o.). Allerdings deckte die erste Version der zugehörigen Testklasse nicht diesen Fehler ab, das Ergebnis der getesteten Division war richtig. JUnit ist kein intelligentes Testprogramm. Es ist lediglich ein Test-

Framework und die Schwierigkeit besteht darin, intelligente Tests zu schreiben. Es hilft nur beim Aufdecken eines Fehlers, nicht bei dessen Beseitigung. So konnte ich den Fehler oben erst durch Einfügen von print()-anweisungen beheben, JUnit ersetzt kein Debugging. Es fehlt eine Log-Funktion, die man innerhalb der Methoden der Anwendung aufrufen kann. Erst nach dem debuggen macht dann eine solche Testmethode Sinn: public void testdiv() Polynom p=new Polynom(new double[] 3.0,5.0,6.0); // 6x^2+5x+3 Polynom p2=p.getclone(); p2.div(p1); assert(p2.equals(p)); // Division durch 1 p2.div(p2); p2.mul(p2); // Division durch 2, anschließend *2 assert(p2.equals(p)); Polynom Prest=p.div(new Polynom(new double[] 4.0,3.0)); // *3x+4 assert(p.equals(new Polynom(new double[] -1.0,2.0))); assert(prest.equals(new Polynom(new double[] 7.0))); // Rest 7 ZUSAMMENFASSUNG / BEWERTUNG Zusammenfassend lässt sich sagen, dass JUnit ein durchaus nützliches Tool für das inkrementelle Entwickeln von Programmen ist. Es erzieht gewissermaßen den Programmierer zur fortlaufenden Qualitätskontrolle seiner Software. Bei konsequenter Anwendung ist es möglich, Programm und Test voneinander zu trennen und die Tests zu konservieren. Sonst werden die Tests im Quelltext der Anwendung durchgeführt und verschwinden beim Release. Kommt es später noch zu Änderungen, kann auf die alten Tests nicht mehr zurückgegriffen werden, was mit JUnit ohne weiteres möglich ist. JUnit bewährt sich vor allem bei stark strukturierten Objektbäumen, es kann eine analoge Objekthierarchie aufgebaut werden. Schwächen liegen vor allem in der Fehleranfälligkeit der Testmethoden selbst. Die Soll-Ist-Kontrolle erfolgt nicht mehr per Hand (Ausgabe von Strings, etc.) sondern muss für die assert()-anweisungen in boolsche Ausdrücke umgewandelt werden. Dabei kann es vorkommen, dass völlig korrekte Ergebnisse einen Assertion-Fehler verursachen. Es erfordert einige Übung, sich in diese Art des Testens hineinzudenken. Ein weiterer Schwachpunkt ist die Beschränkung auf den Test von ganzen Methoden, ein Tool für die Fehlersuche in Methoden wäre hilfreich.