DIPLOMARBEIT Entwicklung einer Software für die

Transcription

DIPLOMARBEIT Entwicklung einer Software für die
DIPLOMARBEIT
Entwicklung einer Software
für die automatische Generierung
und wiederholbare Durchführung
von Funktionstests mit .NET
Nico Franze
diplom@nfranze.de
Technische Fachhochschule Berlin
Sommersemester 08
Betreuer:
Prof. Dipl.-Ing. Detlef Gramm
Gutachter:
Prof. Dr. Heike Ripphausen-Lipa
Kurzzusammenfassung
Qualität der Software - was bedeutet das überhaupt? Und wie steigert man die Qualität
einer Software? Wo liegen eigentlich die Ursachen der Fehler und Abstürze in Software?
Eines der wichtigsten Bestandteile für die Erhöhung der Qualität sind Softwaretests. Das
bedeutet aber nicht das Starten der Software. Gute Tests beinhalten wesentlich mehr.
In dieser Arbeit werden zu Beginn Softwarefehler vorgestellt und mit berühmt „berüchtigten“
1
Beispielen belegt. Weiterhin werden die Ursachen der Fehler analysiert.
Nach der Vorstellung und Analyse von Fehlern werden die Vor- und Nachteile von Softwaretestsystemen analysiert. Zielstellung der Arbeit ist es, ein eigenes Testsystem zu
entwickeln, welches auf der Testmethode Fuzzing beruht. Dieses Testsystem, welches
AutoTest.Net genannt wird, ist in der Lage, mithilfe von Reflection eine .NET-Assembly
zu analysieren und alle enthaltenen Methoden auszuführen. Jede einzelne Methode wird
separat getestet (Funktionstests). Die dafür nötigen Parameter werden aus Äquivalenzklassen generiert. Am Ende eines jeden Tests wird ein Bericht ausgegeben, der die Ergebnisse des Tests präsentiert und detailliert aufschlüsselt, welche Methoden mit welcher
Exception abstürzte.
Für die Entwicklung dieses Testsystems wird Microsoft .NET 3.5 und Microsoft Visual
Studio 2008 verwendet. Das Testsystem ist in der Lage, jegliche Software zu testen, die
ebenfalls mit .NET entwickelt wurde. Es konnte gezeigt werden, dass durch diese Testmethode Fehler gefunden werden können, die bisher mit anderen Testmethoden nur sehr
schwer gefunden wurden. Somit ist dieses Testsystem im allgemeinen Testzyklus eine
optimale Ergänzung.
1 berühmte
Negativ-Beispiele
ii
[K]
Abstract
Quality in software: what is that really? How can you improve your software? Where
are the causes of errors and crashes in software? One of the most important elements for
improvement are tests, specifically software tests, but that does not mean simply to start
your software. Good tests contain considerably more.
At first a few software bugs are presented and documented with notorious samples.
Also the causation of these bugs are analysed. After that the advantages and disadvantages of different test systems are analysed. The main goal of this thesis is to develop a
custom test system, which is based on the test method Fuzzing. This test system is called
AutoTest.Net and is able to decode a .NET-Assembly with Reflection. All containing methods will be executed separately (function tests). All needed parameters are generated
from equivalence classes. At the end a report shows in detail which method crashed, with
which error, and which parameters were used for the crash.
This test system will be developed with Microsoft .NET 3.5 and Microsoft Visual Studio 2008. The test system will have the ability to test all .NET software. It should be
shown that this test method can find some errors that cannot be found easily with other
test methods. Therefore, this test system will be the optimal final step of the general test
cycle.
iii
[A]
Inhaltsverzeichnis
Kurzzusammenfassung
ii
Abstract
iii
1
Einleitung
1
1.1
Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.3
Zielstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.3.1
Warum .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.3.2
Was ist Reflection . . . . . . . . . . . . . . . . . . . . . . . . .
3
Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.4
2
Bekannte Fehler und deren Ursache
6
2.1
Die Einführung des Begriffes „Software-Bug“ . . . . . . . . . . . . . . .
6
2.2
Allgemeines zu Softwarefehlern . . . . . . . . . . . . . . . . . . . . . .
7
2.3
Ursachen und Ausmaße von Softwarefehlern . . . . . . . . . . . . . . . 10
2.4
3
2.3.1
Tippfehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.2
Umwandlungsfehler . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.3
Überläufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.4
Unterschiedliche Einheiten . . . . . . . . . . . . . . . . . . . . . 16
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Grundlegendes zu Softwaretests
18
3.1
Was ist Testen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.2
Qualitätsmerkmale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3
Blackbox-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.4
Whitebox-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.4.1
Statische Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.4.2
Strukturelle Analyse . . . . . . . . . . . . . . . . . . . . . . . . 25
3.4.3
Überdeckungstests . . . . . . . . . . . . . . . . . . . . . . . . . 26
iv
3.5
3.6
3.7
4
Testverfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.5.1
Komponententest . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.5.2
Integrationstest . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.5.3
Systemtest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.5.4
Abnahmetest . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.5.5
Regressionstests . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Konkrete Testverfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.6.1
Unit-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.6.2
GUI-Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.6.3
Fuzz-Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.6.4
Datengesteuerte Tests . . . . . . . . . . . . . . . . . . . . . . . . 36
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Vorhandene Testsysteme
4.1
4.2
Test-Frameworks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.1.1
NUnit / JUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.1.2
Fuzzing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Statische Codeanalyse . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.2.1
4.3
4.4
5
6
38
Microsoft FxCop . . . . . . . . . . . . . . . . . . . . . . . . . . 42
GUI-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.3.1
Ranorex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.3.2
TestComplete . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Projekt AutoTest.Net
49
5.1
Soll-Zustand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.2
Optionale Funktionalitäten . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.3
Aufbau der Bedienoberfläche . . . . . . . . . . . . . . . . . . . . . . . . 55
5.4
Arbeitsablauf in AutoTest.Net . . . . . . . . . . . . . . . . . . . . . . . 57
5.5
Lösungskonzeption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.5.1
Äquivalenzklassenverwaltung . . . . . . . . . . . . . . . . . . . 58
5.5.2
Exceptionverwaltung . . . . . . . . . . . . . . . . . . . . . . . . 59
5.5.3
Testdurchführung . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Implementierung des Testsystems
62
6.1
Implementierung der GUI . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.2
Implementierung der Use-Cases . . . . . . . . . . . . . . . . . . . . . . 65
v
6.3
7
8
6.2.1
Implementierung Use-Case „Laden/Analysieren der Assembly“ . 65
6.2.2
Implementierung Use-Case „Tests durchführen“ . . . . . . . . . 66
6.2.3
Implementierung Use-Case „Ausgabe der Testergebnisse“ . . . . 67
6.2.4
Implementierung Use-Case „Exceptions verwalten“ . . . . . . . . 68
6.2.5
Implementierung Use-Case „Äquivalenzklassen verwalten“ . . . 68
Fertigstellung von AutoTest.Net . . . . . . . . . . . . . . . . . . . . . . 70
Sollanalyse - Testen des Testsystems
72
7.1
Testergebnisse mit selbstentwickelter Test-Assembly . . . . . . . . . . . 72
7.2
Testergebnisse mit System-Assembly . . . . . . . . . . . . . . . . . . . 75
7.3
Vergleich mit NUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.4
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Fazit
77
8.1
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.2
Grenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.3
Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
8.4
Schlusswort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Literaturverzeichnis
I
Abbildungsverzeichnis
V
Tabellenverzeichnis
VII
Listings
VIII
Glossar
IX
Index
XII
Anhang
XIII
vi
Einleitung
[1]
1.1
Software ist ein wesentlicher Bestandteil der modernen Gesellschaft geworden. Kaum
eine Anwendung ist heutzutage ohne Software denkbar. Dabei geht Software weit über
das hinaus, was als Textverarbeitungs- oder Mailprogramm auf dem heimischen Computer bekannt ist. Fast jedes elektronische oder technische Gerät, vom Fernseher und
Motivation
1.2
Problemstellung
Kühlschrank bis hin zur elektrischen Zahnbürste, wird heutzutage von Software auf ei-
1.3
nem kleinen Chip gesteuert. Mit Softwarefehlern können Menschen bewusst oder un-
Zielstellung
bewusst praktisch täglich in Kontakt kommen. Einige Fehler haben keine gravierenden
1.4
Folgen oder bleiben unbemerkt, andere wiederum können sehr viel Geld oder sogar Men-
Aufbau
schenleben kosten. Je mehr Verantwortung einer Software oder einem technischen System übertragen wird, desto mehr muss bei der Herstellung auf Fehler geachtet werden.
Funktionierende und vor allem benutzbare Software zu entwickeln, ist eine echte Herausforderung. Um Software hoher Qualität etwas näher zu kommen, sind Tests ein unerlässlicher Bestandteil im Entwicklungsprozess.
1.1 Motivation
Die Motivation für Softwaretests ist die angestrebte Qualität des Softwareproduktes.
Fehlerarme, robuste und funktionierende Software ist das Ziel eines jeden Softwareentwicklers. Dies kann aber nur mit entsprechenden Tests erreicht werden. Allerdings versteht unter dem Begriff „Testen“ Jeder etwas anderes; angefangen vom fehlerfreien Kompilieren eines Programmes bis hin zu äußerst intensiven methodischen Tests jeglicher
Art. Je gründlicher die Tests sind, desto höher ist die Qualität der Software. Zwar sind
selbst die intensivsten Tests kein Garant dafür, dass die Software fehlerfrei ist, aber es
kann dann schon von einer wesentlich höheren Qualität gesprochen werden. Aus diesem
Grunde ist das Testen von Software ein essentieller Schritt bei der Entwicklung.
1
1 Einleitung
1.2 Problemstellung
Das größte Problem bei Softwaretests ist der Aufwand. Das manuelle Erstellen der
Tests und das Konfigurieren der Eingabewerte sowie das Prüfen der Rückgabewerte kostet sehr viel Zeit. Tests können zwar automatisiert werden, allerdings kostet auch dies
sehr viel Zeit von Entwicklern und/oder Testern, da die Tests auf jede zu testenden Komponente einzeln zugeschnitten werden müssen. Individualisierte Tests sind aufwändig zu
erstellen, da die Eigenheiten von Methoden und Klassen gezielt angesprochen und getestet werden müssen. Grobe Tests auf Knopfdruck wären eine gute Ergänzung, um während
der Entwicklung zu testen. Solche Tests sollen mit dem entwickelten Testsystem realisiert werden, ohne dass ein Entwickler selbst manuell Werte oder andere Informationen
eingeben muss.
1.3 Zielstellung
In dieser Arbeit sollen Softwaretests untersucht und ein eigenes Testsystem entwickelt werden. Das Testsystem basiert auf der Methode Fuzzing. Es soll versucht werden,
mit diesem System schnell und einfach Fehler zu finden, die mit anderen Systemen nur
mit wesentlich größerem Aufwand zu finden wären. Das Testsystem trägt den Namen
„AutoTest.Net“ und soll in mehreren Schritten unterschiedliche Fähigkeiten aufweisen
(siehe Kapitel 5). Ziel des Projektes ist es, eine oder mehrere Klassen automatisch zu instanziieren und alle Methoden zu testen, indem die Methoden mit unterschiedlichen und
teilweise zufällig generierten Parameterwerten aufgerufen werden. Dabei wird das Verhalten der Methode überwacht. Am Ende eines Tests wird dem Benutzer ein übersichtliches Protokoll ausgegeben, aus dem genau ersichtlich ist, welche Methode mit welchen
Parameterwerten welchen Fehler erzeugt hat. Folgende Fähigkeiten soll das Testsystem
aufweisen:
• Laden und Analysieren des Aufbaus einer .NET-Assembly
• Konfigurieren der Testgenerierung
• Durchführen von Funktionstests
• Ausgabe eines Protokolls
2
1 Einleitung
1.3.1 Warum .NET
Bei Recherchen in der .NET-Welt wurde festgestellt, dass Fuzzing-Tests für Software
nicht so stark verbreitet sind wie im Java-Umfeld. Aus diesem Grund soll das entwickelte System in .NET geschrieben und für .NET-Programme anwendbar sein. Das .NETFramework ist ein zukunftssicheres Entwicklungs-Framework der Firma Microsoft. Es
bietet sehr vielfältige und moderne Möglichkeiten für die Entwicklung von Software.
Anwendungsentwicklung mit dem .NET-Framework ist äußerst effektiv und effizient. In
Kombination mit Microsoft Visual Studio 2008 werden viele Ergänzungen geliefert, die
das Entwickeln an vielen Punkten enorm erleichtern und unterstützen.
1.3.2 Was ist Reflection
Der Namespace System.Reflection ist seit .NET 1.1 ein fester Bestandteil des Frameworks. Die enthaltenen Objekte bieten die Möglichkeit, Typinformationen von Objekten und deren Bestandteilen in .NET zur Laufzeit der Software abzufragen, zu analysieren
und auszuwerten. Um dies genauer zu erläutern werden vorher einige Begriffe erklärt:
• .NET-Assembly
Eine Assembly ist eine Datei, welche typischerweise auf .exe oder .dll endet, die
mithilfe eines .NET-Compilers erstellt wurde. Neben dem eigentlichen Quellcode,
der in der Assembly als Modul zur Ausführung bereit steht, kann eine Assembly
Ressourcen wie Bilder oder Textdateien beinhalten.
• Properties
Properties sind ein Bestandteil einer Klasse auf den über einen Accessor (eine
Get- und/oder Set-Methode) zugegriffen wird, wobei die Accessor nicht explizit
aufgerufen werden. Ein solcher Klassen-Member kapselt interne Variablen (Felder). Properties stellen Daten dar, wohingegen Methoden Aktionen darstellen. Die
im folgenden Listing dargestellte Klasse Kunde enthält eine Property Name vom
Typ string (siehe Listing 1.1):
Listing 1.1: Beispiel für Property
1 class Kunde
2 { public string Name { get ; set ; } }
3
4 Kunde kunde = new Kunde () ; // Neue Instanz erzeugen
5 kunde . Name = " Meier ";
// Property Name zuweisen
3
1 Einleitung
• Attribut
Mit Attributen sind in .NET Metainformationen für Klassen, Methoden oder auch
Properties gemeint. In Java werden diese Attribute „Annotations“ genannt. Sie
werden über den Objekten in eckigen Klammern (C#) angegeben. In der UML
(Unified Modeling Language) ist ein Attribut ein Speicherplatz, der einem Objekt
zugeordnet ist, z.B. für Zwischenspeicherung von Ergebnissen bei Berechnungen.
Das folgende Beispiel zeigt das Serializable-Attribut über einer Klasse, mit dem
angegeben wird, dass diese Klasse serialisiert werden kann (siehe Listing 1.2):
Listing 1.2: Beispiele für ein Attribut
1 [ Serializable ]
2 class Hello
Zu den auswertbaren Typinformationen gehören enthaltene Klassen in einer Assembly,
Methoden oder Properties von Klassen bis hin zu Informationen über lokale Variablen
einer Methode. Ebenso sind alle Arten von Attributen über Reflection abfragbar. Im folgenden Beispiel wird die Benutzung von Reflection vorgestellt (siehe Listing 1.3):
Listing 1.3: Beispiele für Reflection
1 using System ;
2 using System . Reflection ;
3 namespace ReflectionTest
4 {
5
class Hello
6
{
// Test - Klasse " Hello "
7
public void World ()
// Methode " World "
8
{ Console . WriteLine ( " Hello World !" ); }
// Ausgabe von " Hello World "
9
}
10
class Program
11
{
// Hauptprogrammklasse
12
static void Main ( string [] args )
13
{
14
Hello hello = new Hello () ;
15
hello . World () ;
// Einsprungmethode Main
// Erzeugen einer neuen Instanz ohne Reflection
// Ausführen der Methode
16
17
Assembly a = Assembly . GetExecutingAssembly () ; // aktuelle Assembly abrufen
18
Type t = a. GetType ( " ReflectionTest . Hello " ); // Abrufen des Typs Hello
19
MethodInfo m = t. GetMethod ( " World " );
20
Object o = Activator . CreateInstance ( t );
// Instanzerzeugung mit Rflctn .
21
m. Invoke ( o , null );
// Ausführen der Methode
22
23
}
}
24 }
4
// Methode " World " des Typs " Hello "
1 Einleitung
Dieses Beispiel führt zwei Mal die Methode World aus, weswegen auch „Hello
World!“ zwei Mal auf der Konsole ausgegeben wird. Reflection sind ebenfalls in der
Lage, mithilfe des Namespace System.Reflection.Emit Code dynamisch zu erzeugen. Parallel dazu gibt es noch Introspection, diese Technik wird aber nur mit FxCop
ausgeliefert (vgl. Abschnitt 4.2.1 auf Seite 42) und ist aufgrund der fehlenden Fähigkeit,
Programmteile auszuführen, für diese Arbeit weniger interessant.
1.4 Aufbau
Im folgenden Kapitel werden Softwarefehler im Allgemeinen untersucht und mit berühmten Beispielen belegt. Im Kapitel 3 werden allgemeine Testverfahren vorgestellt und
Vor- und Nachteile erörtert. Ein Überblick über aktuell vorhandene Testsysteme wird in
Kapitel 4 gegeben. In Kapitel 5 werden das Projekt im Detail formuliert und Anforderungen an die zu erstellende Software definiert. Implementiert wird AutoTest.Net in Kapitel
6, wo Probleme und Herausforderungen bei der Umsetzung sowie Fortschritte dokumentiert werden. In der experimentellen Phase (Kapitel 7) wird das Testsystem angewendet
und abgewogen, inwiefern ein praktischer Einsatz denkbar ist. Abschließend wird die gesamte Arbeit in Kapitel 8 zusammengefasst. Sowohl die Grenzen als auch ein Ausblick
darüber, welche ergänzenden oder weiterführenden Möglichkeiten denkbar sind, werden
erörtert.
Als allgemeiner Hinweis soll vermerkt werden, dass alle Quellcodebeispiele aus Platzgründen nicht mit den üblichen XML-Kommentarblöcken versehen sind, aus denen später eine übersichtliche Quellcodedokumentation erzeugt werden kann. Weiterhin sei angemerkt, dass die genutzten programmiertechnischen Begriffe an die von Microsoft veröffentlichte C#-Terminologie angelehnt sind, welche unter folgender Adresse zu finden
sind: http://msdn.microsoft.com/de-de/library/ms173231.aspx.
5
Bekannte Fehler und deren Ursache
[2]
2.1
„Die Fehler sind alle da, sie müssen nur noch gemacht werden.“
Die Einführung
Savielly Tartakower, polnischer Schachspieler
des Begriffes
„Software-Bug“
2.2
„The most dangerous phrase in the language is, ’We’ve always done it
this way.’“
Grace Murray Hopper, Konteradmiral der amerikanischen Marine, Doktor für Mathematik, Entwicklerin des ersten Compilers und Hauptentwicklerin der Programmiersprache COBOL (Wil04)
Allgemeines zu
Softwarefehlern
2.3
Ursachen und
Ausmaße von
Softwarefehlern
Wenn es keine Fehler in Software geben würde, so müsste sie nicht getestet werden.
2.4
Was genau ist ein Softwarefehler bzw. welche Auswirkungen kann er haben? Die wich-
Zusammen-
tigste Frage aber ist, welche Ursachen diesen Fehlern zu Grunde liegen? Das Problem
fassung
liegt bekanntlich immer im Detail. Deswegen sollen in diesem Kapitel Fehler in Software und mögliche Fehlerarten untersucht und deren Ursache analysiert werden. Oft ist das
Ausmaß eines Fehlers bei genauerer Fehlerbetrachtung weit größer, als es auf den ersten
Blick den Anschein hat. Um dies zu belegen, werden einige berühmte Fehler vorgestellt,
die verheerende Auswirkungen hatten und teilweise viele Menschenleben gekostet haben.
2.1 Die Einführung des Begriffes „Software-Bug“
Der Begriff „Bug“ (dt. Käfer/Wanze) stammt aus der Zeit, als Computer noch mit
Röhren und Relais realisiert waren. Damals kam es des Öfteren vor, dass Insekten in die
Röhren oder Relais hineinflogen und es so zu Kurzschlüssen oder anderen Fehlern in
der Hardware kam. Der erste offizielle Bug wurde am 9. September 1945 an der Har-
6
2 Bekannte Fehler und deren Ursache
vard University im damaligen Großrechner „Mark II Aiken Relay Calculator“ von Frau
Konteradmiral Grace Murray Hopper, Doktor für Mathematik, gefunden. (Wil04)
Der Begriff Bug hat sich stark verbreitet und ist heute ein gängiges Wort, um Fehler
in einer Software zu beschreiben. So entstand auch das Wort „Debugger“ (dt. entwanzen/entkäfern). Fast jedes Softwareentwicklungs-Tool verfügt heutzutage über einen Debugger, um Fehler in Software aufspüren zu können. Dabei wird die Möglichkeit geboten,
ein Programm Schritt für Schritt abzuarbeiten und alle Zustände von internen Variablen
oder anderen Objekten aufzuzeigen.
2.2 Allgemeines zu Softwarefehlern
Ein Fehler in einer Software ist ein unerwartetes Verhalten der Software. Etwas detaillierter betrachtet ist ein Fehler ein Unterschied zwischen beobachteten, ermittelten
oder berechneten Zuständen oder Vorgängen bzw. Daten und wahren, festgelegten oder
theoretisch korrekten Zuständen oder Vorgängen bzw. Daten. Das deutsche Institut für
Normen (DIN) definiert Fehler folgendermaßen: Ein Fehler ist die Nichterfüllung einer
Forderung (Vog08).
Wenn ein Programmierer einen Fehler im Quellcode macht, so handelt es sich hierbei
um einen Defekt (engl. Defect). Bei der Ausführung erzeugt dieser Fehler im Quellcode einen inkorrekten Programmzustand, der Infektion (engl. Infection) genannt wird. Die
Infektion breitet sich aus und führt früher oder später zu einem beobachtbaren Fehlverhalten der Software (engl. Failure), was im Allgemeinen Fehler oder Bug genannt wird.
Nicht jeder Fehler im Code führt zwangsläufig zu einer Infektion, und nicht jede Infektion führt zwangsläufig zu einem Fehler (Fehlverhalten) in der Software. (Mer08)
Nach Basili lässt sich ein Erwartungswert der Fehlerzahl berechnen. Es gilt:
4
F = 4, 04 + 0, 0014 · LOC 3
mit LOC = Lines Of Code (Anzahl der Programmzeilen). Somit hätte ein Programm mit
1.000 effektiven Codezeilen eine erwartete Fehlerzahl von 18,04. (sR08, S. 45) Ein Graph
visualisiert das LOC - Fehlerverhältnis (siehe Abbildung 2.1 auf der nächsten Seite).
Diese empirische Formel basiert auf Auswertungen von vergangenen Projekten der
NASA, bei der die Beziehung zwischen der Größe eines Programmes, also die Anzahl
der Quellcodezeilen, und der Programmfunktionalität sowie der Fehlerträchtigkeit analysiert wurde. Auf Nachfrage deutete Basili darauf hin, dass diese Formel für eine geringe
7
2 Bekannte Fehler und deren Ursache
Abbildung 2.1: Erwartete Fehlerrate im Programmcode nach Basili
Anzahl an Quellcodezeilen nicht präzise sei. Der Nullpunkt und kleinere Werte wurden
nicht berücksichtigt, da dies den Graph der Funktion negativ beeinflusst hätte. (Bas08)
Eine andere, etwas kompliziertere Methode zur Berechnung der erwarteten Fehler entwickelten Takahashi und Kamayachi. Hierbei gilt folgende Formel (sR08):
Fr = C1 +C2 · (
DOCC
SCHG
) −C3 · ISKL −C4 · (
)
KLOC
KLOC
Wobei gilt:
• FR = Anzahl Fehler pro 1.000 Zeilen Quellcode
• C1 = 67,98
• C2 = 0,46
• C3 = 9,69
• C4 = 0,08
• SCHG = Änderungen in der Spezifikation während der Entwicklung
• KLOC = 1.000 LOC (Lines of code)
• ISKL = Durchschnittserfahrung des Teams mit der Programmiersprache in Jahren
• DOCC = Anzahl der geänderten oder neu eingefügten Seiten im Designdokument
Beispiel: In einem Softwareprojekt wurde eine Komponente von 1.000 Programmzeilen von einem Entwicklungsteam geschrieben, das seit zwei Jahren mit Java programmiert. Während der Entwicklung musste dreimal das Lastenheft geändert werden.
Die vorliegende Komponente liegt in der vierten Version vor, d.h. das ursprüngliche
8
2 Bekannte Fehler und deren Ursache
Designdokument wurde dreimal geändert. Die Fehlerrate berechnet sich nun wie folgt
(Ger08):
• SCHG = 3
• KLOC = 1
• ISKL = 2
• DOCC = 3
Die erwartete Fehlerzahl der Softwarekomponente (1.000 Programmzeilen) ist nach
dieser Formel 49,74. Diese Formel weist ausschließlich empirische Konstanten auf.
Normale Software, die keine Menschenleben oder Millionen Euro teure Ausrüstung
steuert, enthält ca. 10 bis 50 Fehler auf 1.000 Zeilen Quellcode, was auch den Erfahrungen mit Rechtschreibfehlern in neu verfassten Texten entspricht. Das sind im Durchschnitt 25 Fehler pro 1.000 Zeilen. Wichtige Software, bei der es teilweise um die Sicherheit oder auch relativ teures Equipment gehen kann, beinhaltet nur 2 bis 3 Fehler auf
1.000 Zeilen (siehe Tabelle 2.1). Diese Software wird mit wesentlich höherem Aufwand
hergestellt und viel stärker getestet. Die Software bei der NASA im Space Shuttle beinhaltet nach eigenen Angaben weniger als 0,1 Fehler auf 1.000 Zeilen Quellcode. Es wird
eine sehr penible Datenbank geführt, in die jeder jemals aufgetretene Fehler sorgfältig
mit Datum, Ursache und auch der Lösung des Problems eingetragen wird. (Gie08)
Programm
Zeilen
Handy
Space Shuttle, auch IIS
Windows 2000
SDI (Raketenabwehr USA)
200.000
3 Millionen
27 Millionen
25-100 Millionen
Fehler
ca. 500
ca. 300
ca. 50.000
ca. 10.000
Tabelle 2.1: Programme mit der Anzahl der Quellcodezeilen und Fehler (Gie08)
Zeilen müssen kommentiert, dokumentiert und getestet werden. Nicht nur einzeln,
sondern auch im Zusammenhang mit anderen Zeilen, also anderen Teilen des Programmes. Fehler können in jeder Zeile, bzw. in jeder Anweisung oder auch in jedem Operator
auftreten. Einige sind offensichtlich und schnell gefunden, andere treten nur manchmal
auf. Auch Aspekte wie z.B. das soziale oder organisatorische Umfeld des Einsatzortes der
Software können Fehlerindikatoren sein. Simple Denkfehler, wie z.B. dass aus cos(a) =
cos(b) folgt, dass a = b ist, können eine Ursache darstellen. Dies mag logisch erscheinen,
wenn es in einem Kontext gelesen wird. In einem Programm kann dies aber in einem
wesentlich komplexeren Zusammenhang auftreten. Somit können simple Sachverhalte
unüberschaubar werden. (Gie08)
9
2 Bekannte Fehler und deren Ursache
Abgesehen von bewusst betriebener Sabotage an Software trägt der Benutzer keine
Schuld an Abstürzen in Software. Bei ungültigen Benutzereingaben, die zum Absturz der
Software führen, liegt ein Fehler in dem Programmteil vor. Der Programmierer hat bestimmte Fehlerfälle nicht berücksichtigt und keine fehlerüberprüfenden Routinen implementiert, weil bestimmte Eingaben sehr unwahrscheinlich erschienen. Im Allgemeinen
wird ein Großteil des entwickelten Codes dafür verwendet, um Fehler abzufangen und
Eingangsdaten auf Plausibilität zu prüfen. Selbst die beste Recherche zu Beginn kann
niemals garantieren, dass immer alle möglichen Fälle berücksichtigt wurden. Jegliche
Tests sind kein Beweis dafür, dass eine Software fehlerfrei ist. Tests können nur das Vorhandensein von Fehlern beweisen, nicht aber deren Abwesenheit. Es kann generell nicht
davon ausgegangen werden, dass keine Fehler auftreten bzw. keine Fehlerüberprüfungsroutinen erforderlich sind. Weitere Beispiele werden im Laufe des Kapitels aufgeführt.
2.3 Ursachen und Ausmaße von Softwarefehlern
Die meisten Fehler, egal in welcher Form sie auftreten, haben Folgen. Diese Folgen
können teilweise sehr verheerende Ausmaße annehmen. Es gibt sehr unterschiedliche
Ursachen für Fehler in Software. Die Ursachen der berühmtesten Fehler werden im Folgenden etwas näher betrachtet, um einen Zusammenhang zwischen trivialen Fehlern in
Software und riesigen Ausmaßen herzustellen.
2.3.1 Tippfehler
Tippfehler werden in der Programmierung oft unterschätzt, weil normalerweise davon ausgegangen wird, dass der Compiler solche Fehler entdeckt. Fehler, wie z.B. zwei
vertauschte Buchstaben, würde wohl jeder Compiler entdecken, wenn es nicht gerade zufällig eine Struktur mit diesem Namen gibt und diese auch noch auf die gleiche Art und
Weise angesprochen werden würde. Wenn aber eine solche Struktur existiert, liegt wahrscheinlich ein Designfehler in der Namensgebung vor. Selbst in natürlichen Sprachen
können kleine Tippfehler bei Satzzeichen ganz andere Bedeutungen hervorrufen.
• Der brave Mann denkt an sich selbst zuletzt.
• Der brave Mann denkt an sich, selbst zuletzt.
In einem Satz wurde ein Komma gesetzt, im anderen nicht. Das Ergebnis ist eine gänzlich gegenteilige Aussage. Auch im nächsten kurzen Beispiel ist lediglich ein Komma
verschoben worden, was die Aussage verändert. (Gie08)
10
2 Bekannte Fehler und deren Ursache
• Treu war sie, nicht ohne Tränen ließ ich sie gehen.
• Treu war sie nicht, ohne Tränen ließ ich sie gehen.
In .NET sind solche, meistens offensichtlichen Fehler, aufgrund der CompilerWarnungen recht leicht zu finden. In anderen Sprachen können kleine Tippfehler schon
verheerende Auswirkungen haben, besonders dann, wenn der Compiler sie nicht als fehlerhaft interpretiert. Diese Codezeilen werden dann vom Compiler in einer anderen Art
und Weise übersetzt, als es der Entwickler im Sinn hatte. Die klassischen Tippfehler sind
falsche Interpunktionszeichen, wie z.B. ein Semikolon hinter einer while-Schleife oder
Punkte und Kommas, welche vertauscht benutzt werden. Teilweise können auch Leerzeichen oder andere, nicht sichtbare Zeichen zu Fehlern führen. Diese sind ohne einen
Debugger besonders schwer zu entdecken.
• Mariner 1 (1962)
Am 22. Juni 1962 verlor die NASA die Raumsonde Mariner 1, die in Cape Canaveral startete und sich auf dem Weg zur Venus befand. Durch eine Abweichung
der Flugbahn wurde die Rakete nach 290 Sekunden zerstört. Ursache hierfür war
ein simpler Rechtschreibfehler in dem Programm zur Steuerung der Flugbahn der
Trägerrakete. Das Programm wurde in der Programmiersprache FORTRAN entwickelt (siehe Listing 2.1).
Listing 2.1: Ausschnitt aus dem FORTRAN-Steuerprogramm von Mariner 1 (Gie08)
1
IF ( TVAL . LT . 0.2E -2) GOTO 40
2
DO 40 M = 1, 3
3
W0 = (M -1) *0.5
4
X = H *1.74533 E -2* W0
5
DO 20 N0 = 1, 8
6
EPS = 5.0*10.0**( N0 -7)
7
CALL BESJ (X , 0, B0 , EPS , IER )
8
IF ( IER . EQ . 0) GOTO 10
9 20
CONTINUE
10
DO 5 K = 1. 3
11
T(K) = W0
12
Z = 1.0/( X **2) * B1 **2+3.0977 E -4* B0 **2
13
D(K) = 3.076 E -2*2.0*(1.0/ X* B0 * B1 +3.0977 E -4*
!!! Fehlerhafte Zeile !!!
14
*( B0 **2 - X* B0 * B1 ))/Z
15
E(K) = H **2*93.2943* W0 / SIN ( W0 )*Z
16
H = D(K) -E(K)
17 5
CONTINUE
18 10
CONTINUE
19
Y = H/W0 -1
20 40
CONTINUE
11
2 Bekannte Fehler und deren Ursache
In Zeile 10 wurde anstatt einem Komma, welches eine Schleife beschrieben hätte,
ein Punkt gesetzt, was einer normalen Variablenzuweisung entspricht. Aufgrund
dessen wurde der Variablen „DO5K“ der Wert 1,3 zugewiesen, da in FORTRAN
Variablen Zahlen und Leerzeichen enthalten dürfen. Somit wurde die ursprünglich
gedachte Schleife niemals ausgeführt. (Gie08)
Auch in anderen Sprachen können solche trivialen Satzzeichen ein gänzlich anderes
Verhalten hervorrufen. Ein altbekanntes Problem ist der ungewollte Abbruch einer Bedingung durch ein Semikolon. Dies geschieht im Zusammenhang mit Steueranweisungen
wie for, do/while oder if. Die folgenden Beispiele zeigen die drei Programmiersprachen C, Pascal und Modula 2 (siehe Listing 2.2):
Listing 2.2: Beispiele für Tippfehler und deren Auswirkungen (IS08)
1 C
for (i =1; i <=3; ++ i);
2
f(i);
1 mal mit i =4
3
4 Pascal
for i :=1 TO 3 DO ;
5
f(i);
1 mal mit i =3
6
7 Modula 2
FOR i := 1 TO 3 DO ;
8
f(i)
9
richtig
END ;
Dieses Problem, welches in C besteht, ist ebenfalls in C++ und C# vorhanden, allerdings liefert Visual Studio bei dieser Anweisung eine Warnung, die auf einen ungewollten
Schleifenabbruch hinweist („Possible mistaken empty statement“). In C und C++ geht es
sogar noch weiter. Im folgenden Listing sind zwei Schleifen gezeigt, die sich beide kompilieren lassen (siehe Listing 2.3):
Listing 2.3: Beispiele für Tippfehler in C-Schleifen
1 // Schleife mit 6 Durchläufen
2 float x = -1;
3 while (x < 0.1)
4 {
5
x +=0.2;
6
Console :: WriteLine (L"x: " + x. ToString () );
7 }
8 // Endlosschleife aufgrund des Kommaoperators
9 float x = -1;
10 while (x < 0 ,1)
11 {
12
x +=0.2;
13
Console :: WriteLine (L"x: " + x. ToString () );
14 }
12
2 Bekannte Fehler und deren Ursache
Die erste Schleife endet nach 6 Durchläufen, wohingegen die andere Schleife nie
endet, also eine Endlosschleife darstellt. Diese recht simplen Beispiele zeigen, welche
Auswirkungen triviale Tippfehler haben können. Solche Fehler können nur durch gewissenhafte Programmierung vermieden oder im Nachhinein durch Tests und mit Hilfe des
Debuggers gefunden werden.
2.3.2 Umwandlungsfehler
Umwandlungsfehler treten bei der Umwandlung einer Variable von einem Datentyp
in einen anderen auf. Wenn z.B. eine 64-Bit-Gleitkommazahl in eine 64-Bit-Ganzzahl
konvertiert wird, entstehen unter Umständen Ungenauigkeiten, da die Nachkommastellen nicht mit übertragen und entweder wegfallen oder gerundet werden. Bei einer Umwandlung von einem Datentyp in einen Datentyp mit geringerem Speicherbedarf, z.B.
von einer 64-Bit-Zahl in eine 16-Bit-Zahl, können unter Umständen auch Überläufe entstehen, wenn der Wert größer als der maximale Wert des kleineren Datentyps ist (vgl.
Abschnitt 2.3.3 auf der nächsten Seite).
• „Ariane 5“-Rakete (1996)
Wohl einer der teuersten Softwarefehler zeigte seine Ausmaße am 4. Juni 1996,
als vom französischen Guyana aus die „Ariane 5“-Rakete startete. Sie explodierte
ca. 40 Sekunden nach dem Start. Der Verlust betrug ca. 500 Millionen Dollar für
die Rakete und vier Satelliten zuzüglich der weiteren 300 Millionen Dollar für
nachfolgende Verbesserungen. Der Verdienstausfall betrug ca. 2 bis 3 Jahre. Der
Bordcomputer stürzte nach 36,7 Sekunden ab, weil eine 64-Bit-Gleitkommazahl
in ein 16-Bit-Signed-Integer umgewandelt werden sollte. In einer Flughöhe von
3700m erreichte die Horizontalgeschwindigkeit einen Wert von 32768,1 interne
Einheiten und war somit größer als 215 = 32768. (Gie08)
Die eingesetzte Software stammte aus der „Ariane 4“-Rakete, wo sie einwandfrei
funktionierte. Tragischerweise war dieser Teil der Software für den eigentlichen
Flug überflüssig. Die Geschwindigkeit war bei der „Ariane 4“ bei weitem nicht
so hoch wie bei der „Ariane 5“. Ein redundanter Computer verwendete die gleiche
Software. Dieser war aber schon kurze Zeit vorher abgestürzt. Die Zahlenumwandlung war nicht abgesichert, da niemand gedacht hätte, dass die Geschwindigkeit so
groß werden könnte. Im folgenden Listing ist in der Zeile 15 die Umwandlung zu
sehen, welche den tragischen Unfall auslöste (siehe Listing 2.4): (Huc08)
13
2 Bekannte Fehler und deren Ursache
Listing 2.4: Auszug aus dem Trägheitsnavigationssystem der „Ariane 5“ (Gie08)
1 ...
2 declare
3 vertical_veloc_sensor : float ;
4 horizontal_veloc_sensor : float ;
5 vertical_veloc_bias : integer ;
6 horizontal_veloc_bias : integer ;
7 ...
8 begin
9 declare
10 pragma suppress ( numeric_error , horizontal_veloc_bias );
11 begin
12 sensor_get ( vertical_veloc_sensor );
13 sensor_get ( horizontal_veloc_sensor );
14 vertical_veloc_bias := integer ( vertical_veloc_sensor );
15 horizontal_veloc_bias := integer ( horizontal_veloc_sensor );
16 ...
17 exception
18 when numeric_error => calculate_vertical_veloc () ;
19 when others => use_irs1 () ;
20 end ;
21 end irs2 ;
2.3.3 Überläufe
Jeder Datentyp entspricht einer gewissen Anzahl an Bytes im Speicher. Zum Beispiel
entspricht der Datentyp byte dabei genau einem Byte. In den meisten Sprachen ist die
Folge eines Überlaufs, dass der Variablenwert wieder auf der anderen Seite des Wertebereiches beginnt und daher vom größtmöglichen Wert auf den kleinstmöglichen Wert,
oder andersherum, überspringt. Bei einem 16-Bit-Integer (Int16), welcher einen Wertebereich von -32768 bis +32767 hat, gilt also folgendes: 32767 + 1 = −32768. In .NET
bzw. in Microsoft Visual Studio 2008 ist dies eine Einstellungssache im Compiler oder
in der Entwicklungsumgebung. Teilweise können Überläufe auch ein gewünschter Effekt
sein, z.B. bei einem Ringpuffer, bei dem nach der maximalen Anzahl wieder automatisch
von vorn begonnen werden soll. Überläufe können auch als Folge einer Umwandlung
(vgl. Abschnitt 2.3.2 auf der vorherigen Seite) entstehen. Allerdings gibt es ab .NET 2.0
das Schlüsselwort checked, mit dem das Werfen einer Exception bei einem Überlauf für
arithmetische Operationen und Konvertierungen mit ganzzahligen Typen erzwungen werden kann. Somit kann sichergestellt werden, dass ein Überlauf niemals übersehen oder
versehentlich ausgeführt wird, da eine System.OverflowException bewusst behandelt
14
2 Bekannte Fehler und deren Ursache
werden muss. Der Befehl checked kann als Methode mit runden Klammern oder auch
als Block mit geschweiften Klammern genutzt werden (siehe Listing 2.5).
Listing 2.5: Beispiel für sichere Handhabung von Überläufen
1 using System ;
2
3 namespace @checked
4 {
5
class Program
6
{
7
static void Main ( string [] args )
8
{
9
short x = 32765;
10
// 16 Bit Wert mit MAX = 32767
for ( int i = 0; i < 5; i ++)
11
// checked - Block . Überläufe in diesem Block werden sicher erkannt .
12
checked
13
{
14
Console . WriteLine ( x ++ );
15
}
16
// alternativ zu diesem Block würde auf folgendes gehen :
17
// Console . WriteLine ( checked ( x ++ ) );
18
}
19
}
20 }
In diesem Beispiel entsteht kein Überlauf, da eine System.OverflowException geworfen wird. Ohne die Hilfe von checked wäre die Ausgabe in diesem Beispiel:
32765
32766
32767
-32768
-32767
Im Gegensatz zum Schlüsselwort checked steht das Schlüsselwort unchecked. Es
wird genutzt, um Überlaufprüfungen für arithmetische Operationen und Konvertierungen
mit ganzzahligen Typen zu unterdrücken.
• Röntgen/Elektron-Bestrahlung - Therac-25 (1985-1987)
Das Bestrahlungsgerät Therac-25 der Firma Atomic Energy of Canada Limited
(AECL) sollte vielen Menschen helfen, Krankheiten wie Krebs zu überwinden.
Die Patienten wurden einer Strahlendosis ausgesetzt, die ca. das 100fache der eigentlich vorgesehenen Energiemenge besaß. Die Software, die dieses Gerät steuerte, bestand aus mehr als 20.000 Anweisungen und wurde von nur einem einzigen
15
2 Bekannte Fehler und deren Ursache
Programmierer entwickelt. Eine Variable, die als Zählvariable für Fehleinstellungen galt, war vom Typ byte. Wenn diese Variable den Wert 0 hatte, lag kein Fehler
vor und die Bestrahlung konnte begonnen werden. Bei genau 256 fehlerhaften Einstellungen entstand ein Byte-Überlauf und der Wert der Variablen lag wieder bei
0. Der Fehler wurde anfangs nicht dem Gerät zugeschrieben. Erst nachdem insgesamt 6 Menschen an den Folgen der viel zu hohen Strahlendosis gestorben waren,
wurde das Gerät außer Betrieb genommen. (Pfe08)
2.3.4 Unterschiedliche Einheiten
Noch gibt es auf der Welt für gleiche Dinge viele unterschiedliche Namen in vielen
unterschiedlichen Sprachen. Genauso wie diese unterschiedlichen Bezeichnungen haben
viele Nationen dieser Erde auch noch eigene Begriffe und Einheiten für z.B. Größen- oder
Gewichtsangaben, die aus längst vergangenen Zeiten stammen. Einheiten wie Pfund, Unze, Fuß oder Gallone, die auch noch in jeder Gegend andere Werte annehmen können,
sind ungenau, uneinheitlich und führen zwangsläufig zu Problemen. Aus diesem Grunde
wurden die SI-Basiseinheiten eingeführt, welche genormt sind und deswegen ein internationales Einheitensystem darstellen. Die SI-Tabelle beinhaltet genau 7 Basiseinheiten,
u.a. Einheiten für Länge, Zeit, Masse und Lichtstärke. Aus diesen Einheiten lassen sich
wiederum andere zusammengesetzte Einheiten bilden, die auch mit anderen Basis- und
abgeleiteten Einheiten widerspruchsfrei kombiniert werden können. Abgesehen vom Kilogramm basieren alle Basiseinheiten auf Naturgesetzen und eignen sich von daher hervorragend für alle Regionen dieser Erde. Welche Folgen aufgrund von unterschiedlichen
Einheiten entstehen können, zeigen die folgenden Beispiele:
• Mars Climate Orbiter (1999)
Am 11. Dezember 1998 startete eine Sonde ihren Weg zum Mars mit der Aufgabe, eine Umlaufbahn zu erreichen und die Marsoberfläche zu kartographieren. Ein
dreiviertel Jahr später, am 23. September 1999, kurz bevor die Sonde ihre Marsumlaufbahn erreichte, stürzte sie beim Anflug auf den Mars ab. Die Ursache des
Absturzes lag in der Verwendung von unterschiedlichen Einheiten für die Angabe
von Kräften. Die damalige Firma lieferte Module, die mit der imperialen Einheit
„pound“ (Pfund) rechneten. Die NASA hatte aber in Auftrag gegeben, dass das
Modul die Einheit in Newton angeben sollte, was dem metrischen System entspricht. Dieser simple Fehler hatte den Absturz der Sonde zur Folge. Die Kosten
beliefen sich auf ca. 125 Millionen US-Dollar. (Gla08)
16
2 Bekannte Fehler und deren Ursache
• Shuttle-Laser-Fehler (1985)
Am 19. Juni 1985 bekam die Mannschaft eines Space Shuttles die Aufgabe, dass
Shuttle so zu positionieren, dass ein Spiegel, welcher an der Außenseite des Shuttles montiert war, einen Laserstrahl umlenken konnte. Dieser Laserstrahl ging von
einem Berg aus, welcher sich 10.023 Feet (ca. 3055 m) über dem Meeresspiegel
befand. Das Computerprogramm, welches das Shuttle steuerte, hatte als Berechnungsgrundlage die Einheit nautical mile (dt. nautische Meile oder auch Seemeile). Das Shuttle wurde also so positioniert, als wenn der Berg eine Höhe von 10.023
nautischen Meilen (ca. 18.562.596 m) hätte. (Lin85)
2.4 Zusammenfassung
Fehler können immer und überall in einer Software auftreten. Die Unterschätzung des
nötigen Aufwandes, das Setzen von zu knappen Timelines und Fehlbudgetierung zwingt
Entwickler, wichtige Fehlerprüfroutinen auszulassen. Essentiellen Programmteilen wird
zu wenig Aufmerksamkeit geschenkt. Dies führt häufig zu nicht bedachten Nebeneffekten und Fehlern. Zudem entstehen Fehler hauptsächlich, weil sich die Anforderungen
bei wohl keinem anderen Produkt so schnell ändern wie bei Software. Solche ständig
wechselnden Aufgabenstellungen sind eine große Herausforderung an Entwickler, da bei
größeren Änderungen das Projekt fast immer neu überdacht und überarbeitet werden
muss. Viele Fehler könnten eventuell durch weitreichende Fehlerprüfroutinen vermieden
werden, welche unvorhergesehene Fälle, wie Division durch Null oder Zahlenumwandlungen, absichern. Deswegen hilft jede Fehlerprüfroutine die Software etwas sicherer
und fehlerfreier zu machen. Durch solche Routinen können Fehler früher erkannt werden. Selbst Fehler, die auf den ersten Blick keine Auswirkungen hätten, können entdeckt
werden. Diese Routinen allein sind aber nicht hinreichend, um fehlerarme Software zu
produzieren. Natürlich sind Tests eine äußerst nützliche und auch notwendige Art, Fehlern vorzubeugen. Und das so frühzeitig wie möglich.
Durch ausreichende Kontrolle können Fehler, die in der Soft- und Hardwareentwicklung entstehen, im Allgemeinen vermieden werden. Dies kann zum einen durch gute
Kommunikation der Entwickler gewährleistet werden, zum anderen durch gute Dokumentation und Spezifikation des Projekts. Einst funktionstüchtige Soft- und Hardware
sollte vor der Wiederverwendung in anderen Projekten genau auf deren Funktionsweise
unter anderen bzw. neuen Voraussetzungen getestet werden. Aus all diesen Fehlern muss
für zukünftige Softwareprojekte gelernt werden.
17
Grundlegendes zu Softwaretests
[3]
3.1
„Program testing can be used to show the presence of bugs, but never
to show their absence!“
Was ist Testen
Edsger W. Dijkstra, niederländischer Informatiker
Qualitäts-
3.2
merkmale
„Qualität ist kein Zufall; sie ist das Ergebnis angestrengten Denkens.“
John Ruskin, englischer Schriftsteller und Maler
3.3
Blackbox-Tests
3.4
Whitebox-Tests
Testen und Qualität stehen in einer engen Beziehung zueinander. Getestet werden kann
3.5
nur, was vorher definiert wurde. Definiert werden Anforderungen an die Software, was
Testverfahren
ebenfalls zu Qualitätsmerkmalen zählt. Neben den unterschiedlichen Testarten soll in
3.6
diesem Kapitel ein allgemeiner Überblick über das Testen gegeben und erläutert werden,
Konkrete
was genau Testen eigentlich ist.
Testverfahren
3.7
3.1 Was ist Testen
Zusammenfassung
Testen ist ein wesentlicher Schritt beim Entwickeln von Software. Im Zuge dessen
treten viele Begriffe auf, die im Folgenden erläutert werden sollen.
• Testorakel
Dieser sehr mystisch klingende Begriff weist auf eine Komponente hin, welche bei
der Eingabe von Testfällen immer die korrekten, erwarteten Ausgabewerte liefert.
Wichtig ist hierbei, dass korrekte Ausgabewerte niemals anhand eines Programmcodes ermittelt werden dürfen, sondern immer anhand von Spezifikationen. Ein
rechnergestütztes Testorakel wäre optimal, allerdings handelt es sich dabei meistens um einen Menschen. (Ger08)
18
3 Grundlegendes zu Softwaretests
• Testtreiber
Ein Testtreiber ist ein Programm, welches in der Lage ist, einen Test an einem Testobjekt durchzuführen und die Resultate aufzunehmen. Mit Hilfe eines Testtreibers
werden auch Fehlverhalten der Software protokolliert. Im Rahmen dieser Arbeit
wird ebenfalls ein solcher Testtreiber entwickelt.
• Testen
Das Testen ist ein Prozess, um die Qualität von Software systematisch zu untersuchen, indem Teile der Software in einer definierten Umgebung ausgeführt werden, um die Resultate der Software mit den Erwartungen zu vergleichen (dynamische Tests). Mit möglichst gut gewählten Testwerten (Möl08) wird eine Software
gegen die Anforderungen hin getestet, die zu Beginn festgelegt wurden, um so
die Ist-Daten mit den Soll-Daten zu vergleichen. Testen ist im Allgemeinen eine
stichprobenartige Ausführung der Software unter spezifizierten Testbedingungen
(vV08), um die Qualität von Software zu messen. Dabei wird unterschieden zwischen Verifizieren und Validieren. Aus den Anforderungen werden die Soll-Daten
vom Testorakel abgeleitet.
Alle Funktionalitäten, Daten oder Leistungen der Software, müssen mindestens
mit einem Testfall abgedeckt sein. Ziel des Testens ist es, Fehler zu finden oder
diese zu vermeiden. Ziel ist nicht, Fehler zu korrigieren, denn dieser Vorgang wird
Debuggen genannt. Weiterhin ist Testen kein Korrektheitsnachweis, denn ein Test
kann nur das Vorhandensein von Fehler nachweisen, nicht aber, dass keine Fehler
vorhanden sind. (Ger08)
• Validieren
Beim Validieren bzw. bei einer Validierung werden bestimmte Eigenschaften der
Software bezüglich ihrer Anforderungen geprüft (Möl08). Die Entwicklungsergebnisse werden bezüglich der individuellen Anforderungen geprüft (vV08). Laut ISO
ist Validierung das Sicherstellen, dass ein Produkt und somit auch eine Software
zu den Benutzerbedürfnissen und den Anforderungen konform ist (Ger08). Hier
gilt als Anmerkung, dass die Benennung „validiert“ eine Bezeichnung für den Status der Software darstellt (Vog08). Die Bedingungen, unter denen eine Software
validiert wird, können echt oder simuliert sein (ebd.).
• Verifizieren
Beim Verifizieren bzw. bei einer Verifikation wird die Qualität der Software durch
einen mathematischen Beweis gegen die Vorgaben geprüft. Somit kann die Korrektheit eines Softwareteils bewiesen werden (vV08). Dies ist allerdings nur mög-
19
3 Grundlegendes zu Softwaretests
lich, wenn die Anforderungen an die Software in einem exakten mathematischen
Modell spezifiziert sind. Im Gegensatz zu den testenden (validierenden) Verfahren
wollen verifizierende Verfahren die Korrektheit solcher Spezifikationen beweisen
(Möl08). Als Anmerkung gilt, dass die Benennung „verifiziert“ zur Bezeichnung
des entsprechenden Status verwendet wird (Vog08).
• Testfall
Ein Testfall umfasst neben den notwendigen Vorbedingungen die aus den Spezifikationen oder dem Programm abgeleiteten Testdaten als Eingabedaten für den Test.
Zusätzlich beinhalten die Testfälle die zugehörigen erwarteten Ergebnissen (Sollwerte) sowie die Prüfanweisungen (wie die Eingaben an das Testobjekt übergeben
werden müssen) und erwartete Nachbedingungen. (Möl08) und (vV08)
• Testdaten
Testdaten sind die Werte, die für die Tests genutzt werden (Möl08). Im Rahmen
dieser Arbeit entspricht ein Testdatum genau einem übergebenen Parameterwert,
z.B. „15.08.2008“ für einen Parameter vom Typ System.DateTime. Bei mehreren
Parametern einer Methode wird für jeden Parameter ein Wert übergeben.
• Testplan
Zeitliche Planung der Testdurchführung (Zuordnung der Testfälle zu Testern und
Festlegung des Durchführungszeitpunktes). Enthält Verzeichnis aller Testfälle sowie die erwarteten Ergebnisse, in der Regel thematisch bzw. nach Testzielen gruppiert. (vV08)
Meistens wird eine Validierung durchgeführt, weil selten ein exakt mathematisches
Modell der Anforderungen vorliegt bzw. ein Beweis der Funktionalität durchgeführt wird
oder durchgeführt werden kann. Dazu gibt es unterschiedliche Verfahren, um bestimmte
Merkmale zu testen. Kein Test kann alle Merkmale auf einmal abdecken. Bei den zu
testenden Merkmalen handelt es sich um die Qualitätsmerkmale einer Software, die im
Folgenden näher erläutert werden sollen.
3.2 Qualitätsmerkmale
Qualität im Allgemeinen ist ein fehlerfreies, funktionierendes Produkt oder eine
Dienstleistung, wie es vom Auftraggeber oder Kunden gewünscht ist. Die Qualität speziell für Software wurde eigens nach DIN ISO 9126 standardisiert. Diese Norm besagt
Folgendes: „Software-Qualität ist die Gesamtheit der Merkmale und Merkmalswerte ei-
20
3 Grundlegendes zu Softwaretests
nes Software-Produkts, die sich auf dessen Eignung beziehen, festgelegte oder vorausgesetzte Erfordernisse zu erfüllen“. (Möl08)
Diese Norm unterteilt Qualitätsmerkmale in sechs Hauptkategorien. Sie sind wiederum in weitere Teilmerkmale aufgegliedert (siehe Tabelle 3.1):
Merkmal
Teilmerkmal
Beschreibung
Funktionalität
Korrektheit
Korrektes Arbeiten der Software mit gewünschten Resultaten. Sequentielle sowie nebenläufige Fehlerfreiheit. Korrekte Terminierung.
Angemessenheit
Vorhandensein einer Reihe angemessener
Funktionen für die spezifizierte Problemstellung.
Interoperabilität
Allgemein: Fähigkeit der Software mit anderen Systemen zusammenzuarbeiten - hier
(.NET): Zusammenarbeiten von verwaltetem
und unverwaltetem Code (NWR06, S. 921).
Normenkonformität
Einhaltung bestehender Normen, Regelungen
oder Gesetze.
Sicherheit
Verhindern von unbefugtem Zugriff auf vertrauliche Daten.
Persistenz
Fähigkeit, Daten jeglicher Art dauerhaft zu
speichern und jederzeit bereitzustellen.
Zuverlässigkeit
Verfügbarkeit
Fähigkeit der Software, schnell und häufig zur
Verfügung zu stehen. Auch bei schwierigen
Einsatzsituationen.
Determinismus
Vorherbestimmbarkeit aller Reaktionen der
Software auf Aktionen.
Fehlertoleranz
Fähigkeit der Aufrechterhaltung des Leistungsniveaus beim Auftreten von Fehlern,
fehlerhafter Bedienung oder Verstöße gegen
Schnittstellenspezifikationen.
Wiederherstellbarkeit
Fähigkeit, auch im Fehlerfall mit geringstmöglichem Aufwand die Software an sich
und alle Daten wiederherstellen zu können.
21
3 Grundlegendes zu Softwaretests
Benutzbarkeit
Verständlichkeit
Das logische Konzept im Programm und deren Anwendbarkeit sind intuitiv zu verstehen.
Erlernbarkeit
Einfaches Erlernen aller Funktionen der Software.
Bedienbarkeit
Einfache und vor allem intuitiv richtige Bedienung und Steuerung der Software.
Effizienz
Ressourcen
Effizientes Verhalten der Software beim Verwenden von Ressourcen.
Laufzeit
Zeitliches Verhalten der Software bezüglich
Antwort- und Verarbeitungszeiten.
Wartbarkeit
Analysierbarkeit
Fähigkeit, mit geringstmöglichem Aufwand
Diagnosen zu stellen und Mängel, Fehler und
Ursachen zu identifizieren.
Änderbarkeit
Änderungen und Fehlerbehebung sind mit geringem Aufwand und nicht notwendigerweise Veränderung der Umgebung durchführbar
und betreffen ausschließlich die geänderten
Softwareteile.
Stabilität
Fähigkeit, nach Änderungen stabil auf unerwartete Auswirkungen zu reagieren und das
Leistungsniveau beizubehalten.
Testbarkeit
Änderungen sind mit geringstmöglichem
Aufwand prüfbar.
Portabilität
Anpassbarkeit
Fähigkeit der Software, auf unterschiedlichen
Umgebungen zu laufen, ohne diese zu verändern.
Installierbarkeit
Die Software lässt sich unter Berücksichtigung der Anforderungen mit geringstmöglichem Aufwand installieren.
Austauschbarkeit
Fähigkeit der Software, andere Software mit
geringstmöglichem Aufwand an die Software
selbst oder die Einsatzumgebung zu ersetzen.
Tabelle 3.1: Liste der Qualitätsmerkmale nach DIN ISO 9126 (Möl08) und (GK08)
22
3 Grundlegendes zu Softwaretests
Einige der Merkmale überschneiden oder bedingen sich gegenseitig, positiv sowie
auch negativ. Zum Beispiel wirkt sich die Korrektheit einer Software positiv auf die Robustheit aus, wohingegen die Effizienz einer Software sich negativ auf die Lesbarkeit
oder die Änderbarkeit auswirken kann (Möl08, nach Pomberger). Grob betrachtet treffen all diese Merkmale auf jedes Softwareprodukt zu. Bei detaillierter Betrachtung der
Teilmerkmale ist allerdings festzustellen, dass jedes Softwareprodukt seine eigenen Qualitätsmerkmale besitzt, da laut schon erwähnter Definition „vorausgesetzte Erfordernisse“
ein Teil der Qualitätsmerkmale darstellt. An einer Software können nur Qualitätsmerkmale getestet werden.
Tests bei Software sind in zwei große Kategorien unterteilt, die sich wiederum unterteilen lassen. Diese beiden Kategorien sind Blackbox- und Whitebox-Tests (TS04). Zwar
existiert seit kurzer Zeit auch noch der Begriff „Graybox-Test“, allerdings ist dies nur
eine Mischform und soll die Vorteile von Blackbox- und von Whitebox-Tests verbinden.
3.3 Blackbox-Tests
Blackbox-Tests sind Tests, welche ohne den Zugriff auf den Quellcode ausgeführt
werden. Hierbei wird die zu testende Software benutzt und von außen getestet. Deswegen
werden sie auch Funktionstests oder funktionale Tests genannt. Als Grundlage dient die
Spezifikation der Software (TS04). Zugegriffen wird auf die Schnittstelle der Software
nach außen. Diese Tests sind meist wesentlich einfacher zu erstellen als Whitebox-Tests
(vgl. Abschnitt 3.4 auf der nächsten Seite), da der Quellcode nicht zur Verfügung steht
und nicht analysiert werden muss. Blackbox-Tests eignen sich hervorragend, um schon
vorhandene Software im Nachhinein zu testen, sie können aber auch während aller anderen Testphasen angewendet werden. Sie eignen sich ebenfalls um automatische Tests zu
generieren. (Ger08)
Blackbox-Tests sind ausschließlich dynamische Tests. Bei dynamischen Tests wird die
Software gestartet und an der lauffähigen Version Untersuchungen und Analysen durchgeführt. Ausgabewerte und Zustände werden protokolliert. Diese Art von Tests verläuft
stichprobenartig, da niemals oder nur sehr selten alle auftretenden Fälle getestet werden können (Ger08). Die Software wird mit wahrscheinlichen sowie unwahrscheinlichen
Testdaten in einer realen Umgebung ausgeführt (Möl08). Zu Blackbox-Tests gehören:
• Äquivalenzklassenbildung
Äquivalenzklassen sind gruppierte Testdaten von Eingabewerten für eine Methode.
Gebildet werden diese Klassen, indem der Wertebereich eines Eingabeparameters
23
3 Grundlegendes zu Softwaretests
einer Methode unterteilt und in Intervalle oder Untermengen zerlegt wird. Diese Untermengen werden so gewählt, dass alle Werte einer Äquivalenzklasse die
gleiche Reaktion der Testmethode zur Folge haben. Zum Testen ist also ein Repräsentant einer Äquivalenzklasse ausreichend. (TS04)
Als Negativtest werden Äquivalenzklassen gebildet, die ungültige Werte enthalten.
Für Methoden, die mehrere Parameter enthalten, sollten viele Permutationen als
Testfall angewendet werden. Da so die Anzahl der Tests sehr schnell sehr groß
werden kann, gibt es Verfahren, um die Anzahl der Testfälle zu reduzieren (SL02):
– Priorisierung und Auswahl der Testfälle nach Benutzungshäufigkeit
– Bevorzugung der Testfälle, die Grenzwerte enthalten
– Keine Kombinationen von ungültigen Repräsentanten verwenden
Durch eine Minimierung der Testfälle entsteht eine qualifizierte Auswahl an sinnvollen Testfällen. Bei AutoTest.Net entspricht eine Äquivalenzklasse genau einem
Datentyp, z.B. dem Datentyp System.Double. Die enthaltenden Werte der Äquivalenzklasse stellen die Testdaten dar.
• Grenzwerttest
Ein Grenzwerttest basiert auf der Bildung von Äquivalenzklassen. Hierbei werden
Testfälle gebildet, die hauptsächlich Grenzwerte der erwarteten Eingabeparameter beinhalten. Weiterhin werden Testfälle erzeugt, die eine kleinste Einheit größer
oder kleiner sind als der Grenzfall. Wenn z.B. eine Methode bis zum ganzzahligen
Eingabewert 1.000 einen anderen Rückgabewert liefert als für Eingabewerte gleich
oder größer 1.000, so werden als Testfälle neben 1.000 auch 999 und 1.001 ermittelt. Somit wird sogenannten one-off-Fehlern vorgebeugt, die häufig bei nullbasierten Indizes oder auch z.B. durch ein > anstatt ein >= entstehen können. (TS04)
3.4 Whitebox-Tests
Unter Whitebox-Tests, Transparentbox-Tests, Glassbox-Tests oder codebasierten Test,
versteht man Tests, die auf der Programmstruktur oder Programmlogik basieren. Dem
Tester ist also ein Einblick in den Quellcode der Software möglich, um die Testfälle zu
erstellen und die Software zu testen und das Verhalten zu analysieren. Somit kann mit
Whitebox-Tests tiefer in die Strukturen des Programmes vorgedrungen werden. Vollständige Whitebox-Tests sind aufgrund des hohen Aufwands nur sehr selten bzw. nur bei sehr
kleinen Programmen durchführbar. (Möl08)
24
3 Grundlegendes zu Softwaretests
3.4.1 Statische Analyse
Die Analyse gehört zu den statischen Tests. Dies sind Tests, bei denen die Software
nicht ausgeführt, sondern nur statisch betrachtet und untersucht wird. Als Grundlage für
statische Tests dient der Quellcode des Programmes (ebd.). Im Allgemeinen werden bei
statischen Tests keine Testdaten benötigt. Die folgenden Verfahren analysieren statisch
den Quellcode (TS04):
• Review
• Code-Inspektion
• Walkthrough
Da eine solche Analyse meist von mehreren Menschen durchgeführt wird, wird sie
auch Gruppenprüfung genannt. Ziele einer solchen gemeinsamen Prüfung sind das Aufspüren von Fehlern, die Optimierung und die Überprüfung der Einhaltung von Standards
und Richtlinien. Den Nutzen einer solchen Gruppenprüfung zeigt ein Erfahrungswert
der Firma Siemens, bei dem 491 Mängel bei insgesamt 13 Gruppenprüfungen entdeckt
wurden. (Ger08)
Eine solche Analyse kann automatisch und programmunterstützt ablaufen. Als Beispiel dient hier das Analyse-Tool FxCop (vgl. Abschnitt 4.2.1 auf Seite 42), welches den
Quellcode anhand von Microsoft-Richtlinien oder selbst definierten / programmierten
Regeln prüft und Fehler im Code moniert.
3.4.2 Strukturelle Analyse
Kontrollflussanalyse dient der Analyse der Kontrollstrukturen der Software. Kontrollstrukturen sind Kontroll- oder Steueranweisungen, die auch als Schlüsselwörter bekannt
sind. Dazu gehören in C# Anweisungen wie if, else, switch, for, foreach,
while oder do. Unterbrechende oder weiterführende Anweisungen wie break, return
oder continue spiegeln sich ebenfalls in einem solchen Graph wieder. Für die Analyse
dieser Schleifen- und Verzweigungsstrukturen ist ein vorzugsweise automatisch generierter Kontrollflussgraph notwendig. Anhand eines solchen Graphen können Anomalien in
der Programmstruktur entdeckt werden. Mögliche Anomalien sind folgende (Ger08):
• Sprünge in oder aus einen Schleifenkörper
• Methoden mit mehreren Ausgängen
• Unerreichbarer Code
25
3 Grundlegendes zu Softwaretests
Solche Strukturfehler können harmlose oder überhaupt keine Auswirkungen haben,
ebenso sind hier aber viele potentielle Fehler möglich. Das Prüfen der Strukturen einer Software wird heutzutage allerdings von vielen Compilern unterstützt, welcher unerreichbaren Code oder nicht initialisierte Variablen automatisch während des Kompiliervorgangs mit einer Warnung anzeigt.
3.4.3 Überdeckungstests
Hauptsächlich handelt es sich bei Whitebox-Tests um kontrollflussorientierte Tests. In
einem Kontrollflussgraphen werden Anweisungen oder Anweisungsblöcke als Knoten,
und die Richtung eines Kontrollflusses als gerichtete Kanten zwischen Knoten dargestellt. Ein Zweig ist eine Einheit aus einer Kante und den dadurch verbundenen Knoten. Ein Pfad ist eine Sequenz von Knoten und Kanten, die durch einen Start- und Endknoten abgeschlossen sind. Verzweigungen stellen Steueranweisungen wie if oder for
dar. Bei kontrollflussbezogenen Verfahren wird das Prinzip der Überdeckung von Kontrollstrukturen verfolgt, indem Kontrollstrukturen gezielt durch geeignete Gestaltung von
Testfällen durchlaufen werden. Am Ende wird ein angestrebter und ein erreichter Überdeckungsgrad in % angegeben. Solche Tests werden Überdeckungstests genannt. Im Folgenden wird auf die einzelnen Stufen der Überdeckungstests eingegangen. (Möl08)
• Anweisungsüberdeckung C0 (Statement Coverage)
Bei einer Anweisungsüberdeckung, welche die schwächste Art darstellt, werden
alle Anweisungen mindestens einmal ausgeführt. Sie basiert auf dem Kontrollflussgraphen, der üblicherweise vom Quellcode abgeleitet wird. Bei der Anweisungsüberdeckung werden keine leeren Zweige getestet. Dadurch können auch eventuell
fehlende (nicht implementierte) Anweisungen nicht entdeckt werden. Die Überdeckungsrate ermittelt sich wie folgt: (TS04)
Anweisungsüberdeckung =
Anzahl ausge f ührte Anweisungen
Gesamtzahl der Anweisungen
• Zweigüberdeckung C1 (Branch Coverage)
Bei der Zweigüberdeckung werden alle Zweige („Wegabschnitte“) im Kontrollfluss mindestens einmal durchlaufen. Bei der Zweigüberdeckung wird nicht berücksichtigt, wie oft ein Zweig durchlaufen wird. Mit dieser Methode könnten feh-
26
3 Grundlegendes zu Softwaretests
lende (nicht implementierte) Anweisungen entdeckt werden. Die Überdeckungsrate wird ebenfalls in % angegeben und ermittelt sich wie folgt: (ebd.)
Zweigüberdeckung =
Anzahl ausge f ührte Zweige
Gesamtzahl der Zweige
Bei der Zweigüberdeckung werden im Allgemeinen mehr Testfälle benötigt, da
auch leere Zweige berücksichtigt werden. (ebd.)
• Bedingungsüberdeckung C2 (Condition Testing)
Bei den bisherigen Überdeckungsmethoden wurden immer nur ganze Bedingungen betrachtet. In der Realität bestehen Bedingungen aber oft aus komplexen, mit
booleschen Operatoren vermengten Ausdrücken. Um diese in atomare Teilbedingungen aufzugliedern, wird die Bedingungsüberdeckung eingesetzt. Es werden alle Konstellationen der atomaren Bedingungen genutzt, die insgesamt für jede Teilbedingung einmal false und einmal true ergibt. (ebd.)
• Mehrfach-Bedingungsüberdeckung C3 (Branch Condition Combination Testing)
Bei der Mehrfach-Bedingungsüberdeckung ergeben sich alle Testfälle, um alle Konstellationen der atomaren Teilbedingungen abzudecken. Deswegen ist die
Mehrfach-Bedingungsüberdeckung ein stärkeres Kriterium als die Bedingungsüberdeckung. Nachteil ist allerdings, dass die Anzahl der Testfälle exponentiell
steigt (2n mit n atomaren Teilbedingungen). (ebd.)
• Pfadüberdeckung C4 (Path Coverage)
Die Pfadüberdeckung ist die stärkste aller Formen und abgesehen von sehr kleinen
Programmen nur theoretisch möglich. Hierbei werden alle Pfade komplett durchlaufen. Für Schleifen gilt folgende Forderung (ebd.):
– enthaltene Schleifen nicht durchlaufen
– enthaltene Schleifen nicht oft durchlaufen
– enthaltene Schleifen oft durchlaufen
Das Ziel von dynamischen Whitebox-Tests ist, eine möglichst hohe Überdeckungsrate
zu erreichen, bei der so viele Anweisungen, Zweige oder Pfade wie möglich durchlaufen
wurden. Nachteile des Verfahrens sind, dass keine nicht implementierten Anforderungen
erkannt werden, da sich die Tests am Quellcode orientieren und nicht an der Spezifikation
der Software. Weiterhin können Fehler, die von bestimmten Datenkonstellationen abhängig sind, nur sehr schwer entdeckt werden (Möl08). Als Ergänzung hierfür eignen sich
statische Analysen (vgl. Abschnitt 3.4.1 auf Seite 25), um nicht implementierte Features
zu entdecken. (TS04)
27
3 Grundlegendes zu Softwaretests
3.5 Testverfahren
Bei Testverfahren gibt es die unterschiedlichsten Vorgehensweisen und Kombinationen von verschiedenen Vorgehensweisen. Zu Beginn sollen Top-Down- und Bottom-UpTests erwähnt werden. Bei Top-Down-Tests wird die Software von oben herab getestet.
Das heißt, die Tests beginnen an der äußersten Schicht einer Software, was im Allgemeinen die graphische Benutzeroberfläche darstellt. Hierfür eignen sich GUI-Tests (vgl.
Abschnitt 3.6.2 auf Seite 33). Bei Bottom-Up-Tests beginnen die Tests unten in der Quellcodestruktur, womit Methoden und Klassen gemeint sind. Hierbei eignen sich Unit-Tests
(vgl. Abschnitt 3.6.1 auf Seite 32). (vV08)
Es gibt viele Modelle, um Software zu entwickeln. Am Anfang war das Testen kein
Bestandteil eines solchen Modells. Zu Beginn wurde das Wasserfallmodell verwendet.
Nachdem aber recht schnell bemerkt wurde, dass dieses Modell für Software ungeeignet
ist, da sich keine Änderungen auf vorhergehende Phasen im Modell vornehmen lassen,
wurde das Wasserfallmodell etwas angepasst. Allerdings reichte auch das nicht. Es entstand das V-Modell, welches sich sehr gut für Software eignete. Erstmals wurden auch
Tests direkt in dem Modell ersichtlich (siehe Abbildung 3.1).
Abbildung 3.1: Allgemeines V-Modell (Spi08)
28
3 Grundlegendes zu Softwaretests
Die enthaltenden Tests verdeutlichen, welchen Umfang oder welche Bereiche der Software zu welchen Zeitpunkten getestet werden sollten. Der Anforderungsdefinition steht
der allumfassende Systemtest gegenüber, dem Systementwurf steht der Integrationstest
gegenüber und der Spezifikation der Komponenten steht der Komponententest gegenüber. Wichtig ist hierbei zu erwähnen, dass die jeweiligen Spezifikationen die Grundlage der dazugehörenden Tests bilden und nicht die entwickelten Komponenten oder das
System an sich. Der größte Nachteil allerdings ist, dass jegliche Testaktivitäten zu spät
beginnen. Aus diesem Grund wurde das W-Modell entwickelt, um die Nachteile des VModells zu beheben (siehe Abbildung 3.2).
Abbildung 3.2: Allgemeines W-Modell (Spi08)
Das W-Modell integriert die Tests und auch die Vorbereitung komplett in das gesamte
Projekt. Verschiedene Tests sind zu unterschiedlichen Zeitpunkten möglich, nötig oder
sinnvoll, was wiederum bedeutet, dass nicht jeder Test zu jedem Zeitpunkt möglich, nötig
oder sinnvoll ist. Die im W-Modell genannten Tests werden im Folgenden kurz erläutert.
29
3 Grundlegendes zu Softwaretests
3.5.1 Komponententest
Von der Hierarchie her betrachtet finden Komponententest ganz unten statt. Hier werden einzelne Klassen oder entwickelte Module während der Entwicklung getestet. Klassische Tests hierfür sind Unit-Tests (vgl. Abschnitt 3.6.1 auf Seite 32). Komponenten
werden individuell und unabhängig voneinander getestet. Diese Tests sind relativ einfach, allerdings entstehen im Laufe einer Entwicklung sehr viele Unit-Tests (Ger08).
Fuzzing (vgl. Abschnitt 3.6.3 auf Seite 33) kann sich ebenfalls sehr gut eignen, um Fehler
in Komponenten aufzuspüren.
3.5.2 Integrationstest
Diese Art von Tests findet in der Testhierarchie etwas später statt, wenn bestimmte,
klar definierte Teile eines Systems fertigentwickelt sind. Bei den Teilsystemen werden
Schnittstellen nach außen bzw. zu anderen Teilsystemen sowie das Zusammenspiel von
fertigentwickelten Komponenten getestet. Hierbei sind diese Komponenten nicht mehr
unabhängig voneinander (ebd.). Auch hierfür können Unit-Tests (vgl. Abschnitt 3.6.1
auf Seite 32) benutzt werden, allerdings werden diese nicht mehr auf die elementaren
Bestandteile der Software angewendet, sondern auf Teilsysteme, die andere Teilsysteme
benutzen und somit ein Zusammenspiel im Vordergrund steht. Sollten diese Teilsysteme
eine graphische Benutzeroberfläche haben, so eignen sich hier auch schon GUI-Tests
(vgl. Abschnitt 3.6.2 auf Seite 33).
3.5.3 Systemtest
Ein allumfassender Systemtest findet, wie im Modell ersichtlich, am Ende einer Entwicklung statt und befindet sich somit ganz oben in der Hierarchie. Diese Art von Tests
kann als Testphase definiert werden, bei der das gesamte System gegen die Spezifikationen getestet wird. Es kann ein funktionaler und ein nicht funktionaler Systemtest durchgeführt werden. Ein funktionaler Systemtest prüft die Software auf Merkmale wie Korrektheit und Vollständigkeit. Ein nicht funktionaler Systemtest testet Merkmale wie die
Benutzbarkeit oder die Erlernbarkeit einer Software. Neben den Funktionstests (vgl. Abschnitt 3.3 auf Seite 23) werden weitere, umfassende Tests durchgeführt, welche u.a. im
Folgenden erläutert werden (Bal08):
• Leistungstest
Zu dieser Gruppe gehören u.a. Load-Tests, Stress-Tests, Zeittests oder Massentests. Diese Tests analysieren das Verhalten der Software z.B. beim Laden (Load-
30
3 Grundlegendes zu Softwaretests
Test) oder unter einer permanenten Ausführung (Stresstest), was ggf. eine Ausnahmesituation darstellen kann. Somit wird versucht herauszufinden, inwieweit sich
die Performance der Software verhält. So können Fehler gefunden werden, die
bei der Initialisierung, bei einer permanenten Dauerausführung oder sogar einer
Überlastung des Systems entstehen. Ein korrektes Systemverhalten bei bestimmten Speicher- und CPU-Anforderungen soll sichergestellt werden. Auch verteilte
Anwendungen und Multi-User-Tests, in dem viele Anwender auf ein System zugreifen, simuliert oder real, fallen unter die Gruppe der Leistungstests. Weiterhin
gehört ein simulierter Ausfall von Hard- oder Softwarekomponenten, mit denen
das System im Normalbetrieb kommuniziert (z.B. Sensoren), sowie das Eintreffen
von ungewöhnlichen oder gar widersprüchlichen Daten, z.B. über eine Schnittstelle, ebenfalls zu den Leistungstests. Massentests prüfen die Datenmenge, die vom
System verarbeitet werden kann. Zeittests prüfen die Einhaltung von zeitlichen
Restriktionen.
• Sicherheitstests
Hierbei werden allgemein gültige oder geforderte Sicherheitsmaßnahmen geprüft.
Es geht hauptsächlich um sicherheitsrelevante Daten oder Passwörter. Es wird u.a.
geprüft, ob Passwörter unsichtbar erfasst und verschlüsselt gespeichert werden, ob
der Benutzer sein Kennwort ändern kann, ob die vorgeschriebene Mindestlänge
oder enthaltene Zeichen bei Passwörtern eingehalten oder ob Zugriffsrechte- und
verbote geprüft, eingehalten und gemeldet werden.
• Interoperabilitätstests
Interoperabilitätstests testen die Funktionalität bei der Zusammenarbeit voneinander unabhängiger Komponenten. Da Software heutzutage im Allgemeinen mit anderer Software oder anderen Softwareteilen zusammenarbeitet, müssen diese Kommunikationswege ebenso getestet werden. Ein simples Beispiel ist diese Kommunikation (Hineinschreiben und Auslesen) zur Zwischenablage.
• Installationstests
Installationstests testen die Softwareinstallationsroutinen ggf. in verschiedenen
Systemumgebungen (z.B. verschiedene Hardware oder unterschiedliche Betriebssystemversionen). Weiterhin wird auch getestet, ob das System mit der im Handbuch beschriebenen Vorgehensweise korrekt installiert und in Betrieb genommen
werden kann.
Hier bieten sich ebenfalls GUI-Tests an (vgl. Abschnitt 3.6.2 auf Seite 33). Unit-Tests
(vgl. Abschnitt 3.6.1 auf der nächsten Seite) werden auf dieser Ebene eher selten ange-
31
3 Grundlegendes zu Softwaretests
wendet, da die Zusammenspiele von Teilsystemen im Gesamtsystem mit GUI-Tests oft
besser getestet werden können. All diese Tests werden ohne den Auftraggeber durchgeführt.
3.5.4 Abnahmetest
Ähnlich einem Systemtest wird der Abnahmetest oder auch User Acceptance Test
(UAT) an der fertigen Software durchgeführt. „Fertig“ ist hier aus Kundensicht zu sehen und Voraussetzung für eine Rechnungsstellung. Der Unterschied zum Systemtest ist,
dass hierbei der Auftraggeber mithilft und z.B. echte Kundendaten für die Tests genutzt
werden, um die Software unter realen Einsatzbedingungen zu testen. Das Benutzerhandbuch wird nun gänzlich in die Tests mit einbezogen. Auch hierbei bieten sich GUI-Tests
an (vgl. Abschnitt 3.6.2 auf der nächsten Seite). Selbst nach der Abnahme befinden sich
noch Restfehler im Softwaresystem, sowohl bekannte als auch unbekannte. (Bal08)
3.5.5 Regressionstests
Regressionstests sind alle Tests, die jederzeit wiederholt werden können und somit
Regressionsfähigkeit besitzen, sofern sich das System nicht grundlegend ändert und Tests
angepasst werden müssen. Unit-Tests (vgl. Abschnitt 3.6.1) oder auch GUI-Tests (vgl.
Abschnitt 3.6.2 auf der nächsten Seite) sind im Allgemeinen solche Regressionstests.
Sie haben das Ziel, bei Änderungen unerwünschte Seiteneffekte auf unmodifizierte Teile
der Software zu erkennen (Regressions-Fehler) und die Sicherstellung der Erfüllung der
Anforderungen des modifizierten Systems. (Ger08)
3.6 Konkrete Testverfahren
Bisher wurden die Tests kategorisiert bzw. verschiedene Testarten vorgestellt. Es sollen nun einige der gängigsten Testmethoden konkret genannt werden. Die hier erwähnten
Testmethoden sind nur ein geringer Teil vieler möglicher Tests. Viele Testmethoden werden von Test-Tools unterstützt. Teilweise laufen einige dieser Tests parallel ab.
3.6.1 Unit-Tests
Unit-Tests (dt. Komponententests) sind weit verbreitete Tests, mit denen Module oder
Komponenten von Software getestet werden können. Sie werden im Quellcode vom Pro-
32
3 Grundlegendes zu Softwaretests
grammierer des Testobjekts verfasst und rufen die zu testenden Methoden mit bestimmten
Parametern auf. Nach dem Aufruf wird der Rückgabewert mit einem erwarteten Wert verglichen, der vorher mit Hilfe des Testorakel oder des Quellcodes ermittelt wird. In vielen
Quellen werden diese Tests eher den Whitebox-Tests zugeordnet (Sil08). Einige Quellen sprechen von einer Schnittmenge und ordnen so Unit-Tests teilweise den WhiteboxTests und teilweise den Blackbox-Tests zu (See08). Wieder andere ordnen sie ausschließlich den Blackbox-Tests zu, da Unit-Tests lediglich den Rückgabewert vergleichen und
nicht die internen Strukturen einer Methode analysieren (Dai08). Aufgrund dieser Widersprüchlichkeiten wird festgelegt, dass es sich bei Unit-Tests um Blackbox-Tests handelt,
sofern die Testmethoden und auch die Testfälle ohne einen Einblick in den Quellcode des
Testobjekts erstellt wurden oder erstellt werden könnten. Um Whitebox-Tests handelt es
sich, wenn die Testfälle anhand des Quellcodes des Testobjekts abgeleitet wurden. UnitTests können schon vor dem Implementieren der zu testenden Methoden erstellt werden.
Dieses Programmiermodell wird Test Driven Development (TDD) genannt.
3.6.2 GUI-Test
Bei GUI-Tests, UI-Test (User Interface) oder Top-Down-Test ist der Testtreiber in der
Lage, eigenständig Benutzereingaben zu simulieren. Diese Tests sind Blackbox-Tests, da
lediglich die GUI des Programmes als Schnittstelle dient. Die korrekte Zusammenarbeit
wird mit simulierten Benutzereingaben und somit realen Testeingaben getestet. Wenn
die erwarteten Ergebnisse entstehen, dann ist davon auszugehen, dass alle beteiligten
Module und Klassen korrekt arbeiten. Als Grundlage für GUI-Tests dienen sogenannte Handle, welche lediglich als eindeutige Nummern verstanden werden können. Diese
Handle identifizieren alle Steuerelemente wie Buttons, Labels oder auch Textboxen. Alle
Steuerelemente sind durch ein eindeutiges Handle bestimmt und können über dieses angesprochen werden. So ist es z.B. möglich, an Buttons einen Klick zu senden, als wenn
der Benutzer ihn mit der Maus angeklickt hätte. Weiterhin ist es möglich, Text in eine
Textbox zu schreiben und auf diese Weise Benutzereingaben zu simulieren. Das Auslesen ist ebenfalls über das Handle möglich.
3.6.3 Fuzz-Test
Fuzz-Tests wurden 1989 von Professor Barton Miller an der Universität Wisconsin
entwickelt. Miller bemerkte im Herbst 1988, dass bestimmte, zufällig entstehende Geräusche auf einer Telefonleitung diese zum Absturz bringen konnten. Dieses Phänomen
33
3 Grundlegendes zu Softwaretests
wollte er damals im Rahmen eines Projektes untersuchen. Er prägte den Begriff „Fuzzing“. (Mil08)
Die Fuzz-Tests wurden ursprünglich für Unix-Programme entwickelt. Beim Fuzz-Test
werden automatisiert zufällige Zeichenketten in verschiedenen Längen generiert und diese als Eingabeparameter für das Programm benutzt. Nachdem das Programm mit diesen
Daten gelaufen ist, wird geprüft, ob es korrekt ausgeführt wurde oder ob es sogar abgestürzt ist. Aus einer Veröffentlichung von Miller und seinen beiden Studenten Fredriksen
und So 1990 geht hervor, dass fast ein Drittel aller damals mit dieser Methode getesteten Programme abgestürzt sind, darunter auch renommierte Programme wie vi, telnet
und emacs (MFS90). In Tabelle 3.2 auf der nächsten Seite wird eine Auflistung aller
Programme gezeigt, die beim damaligen Test abgestürzt sind. Diese Vielzahl von abgestürzten Programmen zeigt, wie wichtig und effektiv diese Testmethode sein kann.
Allerdings reichen nur zufällig generierte Daten nicht aus, da viele Programme bei
Optionen, die sie nicht unterstützen, gar nicht erst den eigentlichen Programmcode ausführen und somit nicht mit den zufällig generierten Werten arbeiten. Diese Zufallswerte
müssen deswegen einer gewissen Logik entsprechen, damit die Tests wirklich sinnvoll
durchgeführt werden können. Eine solche Logik ist auch bei heutiger Software nötig,
obwohl diese nicht unbedingt auf Startparameter angewiesen ist, wie die früheren Konsolenanwendungen.
Fuzzing wird u.a. von Hackern benutzt und diente dazu, Sicherheitslücken aufzudecken und diese destruktiv zu nutzen. Fuzzing an sich ist nicht auf Software begrenzt. Es
wurde schon erfolgreich in mehreren Anwendungsgebieten angewendet, um die dortigen Systeme zu testen. Dazu gehören z.B. Netzwerkprotokolle, die mit Fuzz-Tests auf
ihre Sicherheit hin untersucht und getestet werden konnten. Es werden, wie beim Fuzzing üblich, diverse zufällig erzeugte Pakete verschickt und geprüft, ob sich das Netzwerk mit diesen ungültigen Paketen richtig verhält. Ähnlich arbeiten auch WEB-Fuzzer,
die HTML-Seiten mit zufällig generierten und somit ungültigen HTML-Tags erzeugen.
Die Browser müssen in der Lage sein, diese ungültigen Seiten zu behandeln und dürfen
auf keinen Fall in einen ungültigen Zustand verfallen, was einer Sicherheitslücke gleich
käme. Auf diese Art wurden schon einige Fehler gefunden, u.a. auch in den renommiertesten Browsern wie Microsoft Internet Explorer, Mozilla Firefox oder Opera (Pol08).
Weiterhin gibt es auch sogenannte file fuzzers, die ungültige Dateien erzeugen und mit
einer bestimmten Zielanwendung öffnen.
Der erste Schritt beim Fuzzing ist das Erzeugen der Eingabedaten. Dabei wird unterschieden zwischen dem Generieren von zufälligen Daten (generation Fuzzing) oder
34
3 Grundlegendes zu Softwaretests
Utility
array/ NCRC
pointer
adb
as
bc
cb
ccom
col
csh
dc
deqn
deroff
diction
ditroff
emacs
eqn
f77
ftp
indent
join
lex
look
m4
make
nroff
plot
prolog
ptx
refer
spell
style
telnet
tsort
ul
uniq
units
vgrind
vi
vshx
vxad
input
subinteraction bad error
signed
race
no source unknown
functions processes
effects
handler characters condition
code
v
x
vhxad
d
vshxad
vshra
vshra
x
s
vshad
vhd
vs
vsh
shx
v
vshad
vshd
s
vshxad
vshxd
x
h
x
sh
vsh
shxd
vshd
vshxad
vhd
vshad
vshxad
vshd
vshxad
vshxad
v
v
vh
Tabelle 3.2: Liste der beim damaligen Test abgestürzten Programme, kategorisiert nach Fehlerursache. (Die Buchstaben geben das System an, auf dem der Absturz hervorgerufen
wurde (siehe Tabelle 3.3)) (MFS90)
Buchstabe
v
s
h
x
r
a
d
Hersteller/Modell
DEC VAXstation 3200
Sun 4/110
HP Bobcat 9000/320
HP Bobcat 9000/330
Citrus 80386
IBM RT/PC
IBM PS/2-80
Sequent Symmetry
Prozessor
CVAX
SPARC
68020
68030
i386
ROMP
i386
i386
Kernel
4.3 BSD + NFS (from Wisconsin)
SunOS 3.2 & SunOS 4.0 with NFS
4.3BSD + NFS (from Wisconsin),
with Sys V shared-memory
SCO Xenix System V Rel. 2.3.1
AOS UNIX
AIX 1.1 UNIX
Dynix 3.0
Tabelle 3.3: Liste der getesteten Systeme (MFS90)
35
3 Grundlegendes zu Softwaretests
dem Modifizieren vorhandener Daten (mutation Fuzzing). Beim Modifizieren vorhandener Daten liegen meist schon gültige Daten vor, die dann in einem gewissen Grad verändert werden. Zum Beispiel könnte ein gültiges XML-Dokument dahingehend manipuliert
werden, dass die XML Struktur erhalten bleibt und nur die von den Tags eingeschlossenen Daten modifiziert werden. Somit ist sichergestellt, dass diese Daten vom Programm
trotzdem als gültig erkannt und verarbeitet werden und nicht von vornherein durch den
XML-Parser verworfen werden. (Wei08)
Im zweiten Schritt müssen die erzeugten Daten zur Zielsoftware gebracht und dort
benutzt werden. Dies geschieht durch ein Tool, welches die Zielsoftware startet und die
generierten oder modifizierten Daten benutzt.
Im letzten und wichtigsten Schritt muss das Verhalten der Software beobachtet und
protokolliert werden. Zu den drei wichtigsten Auswirkungen gehören Abstürze, Speicherlecks und Prozessorauslastungslecks. Abstürze sind mit die schlimmsten Fehler, die
in einer Software auftreten können. Speicherlecks entstehen, wenn mehr Speicher reserviert als freigegeben wird. Prozessorauslastungslecks entstehen z.B., wenn sich eine
Funktion in einer Endlosschleife verfängt. (Wei08)
Der wohl größte Nachteil dieser Technik ist, dass die eigentliche logische Funktion der
Software nicht testbar ist. Mit den Eingabeparametern einer Methode kann kein logischer
Schluss auf die zu erwartenden Ausgabewerte gezogen werden. Selbst wenn die Methode
nicht abstürzt, steht nicht fest, ob sie fehlerfrei ausgeführt wurde.
Ein sehr großer Vorteil gegenüber allen anderen Testmethoden ist die hohe Anzahl
von Testfällen, die generiert werden kann, ohne dass es den Programmierer vorher viel
Zeit kostet, aufwendige Tests zu entwickeln. Durch das Erstellen der Testdaten kann der
Test vollautomatisch durchgeführt werden und erfordert keine zeitintensive Vorbereitung
oder das manuelle Festlegen von Sollwerten. Deshalb ist hierbei eine nicht unerhebliche
Fehlerquelle ausgeschlossen worden; nämlich dass auch der entwickelte Test fehlerhaft
sein kann.
3.6.4 Datengesteuerte Tests
Bei datengesteuerten Tests (engl. data-driven tests) wird eine Anwendung mit simulierten Daten getestet. Daten, wie z.B. Namen, Adressen o.ä., werden generiert und in
einer Datenquelle, z.B. einer Datenbank oder einer Datei, abgelegt. Anwendungen, z.B.
mit einer graphischen Benutzeroberfläche, werden dann mit Hilfe dieser Daten getestet.
Der Testtreiber setzt mittels eines GUI-Tests (vgl. Abschnitt 3.6.2 auf Seite 33) diese Da-
36
3 Grundlegendes zu Softwaretests
ten in die entsprechenden Felder der Maske ein. Eine andere Art von Test wäre, wenn
diese Daten mittels eines Fuzz-Tests (vgl. Abschnitt 3.6.3 auf Seite 33) als Parameter für
eine Methode genutzt werden. Denkbar wäre eine Methode, die Daten wie z.B. einen
Kunden mit Name und Adresse in einer Tabelle einer Datenbank speichert. Der Testtreiber gibt diese Daten vor und prüft nach dem Test, ob diese Daten korrekt wie erwartet in
der Tabelle eingetragen wurden.
3.7 Zusammenfassung
Es existieren sehr viele unterschiedliche Arten und Verfahren von Tests. Ebenso existieren viele Mischformen oder Überschneidungen von Testverfahren. Alle hier vorgestellten Verfahren sind automatisierbar. Je nach Grad der Automatisierung werden viele
sonst aufwendige Aufgaben übernommen und automatisch durchgeführt. Der wohl größte Vorteil ist dabei, dass automatisierte Tests jederzeit konsistent sind. Der Tester, also
meist eine Software, wird niemals müde oder unkonzentriert. Weiterhin werden menschliche Fehler wie Tippfehler vermieden (unter der Annahme, dass keine solchen Fehler
bei der Bereitstellung der Testdaten vorliegen).
Aus dieser Arbeit, vornehmlich diesem Kapitel, resultiert die Erkenntnis, dass Testen ein sehr wichtiger Bestandteil des Softwareentwicklungsprozesses ist. Weiterhin gilt,
dass, außer bei sehr kleinen und unkomplizierten Programmen, sehr selten alle Fehler
gefunden werden können. Selbst wenn alle hier genannten Testverfahren intensiv, gewissenhaft und erfolgreich durchgeführt wurden, so kann die Software immer noch Fehler
enthalten. Jeder Fehler, der während des Testens gefunden wird, ist ein kleiner Erfolg.
Tritt dieser Fehler erst später beim Kunden auf, so entsteht meist ein wesentlich höherer
finanzieller Schaden.
37
Vorhandene Testsysteme
[4]
4.1
„Jedes Denken wird dadurch gefördert, dass es in einem bestimmten Augenblick sich nicht mehr mit Erdachtem abgeben darf, sondern
durch die Wirklichkeit hindurch muss.“
Albert Einstein, dt.-amerik. Physiker
TestFrameworks
4.2
Statische
Codeanalyse
Auf dem heutigen Softwaremarkt gibt es eine Fülle unterschiedlichster Testsysteme.
4.3
Das Spektrum reicht von kostenlos bis „unbezahlbar“. Einige dieser Testsysteme sollen
GUI-Tests
hier betrachtet und Vor- und Nachteile analysiert werden. Zu den bekanntesten Testsys-
4.4
temen gehören einerseits Test-Frameworks und andererseits GUI-Tests. Zudem gibt es
Zusammen-
noch statische Codeanalysen.
fassung
4.1 Test-Frameworks
In diesem Kapitel werden die Test-Frameworks etwas genauer betrachtet. Sie dienen
als Unterstützung bei der Durchführung der Tests und sollen bei der Auswertung der
Tests helfen, entstandene Fehler zu protokollieren. Zudem sollen sie viele lästige Schritte
beim Testen abnehmen (SGA07).
4.1.1 NUnit / JUnit
NUnit und JUnit sind Test-Frameworks, die Module oder Komponenten einer Software auf ihre Korrektheit hin prüfen können. Dafür erstellt der Programmierer zu jeder
seiner programmierten Klassen eine Testklasse, welche dann die eigentliche Quellklasse
testet. NUnit war bisher eine externe Anwendung, die alle in einer Assembly enthaltenden
Testmethoden ausführt und Testergebnisse anzeigt (siehe Abbildung 4.1 auf der nächsten
Seite).
38
4 Vorhandene Testsysteme
Abbildung 4.1: NUnit - Erfolgreicher Gesamttest
Eine solche Testklasse wird mit gesonderten Attributen versehen, genauso wie die
enthaltenen Testmethoden. Idealerweise befinden sich diese Klassen in einem separaten
Projekt, da sich sonst Quellcode und Testcode zu stark vermischen. Ob die Klasse korrekt
funktioniert, erfährt der Programmierer direkt nach dem Ausführen des Unit-Tests. So
können Fehler schon frühzeitig erkannt und direkt behoben werden. Auch bei späteren
Änderungen oder weiteren Implementierungen ist zu jedem Zeitpunkt sichergestellt, dass
die Methoden weiterhin richtig arbeiten und sich die aktuellen Änderungen nicht negativ
auf schon bestehende Klassen auswirken.
Ab Microsoft Visual Studio 2005 Team Edition for Developer / Tester und Microsoft
Visual Studio 2008 wird die Erstellung von NUnit-Testklassen automatisch durch einen
integrierten Assistenten unterstützt. Der Assistent eröffnet dem Programmierer die Möglichkeit frei zu entscheiden, welche Klassen, Methoden oder auch welche Properties getestet werden sollen. Der Assistent erzeugt ein Grundgerüst, welches auf die Quellklasse
zugeschnitten ist. Eine häufig diskutierte Frage war, ob private Methoden getestet werden
sollten. Dies war bisher mit NUnit nur sehr schwer möglich. Dieser Nachteil wurde durch
die Integration ausgeräumt. Der Assistent erzeugt bei einer privaten Methode eine Klasse,
welche die Methode kapselt. Somit kann über einen Umweg, den der Programmierer allerdings gar nicht bemerkt, auch diese private Methode getestet werden. Die eigentlichen
39
4 Vorhandene Testsysteme
Inhalte dieser Tests müssen natürlich vom Programmierer selbst implementiert werden,
da der Assistent von sich aus keine Testwerte festlegt. Nach Fertigstellung einer Klasse kann einfach per Rechtsklick ein komplettes NUnit-Testprojekt erstellt werden (siehe
Abbildung 4.2).
Abbildung 4.2: Erzeugen eines NUnit-Testprojektes mit Hilfe des Assistenten
Mit steigender Komplexität der Quellsoftware steigt ebenfalls die Komplexität der
Tests sehr stark. Ein solcher Test kann sehr umfangreiche Ausmaße annehmen. Die Praxis zeigt, dass es oft um ein Vielfaches länger dauert, einen Test für eine Quellklasse
zu schreiben, als die Quellklasse selbst. Genauso wie die Quellklasse Fehler enthalten
kann, kann auch die Testklasse fehlerhaft sein. Somit könnte in einer ungünstigen Konstellation eine fehlerhafte Quellklasse, die mit einer fehlerhaften Testklasse getestet wird,
wieder ein erfolgreiches Ergebnis liefern, obwohl die Klasse nicht ordnungsgemäß arbei-
40
4 Vorhandene Testsysteme
tet. Dieser Fall ist zwar äußerst selten, soll aber im Rahmen dieser Arbeit nicht gänzlich
vernachlässigt werden.
Ein weiterer Nachteil ist, dass sich der Test-Code zu sehr mit dem eigentlichen Quellcode vermischt. Jede Zeile Code, die in den ursprünglichen Quellcode eingefügt werden muss, um diesen zu testen, macht ihn unübersichtlicher und schwerer zu warten. Es
gibt Ansätze bei NUnit, wo teilweise die Testklassen von den Quellklassen erben, um
so tiefgreifendere Tests durchführen zu können (HT04). Dieser Nachteil ist zwar größtenteils durch die Integration in Microsoft Visual Studio überwunden worden, allerdings
noch immer erwähnenswert. Was ebenso ein Problem darstellt, ist die falsche Benutzung
des Test-Frameworks. Je komplexer die Software ist, desto schwieriger ist es die Übersicht zu behalten. So können durchaus Methoden oder Properties von Klassen auf ihre
Funktionalität hin getestet werden, die aufgrund von Vererbung ursprünglich vom .NET
Framework bereitgestellt wurden. Die investierte Zeit für die Erstellung der Testklasse
und die Durchführung der Tests erhöht nicht die Qualität der Software, obwohl dies bei
erfolgreichem Durchlaufen der Tests den Anschein hat.
„Wenn irgendein Teil einer Maschine falsch eingebaut werden kann,
so wird sich immer jemand finden, der dies auch tut.“
Murphy’s Gesetze
4.1.2 Fuzzing
Eine weitere Methode für automatische Tests ist das sogenannte Fuzzing-Framework.
Es beschreibt die automatisierte Suche nach Programmierfehlern mit zufällig generierten Werten als Eingangsparameter für die zu testenden Programme. Es übernimmt auch
Aufgaben wie das Protokollieren der Ausgabewerte oder das Verhalten der Testobjekte.
Diese Aufgaben sind ein essentieller Teil beim Testen. Ein solches Fuzzing-Framework
ist die „Peach Fuzzing Platform“. Diese Software kann laut eigenen Angaben folgende
Software mit Fuzzing testen (Edd08):
• Netzwerkprotokoll-Parser
• X-Schichten-Anwendungen
• Datei-Parser
• COM und ActiveX-Komponenten
41
4 Vorhandene Testsysteme
Auch Microsoft hat Teile ihrer Software mit Fuzzing getestet. Einem Bericht von Microsoft Research zufolge wurde eine Whitebox-Fuzzing-Technik genutzt, welche auf einer kontextfreien Grammatik beruht. Damit wurde der JavaScript-Interpreter vom Internet Explorer 7 getestet. Das Problem bei einer Software, wie z.B. einem Interpreter ist,
dass bevor ein Text interpretiert werden kann, dieser genau untersucht wird, ob jegliche
Regeln beachtet wurden. Bei diesen Regeln handelt es sich um JavaScript. Das bedeutet,
dass erst nach sehr vielen Prüfungen, die ein Lexer oder ein Parser vornimmt, der eigentliche Interpreter ausgeführt wird. Sollten vorher irgendwelche Unstimmigkeiten in dem
zu interpretierendem Text enthalten sein, wird dieser sofort abgelehnt. Microsoft machte
den Quelltext zur Grundlage und entwickelte einen Fuzzer, der anhand einer Grammatik einen von der Grammatik her gültigen Ausgabetext erzeugte, mit der der Interpreter
getestet werden konnte. Laut eigenen Angaben wurde eine Überdeckungsrate von 81%
erreicht. Herkömmliche Methoden erreichten dabei nur eine Überdeckungsrate von 51%.
(PGL08)
4.2 Statische Codeanalyse
Die statische Codeanalyse unterstützt einen Entwickler, um potentielle Fehler zu finden und vor allem vorzubeugen. Bestimmte Werkzeuge helfen dabei, den Quellcode zu
analysieren und weisen auf bestimmte Aspekte oder Konstrukte hin, die vermieden oder
anders programmiert werden sollten. Bei der Analyse an sich werden lediglich die Quellcodestrukturen analysiert und Tipps sowie Hinweise auf mögliche Fehlerquellen im Code
gegeben. Die Methoden der Klassen werden dabei nicht ausgeführt, sondern, wie der Name schon sagt, nur analysiert.
4.2.1 Microsoft FxCop
Das bekannteste in der .NET-Szene ist FxCop, ein frei verfügbares Tool zum Analysieren einer .NET Assembly. Es wird also nicht direkt der Quellcode analysiert, sondern
die binäre „Common Intermediate Language“ (CIL), welche vom Compiler erzeugt wird
(Kre08). Entgegen anderslautenden Quellen ist FxCop zwar ein frei verwendbares Tool
von Microsoft, allerdings ist es kein Open Source-Projekt, und das war es auch zu keinem
Zeitpunkt (Kea08). Die aktuelle Version 1.36 (Stand 2 Quartal 08) ist ebenfalls mit .NET
entwickelt worden und benutzt die Technik Introspection, welche ähnlich funktioniert
wie Reflection. Zwar bietet Introspection ausschließlich die Möglichkeiten des Lesens
und nicht des Schreiben wie Reflection, allerdings sind diese Lesefähigkeiten dafür um
42
4 Vorhandene Testsysteme
so tiefgreifender. Eine Assembly kann wesentlich genauer und detailreicher analysiert
werden. Die gesamte Struktur einer Assembly mit allen Objekten, enthaltenen Ressourcen, Enumerationen und Klassen, welche wiederum Methoden, Variablen, Properties und
anderen Objekte enthalten können, werden analysiert und mit vorhandenen Regeln verglichen. Diese Regeln sind an die Microsoft Design Standards angelehnt und gliedern
sich in die folgenden Kategorien (CA05):
• Design Rules
• Globalization Rules
• Interoperability Rules (COM)
• Mobility Rules
• Naming Rules
• Performance Rules
• Portability Rules
• Security Rules
• Usage Rules
Das Ergebnis der Analyse ist nach kurzer Zeit sichtbar. Es werden übersichtlich alle
Regelverstöße aufgelistet mit zusätzlichen Informationen, wie der Schwere dieser Regelverletzung, das Objekt, welches die Verletzung verursacht hat und der Grund, weshalb
diese Regel verletzt wurde sowie auch ein Lösungsvorschlag. Eine Regel wird verletzt,
wenn z.B. bestimmte Namenskonventionen nicht eingehalten oder bestimmte Programmierkonstrukte verwendet werden, die eine potentielle Fehlerquelle darstellen. Dazu gehören auch veraltete Befehle, die vom Compiler als „obsolet“ oder „veraltet“ markiert
werden. Ungenutzte Variablen oder Parameter führen ebenfalls eine Regelverletzung
nach sich. Der wohl größte Vorteil liegt in der Möglichkeit, eigene Regeln erstellen zu
können. Beim Abbilden von code conventions in FxCop-Regeln entsteht ein einheitlicher
Programmierstil und menschliche Fehler würden sofort erkannt und korrigiert werden
können. Durch die umfangreichen Möglichkeiten, die Introspection gegenüber Reflection bietet, können u.a. folgende Prüfungen realisiert werden (Kre08):
• Sicherstellen, dass Namenskonventionen bei Steuerelementen auf Masken oder
Webseiten eingehalten werden.
• Sicherstellen, dass bestimmte Komponenten oder Steuerelemente benutzt werden.
• Prüfen von Werten, die als Parameter an Methoden übergeben werden.
43
4 Vorhandene Testsysteme
• Begutachten von Codestrukturen, wie z.B. Schleifen oder Bedingungen.
• Herausfinden aller Aufrufer einer Methode.
• Überprüfung der sprachlichen Richtigkeit von Namen jeglicher Objekte.
• Prüfen der XML-Kommentardokumentation auf Vollständigkeit.
Ein großer Vorteil von FxCop ist, dass Tests jederzeit im Nachhinein durchgeführt
werden können, ohne dass auf irgendwelche besonderen Test-Notationen im Quellcode
geachtet werden muss. Was FxCop allerdings nicht leisten kann, ist das Ausführen der
Methoden zum Testen des Verhaltens. Dies ist allerdings kein Nachteil, da es sich hierbei
um ein Analyse-Tool handelt. Zusammenfassend kann gesagt werden, dass FxCop immer
eingesetzt werden sollte, um Fehlern vorzubeugen. Der Quellcode wird einheitlich und
viele potentielle Fehlerquellen können schon von vornherein ausgeschlossen werden.
4.3 GUI-Tests
Es gibt sehr viele verschiedene Programme, mit denen GUI-Tests durchgeführt werden
können.
4.3.1 Ranorex
Eines solcher GUI-Testerprogramme ist das Tool Ranorex, welches die gleichnamige Firma entwickelt hat. Im Rahmen dieser Arbeit soll die Ranorex Free Edition kurz
vorgestellt werden. Hiermit kann ein GUI-Test an einer Software vorgenommen werden,
indem Benutzereingaben simuliert und Ergebnisse abgelesen und ausgewertet werden
(siehe Abbildung 4.3 auf der nächsten Seite):
Zusammenfassend kann zu Ranorex gesagt werden, dass es eine sehr gute Unterstützung ist, um Software zu testen. Alle Tests können mehrfach wiederholt werden. Ein
nicht zu unterschätzender Vorteil von Ranorex ist die kurze Einarbeitungszeit. Nach einer einmaligen Einarbeitungszeit von ca. einer Stunde kann ein simpler Test (z.B. das
Addieren beim Windows-Taschenrechner) innerhalb von Minuten erstellt werden. Würden nun allerdings alle Funktionen getestet werden, wäre das ein wesentlich größerer
Aufwand. Selbst mit dem RanorexRecorder, mit dem der Ablauf aufgezeichnet werden
kann, müssen die Validierungen manuell nachimplementiert werden. Abgesehen davon
ist der Windows-Taschenrechner eines der am besten zu testenden Anwendungen. Sollte
z.B. der Explorer getestet werden, so wäre dies eine wesentlich größere Herausforderung.
Dort wird nicht mit eindeutigen Zahlen hantiert, sondern mit Listen von Dateinamen. Es
44
4 Vorhandene Testsysteme
Abbildung 4.3: RanorexSpy liest Daten des Buttons 2 des Windows-Taschenrechners aus
müsste z.B. geprüft werden, ob ein angelegter Ordner wirklich vorhanden ist oder ob nach
dem Umbenennen einer Datei diese wirklich mit dem ihr nun zugewiesenen Programm
geöffnet wird. In der Praxis gibt es weitaus schwerere Testfälle, die es zu validieren gilt,
wie z.B. Videos, Bilder, PDF-Dokumente oder auch Audiowiedergaben.
Neben dem Aufwand ist der größte Nachteil, dass während der Tests der Rechner nicht
benutzt werden darf, da Ranorex die Maus und Tastatur benutzt, um so echte Benutzereingaben zu simulieren. Wenn während der Tests die Maus bewegt werden würde, könnte
es sein, dass ein Test verfälscht wird. Weiterhin darf auch kein Programm das zu testende Programm überlagern, da sonst die Mausklicks nicht beim zu testenden Programm
ankommen. In der freien Version eignet sich Ranorex am besten, um Kalkulationsprogramme zu testen. Sofern sich die Zielanwendung mit Ranorex testen lässt, sollte dies
auf jeden Fall getan werden.
4.3.2 TestComplete
TestComplete ist laut Aussage der Herstellerfirma AutomatedQA ein automatischer
Test-Manager, welcher aufgrund der umfangreichen Funktionalität schon mehrfach von
unterschiedlichen Zeitschriften ausgezeichnet wurde. Zu den durchführbaren Tests gehören Unit-Tests, Web-Tests, HTTP-Performance-Tests, datengesteuerte Tests und natürlich auch GUI-Tests. Wie bei allen GUI-Testern müssen die zu testenden Programme
45
4 Vorhandene Testsysteme
und Steuerelemente identifiziert werden. Hierfür ist ebenfalls ein Recorder vorhanden,
mit dem der Testlauf einfach zusammengeklickt werden kann. Im Folgenden sind die
Eigenschaften des Buttons 2 des Windows-Taschenrechners analysiert worden (vgl. Abschnitt 4.3 auf der vorherigen Seite), allerdings diesmal mit TestComplete (siehe Abbildung 4.4):
Abbildung 4.4: Properties des Buttons 2 des Windows-Taschenrechners, ausgelesen von TestComplete
In diesem Test wird die Methode Test1 ausgeführt. Sie startet den WindowsTaschenrechner und simuliert die Aufgabe 2 + 5 = 7, als wenn sie von einem Benutzer
eingegeben worden wäre. Am Ende wird das Ergebnis validiert, welches der Taschenrechner in der Textbox darstellt (siehe Listing 4.1 auf der nächsten Seite).
46
4 Vorhandene Testsysteme
Listing 4.1: Beispiel eines TestComplete-Tests des Taschenrechners, ob 2 + 5 = 7 ist
1 function Main ()
2 {
3
try
4
{
5
Test1 () ;
6
}
7
catch ( exception )
8
{
9
Log [" Error " ]( " Exception " , exception [" description " ]) ;
10
}
11 }
12
13 function Test1 ()
14 {
15
var
16
w1 = Sys [" Process " ]( " calc ")[" Window " ]( " SciCalc " , " Rechner ");
17
w1 [" Window " ]( " Button " , "2")[" ClickButton " ]() ;
18
w1 [" Window " ]( " Button " , "+")[" ClickButton " ]() ;
19
w1 [" Window " ]( " Button " , "5")[" ClickButton " ]() ;
20
w1 [" Window " ]( " Button " , "=")[" ClickButton " ]() ;
w1 ;
21
22
if ( Sys [" Process " ]( " calc ")[" Window " ]( " SciCalc " , " Rechner " , 1) [" Window " ]( " Edit "
23
Log [" Error " ]( " The property value does not equal the template value .");
, "" , 1) [" wText "] != "7, ")
24 }
Sollte das Ergebnis ungleich 7 sein, so wird ein Fehler im Protokoll vermerkt. Das
Protokoll kann sehr detailliert geführt werden. Fähigkeiten wie das Gruppieren von Einträgen über das Erfassen von Umgebungssituationen, wie Speicherauslastung oder Prozessorauslastung, bis hin zum Hinzufügen von Bildern (Screenshots) lassen das Protokoll stets übersichtlich erscheinen. Es gibt viele Events, die ausgelöst werden können,
wie z.B. UnexpectedWindow, welches ausgelöst wird, wenn ein unbekanntes Fenster erscheint. Standardmäßig kann z.B. immer die Cancel-Taste gedrückt werden. Somit kann
ein kleiner Fehler niemals den gesamten Testlauf unterbrechen. Die Ergebnisse können
exportiert oder per Mail versendet werden, was verdeutlicht, dass Testen auch eine wichtige Sache im Team ist.
4.4 Zusammenfassung
GUI-Tests haben den Vorteil, dass sie über .NET hinaus einsetzbar sind. Durch die
simulierten Benutzereingaben jeglicher Art ist eine qualitativ hochwertige Aussage über
das Verhalten der Software möglich. Ein weiterer, sehr großer Vorteil ist, dass der GUI-
47
4 Vorhandene Testsysteme
Tester im Vergleich zum Menschen nicht müde oder unkonzentriert wird. Zwar bieten
diese Tools sehr viele Werkzeuge und Möglichkeiten, es dem Benutzer so einfach wie
möglich zu machen, allerdings ist hier nachteilig zu erwähnen, dass diese Tests sehr
umfangreich und kompliziert werden können.
FxCop dient der statischen Codeanalyse und eignet sich hervorragend für die Prüfung
des Quellcodes in jeder Phase des Entwicklungsprozesses. Somit können potentielle Fehlerquellen schon im Vorfeld ausgeschlossen und kritische Codestellen korrigiert werden.
Die Fähigkeit, eigene Regeln zu implementieren, macht es zu einem idealen Werkzeug,
um code conventions durchzusetzen. Somit wird der Code sehr konsistent und ist wesentlich leichter zu warten. Da FxCop schnell installiert, leicht zu bedienen und auch noch
kostenlos ist, sollte es in keinem Softwareprojekt fehlen.
Durch die Integration von NUnit in Microsoft Visual Studio 2008 wurde die Mächtigkeit stark erhöht. Viele Nachteile, wie das aufwendige Vorbereiten von Tests, wurden
durch Assistenten stark vereinfacht. Weiterhin wurden die Tests stärker gekapselt als früher. Durch das Erstellen eines separaten Testprojektes entsteht keine allzu starke Vermischung zwischen dem Quellcode und dem Testcode. Zusätzlich wurde das Problem
gelöst, dass NUnit keine Objekte oder Objektteile testen konnte, auf die von außen nicht
zugegriffen werden konnte.
Der Nachteil von Fuzzing ist, dass selbst wenn kein Fehler festgestellt werden konnte, dies keine Garantie dafür ist, dass die Methode ordnungsgemäß arbeitet. Fuzzing soll
grobe Fehler und Abstürze aufspüren. Nur in den seltensten Fällen werden in einem komplexen Entwicklungszyklus die geschriebenen Methoden und die Eingabeparameter ausschließlich für den ursprünglich entwickelten Zweck verwendet. Deswegen müssen Parameter auch auf Werte hin validiert werden, für die sie im ersten Moment nicht konzipiert
waren. Der größte Vorteil dieser Technik ist das Generieren der Testfälle.
Video- oder Audiowiedergaben kann keines der hier aufgeführten Systeme testen, da
es sehr schwer zu automatisieren ist. Es wäre so aufwendig, dass es wesentlich effizienter ist, wenn solche Tests immer noch von Menschen erledigt werden (Wel08). Jedes
Testsystem deckt einen bestimmten Testbereich ab. Teilweise gibt es Überschneidungen der Testbereiche. Jedes Testsystem hat seine Vor- und Nachteile sowie Stärken und
Schwächen. Je mehr getestet wird, desto fehlerfreier wird im Allgemeinen die Software.
Deswegen sollten so viele Testsysteme wie möglich zum Einsatz kommen.
48
Projekt AutoTest.Net
[5]
5.1
„Das Ganze ist mehr als die Summe seiner Teile.“
Soll-Zustand
Aristoteles
5.2
Optionale
Funktionalitä-
Dieses Kapitel beschreibt die Anforderungen an AutoTest.Net sowie optionale Featu-
ten
res. Jegliche Klassendiagramme werden mit „MicroTool ObjectIf 7“ erstellt. Obwohl die
5.3
von Microsoft Visual Studio 2008 erzeugten Klassendiagramme wesentlich übersichtli-
Aufbau der Be-
cher aufgebaut sind, werden hier ausschließlich die Diagramme von „MicroTool Objec-
dienoberfläche
tIf“ verwendet, da sich diese an der UML-Norm orientieren.
5.4
Arbeitsablauf
in
5.1 Soll-Zustand
AutoTest.Net
Hier werden alle Anforderungen an AutoTest.Net gestellt. In der ersten Version, welche
AutoTest.Net 2008 heißen wird, soll die Software folgende Funktionalitäten aufweisen:
• Laden und Analysieren des Aufbaus einer .NET-Assembly
Mit einem „Datei|Öffnen“-Dialog soll eine .NET-Assembly ausgewählt und geladen werden. Dabei wird mittels Reflection die interne Struktur analysiert und dem
Benutzer dargestellt.
• Durchführung der Tests
Die Funktionstests werden mittels Fuzzing durchgeführt. Jeder Konstruktor und jede Methode des ausgewählten Objektes wird ausgeführt. Als Parameter sollen alle
Werte einer jeden Äquivalenzklasse benutzt werden. Enthält also die Signatur einer
Methode einen Parameter z.B. vom Typ DateTime, so wird die Methode mit jedem
Wert der Äquivalenzklasse System.DateTime einmal aufgerufen. Die Signatur einer Methode besteht aus dem Namen der Methode sowie allen Parametertypen.
49
5.5
Lösungskonzeption
5 Projekt AutoTest.Net
• Ausgabe eines Protokolls
Es werden alle abgestürzten Methoden, zusammen mit den verursachenden Parameterwerten und den abgefangenen Exceptions aufgelistet.
• Konfigurieren der Exceptions
Hier können erwartete Exceptions eingestellt werden. Sollte eine erwartete Exception, wie z.B. eine System.ArgumentNullException geworfen werden, so wird
diese nicht im Fehlerprotokoll aufgeführt.
• Konfigurieren der Äquivalenzklassen
Alle in den Äquivalenzklassen hinterlegten Werte können hier betrachtet und verändert werden. Neue, zu dieser Klasse passende Werte können hinzugefügt oder
vorhandene gelöscht werden.
AutoTest.Net soll als stand-alone-Anwendung implementiert werden, da so vorhandene Assemblies geladen und getestet werden können, selbst wenn kein Visual Studio
installiert ist. Ein Vorteil einer Integration wäre eine leichtere Handhabung, da keine externe, separate Anwendung benötigt würde. Aus diesem Grund wird die Integration als
optionales Feature bedacht.
Um eine Übersicht über die Funktionalität der Software zu erhalten, lässt sich anhand
der verbalen Beschreibung des Systems folgendes Anwendungsfalldiagramm (Use-CaseDiagramm) erstellen (siehe Abbildung 5.1):
Abbildung 5.1: Anwendungsfalldiagramm für AutoTest.Net
Als Akteur ist hier der Tester zu nennen, der mithilfe von AutoTest.Net eine .NETAssembly testet. Use-Cases werden mittels einer Schablone ähnlich einer Checkliste detailliert mit Ziel, Vor- und Nachbedingungen, dem Akteur, der diesen Use-Case benutzt,
50
5 Projekt AutoTest.Net
sowie Alternativen beschrieben (Bal08). Im Folgenden werden die einzelnen Use-Cases
von AutoTest.Net detailliert beschrieben:
• Use-Case: Testen
Ziel: Testen von Methoden einer Klasse in einer .NET-Assembly
Vorbedingungen: Gültige zu testende .NET-Assembly vorhanden
Nachbedingung Erfolg: Ausgewählte Methoden der Assembly getestet
Nachbedingung Fehlschlag: Ausgabe von Fehlermeldungen im Log-Bereich
Akteure: Tester (Benutzer der Software)
Beschreibung:
1. Testen einer .NET-Assembly
Erweiterungen:
1a. Laden/Analysieren einer .NET-Assembly
1b. Tests durchführen
Alternativen: -
• Use-Case: Laden/Analysieren einer .NET-Assembly
Ziel: Laden und Analysieren einer .NET-Assembly sowie übersichtliches Darstellen der internen Struktur der Assembly
Vorbedingungen: Gültige zu testende .NET-Assembly vorhanden
Nachbedingung Erfolg: Das System identifiziert alle Typen der Assembly und listet sie auf
Nachbedingung Fehlschlag: Ausgabe von Fehlermeldungen im Log-Bereich
Akteure: Tester (Benutzer der Software)
Beschreibung:
1. Das System wartet auf eine mittels eines „Datei|Öffnen“-Dialoges vom Akteur ausgewählte .NET-Assembly
2. Das System lädt die ausgewählte .NET-Assembly in den Speicher
3. Das System analysiert die interne Struktur der Assembly und listet alle enthaltenden Klassen auf
4. Das System lässt den Benutzer eine Klasse zum Testen auswählen
Erweiterungen:
3a. Das System listet nur Klassen auf. Weitere Typen wie z.B. Interfaces werden
nicht aufgelistet, da diese nicht getestet werden können
51
5 Projekt AutoTest.Net
Alternativen:
2a. Wenn die vom Akteur ausgewählte Datei keine gültige .NET-Assembly ist,
dann Ausgabe von Fehlermeldungen im Log-Bereich
• Use-Case: Tests durchführen
Ziel: Durchführung der Tests an allen ausgewählten Konstruktoren und Methoden
Vorbedingungen: Ein zu testendes Objekt wurde vom Akteur ausgewählt
Nachbedingung Erfolg: Das System gibt alle fehlgeschlagenen Methoden und die
Parameterwerte, die zum Absturz der Methode führten, auf dem Bildschirm aus
Nachbedingung Fehlschlag: Ausgabe von Fehlermeldungen im Log-Bereich
Akteure: Tester (Benutzer der Software)
Auslösendes Ereignis: Akteur startet die Tests
Beschreibung:
1. Das System bedient sich der im Speicher vorhandenen Auflistung der zu testenden Konstruktoren
2. Erkennen der benötigten Werte aus den Äquivalenzklassen anhand der Parameter des zu testenden Konstruktors
3. Durchführen des Funktionstests an den Konstruktoren (Instanziieren eines
Objektes mit dem zu testenden Konstruktor; auftretende Exceptions abfangen)
4. Erzeugen einer Instanz für die Funktionstests an den Methoden
5. Erkennen der benötigten Werte aus den Äquivalenzklassen anhand der Parameter der zu testenden Methoden
6. Durchführen des Funktionstests an den Methoden (Ausführen der Methode
mit der erstellten Instanz; auftretende Exceptions abfangen)
Erweiterungen:
4a. Erzeugen einer Instanz des Objektes mittels des Standardkonstruktors
4a1. Ist kein Standardkonstruktor vorhanden, so versucht das System einen
anderen Konstruktor zu instanziieren
Alternativen:
3a. Wenn z.B. aus Sicherheitsgründen ein Fehler auftritt, der die Durchführung
der Tests an Konstruktoren verhindert, dann Ausgabe einer Fehlermeldung
im Log-Bereich
52
5 Projekt AutoTest.Net
4a. Wenn das System nicht in der Lage ist, eine Instanz zu erzeugen, dann Ausgabe einer Fehlermeldung im Log-Bereich
5a. Wenn z.B. aus Sicherheitsgründen ein Fehler auftritt, der die Durchführung
der Tests an Methoden verhindert, dann Ausgabe einer Fehlermeldung im
Log-Bereich
• Use-Case: Ausgabe der Testergebnisse
Ziel: Ausgabe der beim Test abgestürzten Methoden und die Parameterwerte, die
zum Absturz führten sowie die entstandene Exception
Vorbedingungen: Das System hat erfolgreich alle Tests durchgeführt
Nachbedingung Erfolg: Ausgabe von Testergebnissen auf dem Bildschirm
Nachbedingung Fehlschlag: Ausgabe von Fehlermeldungen im Log-Bereich
Akteure: Tests durchführen
Auslösendes Ereignis: Beendigung der Tests
Beschreibung:
1. Das System zeigt die Testergebnisse an. Dargestellt werden die abgestürzte
Methode, die zum Absturz geführten Parameterwerte sowie die aufgetretene
Exception
Erweiterungen:
1a. Gruppierungsmöglichkeit nach abgestürzten Methoden sowie nach geworfener Exception
1b. Falls keine Exception auftritt, Ausgabe einer Meldung im Ergebnissfenster
(„Keine Exception aufgetreten“)
Alternativen:
1a. Wenn Fehler bei der Visualisierung der Testergebnisse aufgetreten, dann
Ausgabe einer Fehlermeldung im Log-Bereich
• Use-Case: Exceptions verwalten
Ziel: Das System bietet eine Auswahlmöglichkeit für alle Exceptions des Namespace System an
Vorbedingungen: Nachbedingung Erfolg: Das System speichert die Auswahl. Nur ausgewählte Exceptions werden bei der Ausgabe der Ergebnisse berücksichtigt
Nachbedingung Fehlschlag: Ausgabe von Fehlermeldungen im Log-Bereich
53
5 Projekt AutoTest.Net
Akteure: Tester (Benutzer der Software)
Auslösendes Ereignis: Öffnen der Exception-Maske
Beschreibung:
1. Das System startet die Exception-Maske, welche alle Typen des Namespace
System auflistet, die von System.Exception erben
2. Wartet auf das „Checked“-Event
Erweiterungen:
2a. Sofortiges Speichern der Auswahl
Alternativen:
2a. Wenn die Speicherung der Auswahl fehlschlägt, dann Ausgabe einer Fehlermeldung im Log-Bereich
• Use-Case: Äquivalenzklassen verwalten
Ziel: Anpassen der vordefinierten Äquivalenzklassen
Vorbedingungen: Nachbedingung Erfolg: Äquivalenzklassen sind angepasst
Nachbedingung Fehlschlag: Ausgabe von Fehlermeldungen
Akteure: Tester (Benutzer der Software)
Auslösendes Ereignis: Öffnen der Äquivalenzklassen-Maske
Beschreibung:
1. Das System stellt die vorhandenen Äquivalenzklassen dar
2. Das System wartet auf eine Auswahl
3. Das System stellt alle enthaltenen Werte der Äquivalenzklasse dar
4. Das System bietet Verwaltungsmöglichkeiten an
Erweiterungen:
4a. Das System wartet auf eine Eingabe zum Hinzufügen neuer Werte zu einer
Äquivalenzklasse
4b. Das System löscht ausgewählte Werte einer Äquivalenzklasse
Alternativen:
4a. Wenn der hinzugefügte Wert nicht der Äquivalenzklasse entspricht, dann
Ausgabe einer Fehlermeldung im Log-Bereich
54
5 Projekt AutoTest.Net
5.2 Optionale Funktionalitäten
Neben dem Soll-Zustand können viele weitere Funktionen implementiert werden. In
der folgenden Tabelle werden optionale Features aufgelistet (siehe Tabelle 5.1):
optionale Feature
Beschreibung
Prüfen von Ressourcenauslas-
Performance des Systems überwachen und heraus-
tung wie CPU oder Speicher
finden, ob Speicher- oder CPU-Leaks auftreten.
Benutzen komplexer Testfälle
Neben den simplen Datentypen können im Testsystem komplexe Objekte (z.B. Klassen) erstellt und als
Eingangswerte für die Tests genutzt werden.
Benutzen der Rückgabewerte als
Die Rückgabewerte einer getesteten Methode kann
Testfälle
als Eingangswert für den nächsten Test genutzt werden.
Sicherheitsbereiche einstellen
Eventuell werden bei einem Test ungewollte Ressourcen wie z.B. Zugriff auf das Dateisystem benutzt. Mittels einer Sandbox könnte dies verhindert
werden.
In Microsoft Visual Studio 2008
Eventuell zusätzlich zur externen Anwendung kann
als Plug-In integrieren
AutoTest.Net in Microsoft Visual Studio 2008 integriert werden.
Ausgabeprotokoll exportieren
Das Ausgabeprotokoll kann z.B. in eine Textdatei
oder als HTML-Ansicht exportiert werden.
Tabelle 5.1: Liste der optionalen Features
Beim Betrachten dieser optionalen Features fällt keines auf, dass besonderer Voraussetzungen bedarf. Alle sind ohne großen Aufwand im Nachhinein implementierbar. Wie
viele und welche dieser optionalen Features implementiert werden können, wird sich im
Laufe der Implementierung zeigen.
5.3 Aufbau der Bedienoberfläche
Um die Anwendung stets übersichtlich zu halten, soll sie mittels andockbaren Fenstern
arbeiten. Geplant sind neben einer Menü- und Schnellstartleiste folgende Elemente:
55
5 Projekt AutoTest.Net
• Log-Bereich
In diesem Fenster werden jegliche Fehler, Warnungen oder normale Informationsmeldungen ausgegeben (siehe Abbildung 5.2).
Abbildung 5.2: Log-Bereich in AutoTest.Net 2008
Die Verwendung von MessageBoxen soll, sofern möglich, vermieden werden, da
sie den Arbeitsfluss behindern.
• Verwaltungsbereich für die Äquivalenzklassen
Die Äquivalenzklassen stellen Testdaten dar. Mit ihnen wird festgelegt, zu welchem
Typ welche Werte als Testdaten in Frage kommen. Hier wird es ein StandardRepertoire an Werten geben, welches jederzeit beliebig angepasst werden kann
(siehe Abbildung 5.3). Diese Maske arbeitet unabhängig von anderen Programmteilen und ist angelehnt an den Use-Case „Äquivalenzklassen verwalten“.
Abbildung 5.3: Verwaltungsbereich für die Äquivalenzklassen in AutoTest.Net 2008
• Verwaltungsbereich für die Exceptions
Exceptions, die keinen Fehlerfall darstellen und somit nicht im Fehlerprotokoll aufgelistet werden, können hier verwaltet werden. Dies wird wie die Äquivalenzklassenverwaltung eine separate Maske sein, die unabhängig von anderen Programmteilen arbeitet. Es werden alle Exceptions des Namespace System zur Auswahl
56
5 Projekt AutoTest.Net
aufgelistet. Änderungen werden mit dem „Checked“-Event sofort übernommen,
wie im Use-Case „Exceptions verwalten“ beschrieben (siehe Abbildung 5.4):
Abbildung 5.4: Verwaltungsbereich für die Exceptions in AutoTest.Net 2008
• Hauptfenster
Das Hauptfenster beinhaltet im ersten Schritt die verwaltenden Elemente für die
Vorbereitungen der Tests (wird im Folgenden Startmaske genannt). Dazu gehören
das Laden der Assembly und das Auswählen der zu testenden Methoden und Konstruktoren. Nach dem Start werden im Hauptfenster als weiteres Tab die Ergebnisse
präsentiert (wird im Folgenden Ergebnismaske genannt).
5.4 Arbeitsablauf in AutoTest.Net
Nach dem Start der Anwendung kann der Benutzer mittels der Startmaske eine .NETAssembly einlesen. Selbstständig erkennt die Software schon vor dem Laden, ob es sich
um eine gültige .NET-Assembly handelt. Andernfalls wird ein Eintrag im Log-Bereich
erstellt. Nach dem Laden werden alle enthaltenden Klassen aufgelistet und der Benutzer
kann sich eine zum Testen auswählen. Jegliche Einstellungen wie z.B. das detaillierte Auswählen der zu testenden Methoden können vor dem Test vorgenommen werden.
Ohne weitere Einstellungen werden standardmäßig alle in der Klasse vorhandenen Konstruktoren und Methoden mit den Standard-Äquivalenzklassen getestet. Nach dem Test
wird die Ergebnismaske angezeigt, welche alle Fehlerfälle auflistet sowie Gruppierungsmöglichkeiten anbietet. Im einfachsten Fall lädt der Benutzer eine Assembly, wählt eine
Klasse aus und startet sofort die Tests.
57
5 Projekt AutoTest.Net
5.5 Lösungskonzeption
Die wichtigsten Klassen sind die Klassen Test, ExceptionManagement,
EquivalenceClass<T> und TestResult. Im Folgenden wird näher auf die einzelnen Klassen und deren Zusammenspiel eingegangen.
5.5.1 Äquivalenzklassenverwaltung
Um die Äquivalenzklassen verwalten zu können, sind Funktionen wie Hinzufügen oder
Löschen von Klassenwerten nötig. Ein vordefinierter Satz an Äquivalenzklassen steht permanent zur Verfügung. Diese Klasse ist aufgrund ihrer Typabhängigkeit eine generische
Klasse vom Typ T (gelber Balken) (siehe Abbildung 5.5). Das bedeutet, dass der Typ
der Klasse erst zur Zeit der Instanziierung angegeben wird. Interne Berechnungen oder
Sortierungen geschehen jederzeit typsicher. Das Abspeichern und Laden der Äquivalenzklassen soll ebenfalls von dieser Klasse abgedeckt werden.
Abbildung 5.5: Equivalenzklasse für AutoTest.Net
Das Feld fileName ist der Name einer Datei, in der die Äquivalenzklassen abgelegt
sind. Sollte diese Datei nicht existieren, wird sie erstellt und die Standardklassen mit enthaltenen Werten eingetragen. Die Property Values beinhaltet alle Werte dieser Klasse.
Die vom Interface IEquivalenceClass importierte Property Values gibt die Auflistung
Values ohne generische Typsicherheit, also vom Typ object, zurück. Die statische Methode GetEquivalenceClasses liefert alle in der Datei gefundenen Klassen zurück. Die
statische Methode SaveEquivalenceClasses(List<IEquivalenceClass>) speichert
alle Äquivalenzklassen zurück in die Datei.
58
5 Projekt AutoTest.Net
5.5.2 Exceptionverwaltung
Jede Methode kann Exceptions werfen, wovon einige erwartet werden. Wenn z.B. eine
System.ArgumentNullException anstatt einer System.NullReferenceException
auftritt, so wird deutlich, dass der Programmierer den eventuell eintretenden Fall eines
übergebenen null-Parameters berücksichtigt hat. Hier eingestellte Exceptions erscheinen nicht im Ausgabeprotokoll, da davon ausgegangen werden kann, dass diese Methode
bei unbekannten oder ungültigen Parameterwerten nicht abstürzt. Als Funktionalität muss
das Hinzufügen und Entfernen von Exceptions möglich sein. Das folgende Klassendiagramm zeigt die Klasse, die für diese Verwaltung zuständig ist (siehe Abbildung 5.6):
Abbildung 5.6: Exceptionverwaltungsklasse für AutoTest.Net
Diese Klasse ist ein Singleton, weswegen der Konstruktor private ist und über die
statische Property mit dem Namen Instance auf das Objekt zugegriffen werden kann
(siehe Listing 5.1). Die Exceptions werden in einer Schlüsselwertpaar-Auflistung gespeichert. Das Feld exceptions ist vom Typ Dictionary<Type, bool> und speichert zu
jedem Typ einen booleschen Wert. Der Wert true gibt an, dass die Exception in der
Ergebnisauflistung erscheint, wohingegen false dies unterbindet.
Listing 5.1: Singleton-Pattern der ExceptionManagement-Klasse
1 class ExceptionManagement
2 {
3
private static readonly ExceptionManagement instance = new ExceptionManagement () ;
4
public static ExceptionManagement Instance { get { return instance ; } }
5 }
59
5 Projekt AutoTest.Net
5.5.3 Testdurchführung
Die Klasse Test stellt den Kern von AutoTest.Net dar und beinhaltet alle Einstellungen, die in der gesamten Anwendung vorgenommen werden können. Deswegen unterliegt auch diese Klasse dem Singleton-Pattern. Die folgenden Auflistungen der Klasse
Test beinhalten die jeweils beschriebenen Objekte:
• EquivalenceClasses vom Typ List<IEquivalenceClass>
Beinhaltet alle Äquivalenzklassen.
• TestResults vom Typ List<TestResult>
Beinhaltet alle Testergebnisse.
• MethodCollection vom Typ Dictionary<MethodInfo, bool>
Beinhaltet alle Methoden von Typen, die in der TypeCollection enthalten sind.
Der boolesche Wert gibt an, ob diese Methode bei den Tests berücksichtigt wird.
• ConstructorCollection vom Typ Dictionary<ConstructorInfo, bool>
Beinhaltet alle Konstruktoren von Typen, die in der TypeCollection enthalten
sind. Der boolesche Wert gibt an, ob dieser Konstruktor bei den Tests berücksichtigt wird.
• AssemblyCollection vom Typ List<Assembly>
Beinhaltet alle geladenen .NET-Assemblies.
• TypeCollection vom Typ List<System.Type>
Beinhaltet alle vom Benutzer ausgewählten Klassen. Nur Konstruktoren und Methoden hier hinzugefügter Typen werden in die ConstructorCollection respektive MethodCollection übernommen.
• AllTypeCollection vom Typ List<System.Type>
Beinhaltet
alle
Klassen,
die
in
allen
geladenen
Assemblies
der
AssemblyCollection vorhanden sind.
Zu jeder Auflistung gibt es entsprechende Add- oder Remove-Methoden, um die Auflistungen zu verändern. Das Interface IEquivalenceClass wird genutzt, um nicht generisch, d.h. alle Werte sind vom Typ object, durch die Auflistung der Äquivalenzklassen
durchiterieren zu können. Die Flag-Properties der Klasse Test geben an, ob die entsprechenden Methoden und Konstruktoren berücksichtigt werden sollen. Zum Beispiel gibt
das Public-Flag an, ob öffentliche Konstruktoren und Methoden getestet werden. Die
Property TestConstructor gibt an, welcher Konstruktor für die Tests der Methoden benutzt werden soll. Wird dieser nicht angegeben, wird der Standardkonstruktor benutzt.
60
5 Projekt AutoTest.Net
Die Resultate werden als eigene Objekte modelliert, da sie komplexe Datentypen sind,
die weitere Informationen enthalten. Das folgende Klassendiagramm zeigt übersichtlich
die bei einem Testlauf beteiligten Klassen (siehe Abbildung 5.7):
Abbildung 5.7: Klassendiagramm für AutoTest.Net
Blaue Kanten in der rechten unteren Ecke des Klassennamens bedeuten, dass dies
eine abstrakte Klasse ist und somit keine Instanz erstellt werden kann. Die Methode
AbortTest beendet den Thread, in dem die Tests durchgeführt werden. Die beiden statischen Methoden GetAccessType und GetParameters liefern jeweils einen String zurück, der kommagetrennt die Zugriffsmodifizierer (z.B. „Public, Static“) bzw. die Parameter (z.B. „System.String, System.Int32“) zurückgibt. Diese sind statisch, weil sie nicht
nur von der Klasse Test, sondern auch von der Oberfläche zur Darstellung genutzt werden. Der Parameter beider Methoden ist vom Typ MethodBase, welcher die Basisklasse
für ConstructorInfo und MethodInfo darstellt. Deswegen können beide Methoden zur
Ermittlung der Werte beider Typen, Konstruktoren und Methoden, genutzt werden.
61
Implementierung des Testsystems
[6]
6.1
Die Software kann nun anhand der Konzeption und der vorangegangenen Diagramme
Implementierung
und Spezifikationen implementiert werden. Es wird als „2-Schicht“-Anwendung (engl.
der GUI
2-Tier-Application) implementiert (siehe Abbildung 6.1).
6.2
Implementierung
der Use-Cases
6.3
Fertigstellung
von
AutoTest.Net
Abbildung 6.1: Anwendungsschichten von AutoTest.Net
Die Logikschicht beinhaltet die gesamte Logik der Anwendung. Jegliche Visualisierungen werden in der GUI-Schicht gekapselt. Zwischen den Schichten werden lediglich
simple Daten ausgetauscht, d.h. keine GUI-spezifischen Datentypen wie ListViewItem
oder TreeNode. Der Namespace System.Windows.Forms, welcher jegliche visuellen
Objekte enthält, wird nicht in die Logikschicht eingebunden. Somit ist es möglich, die
gesamte graphische Benutzeroberfläche auszutauschen, ohne die eigentlichen Logikklassen zu verändern. Im ersten Schritt wird die GUI erstellt, welche anfangs gänzlich ohne
Funktionalität ist. So hätte ein Auftraggeber schon sehr früh die Möglichkeit, das entstehende Produkt zu begutachten und ggf. Mängel schon sehr früh zu monieren. Nachdem
die GUI als Prototyp fertiggestellt ist, wird jeder Use-Case einzeln betrachtet und im-
62
6 Implementierung des Testsystems
plementiert. Die GUI dient dazu als Testumgebung, um die implementierten Use-Cases
anzusprechen und auszuführen.
6.1 Implementierung der GUI
Die gesamte GUI besteht aus den folgenden Masken (siehe Abbildung 6.2):
Abbildung 6.2: GUI von AutoTest.Net
Durch Rapid Prototyping wird ein erstes Layout erstellt. Diese Form des Vorgehens
erlaubt es, schnell eine Dummy-Anwendung zu erstellen, welche ohne Funktionalität
die Oberfläche zeigt, wie es später sein könnte. Im Zuge dessen können schon sehr früh
Ecken und Kanten im Layout der Software festgestellt werden.
Oben befindet sich die Menüleiste. Direkt darunter die Schnellstartleiste zum Sofortstart oft benötigter Funktionen. Auf der linken Seite befindet sich die Verwaltung der
Äquivalenzklassen. Im Hauptfenster werden alle vorbereitenden Aktivitäten durchgeführt. Weiterhin werden in verschiedenen Reitern die Ergebnisse präsentiert. An der unteren Seite befindet sich das Log-Panel, welches alle Statusmeldungen mit Zeit anzeigt.
Alle Fenster können abgedockt, frei verschoben und an jeder beliebigen Seite wieder
angedockt werden (siehe Abbildung 6.3 auf der nächsten Seite).
63
6 Implementierung des Testsystems
Abbildung 6.3: Erstes Layout von AutoTest.Net 2008
Beim Bau der eigentlichen Masken ist auf unterschiedliche Fluchtlinien sowie allgemeine Einheitlichkeit zu achten. Eine Anwendung wirkt unterbewusst umso beruhigender und somit angenehmer, desto weniger unterschiedliche Fluchtlinen vorhanden sind.
Texte müssen sich, sofern möglich, auf gleicher Höhe befinden. Um dies einfach umzusetzen, bietet Microsoft Visual Studio sogenannte Snaplines an, mit deren Hilfe Steuerelemente exakt ausgerichtet werden können. Snaplines können für eigene Steuerelemente
selbst implementiert werden. Es gibt blaue Snaplines für Ränder und pinkfarbene Snaplines für Texte (siehe Abbildung 6.4).
Abbildung 6.4: Unterstützende Snaplines beim Bau einer Maske
64
6 Implementierung des Testsystems
Die Maske zum Laden und Analysieren einer .NET-Assembly sowie weitere vorbereitende Einstellungen wird als Prototyp wie folgt aussehen (siehe Abbildung 6.5):
Abbildung 6.5: Maske zur Analyse der Assembly
Eine Assembly kann mithilfe des „Durchsuchen“-Buttons ausgewählt werden. Es werden alle vorhandenen Typen aufgelistet. Sobald der Benutzer den zu testenden Typ ausgewählt hat, werden alle enthaltenen Methoden und Konstruktoren ermittelt.
6.2 Implementierung der Use-Cases
Nachdem eine GUI erstellt wurde, können nun die Use-Cases, die die Funktionalität
der Software darstellen, implementiert werden. Der allumfassende Use-Case „Testen“
enthält die drei Hauptbestandteile von AutoTest.Net. Als Nebenbestandteile sind die Verwaltung der Exceptions sowie die Verwaltung der Äquivalenzklassen zu nennen. Bei der
Umsetzung wurden Microsoft-Designrichtlinien beachtet. Diese geben z.B. an, dass private Variablen mittels Properties gekapselt werden und somit der Zugriff ausschließlich
über diese Properties möglich ist. Im Folgenden werden die einzelnen Use-Cases implementiert, wobei deren Funktionalität anhand der GUI getestet werden kann.
6.2.1 Implementierung Use-Case „Laden/Analysieren der Assembly“
Der benötigte „Datei|Öffnen“-Dialog wird direkt in der GUI erstellt. Er listet
„.exe“ und „.dll“-Dateien auf. Nach dem Auswählen der Dateien werden diese an die
AssemblyCollection der Klasse Test übergeben. Nachdem alle gewählten Assemblies
65
6 Implementierung des Testsystems
übergeben sind, können sämtliche enthaltenen Typen in der AllTypeCollection der
Klasse Test abgerufen und in der GUI angezeigt werden. Mit Assembly.LoadFrom(
path ) wird eine Assembly geladen. Sollte es sich um keine gültige .NET-Assembly
handeln, so wird hierbei eine Exception geworfen. Geladen werden alle enthaltenden
Typen mit der Methode GetTypes einer jeden Instanz der Klasse System.Reflection.
Assembly.
6.2.2 Implementierung Use-Case „Tests durchführen“
Die Tests werden in einem separaten Thread durchgeführt. Für die Tests sind die
ausgewählten Objekte, Konstruktoren und Methoden sowie die Äquivalenzklassen notwendig. Bevor die Tests durchgeführt werden können, müssen alle Typen der Parameter
ermittelt und die Parameterlisten für die Tests vorbereitet werden. Bei Enumerationen
werden alle enthaltenden Werte für die Tests benutzt. Das folgende Listing zeigt den
Ausschnitt der Methode BuildParamArray(ParameterInfo[]), der speziell Enumerationen behandelt. Es wird eine Instanz des Enumerations-Objektes mithilfe von Reflection erzeugt und jeweils alle Werte einer Enumeration übergeben (siehe Listing 6.1):
Listing 6.1: Speichern aller Werte einer Enumeration
1 public List < Object [] > BuildParamArray ( ParameterInfo [] parameters )
2 {
3
List < Object [] > objList = new List < Object [] >() ;
4
foreach ( ParameterInfo par in parameters )
5
{
6
List < Object > obj = new List < Object >() ;
7
if ( par . ParameterType . IsEnum )
8
{
9
// Parameter ist eine Enumeration
Type type = par . ParameterType ;
// Typ des Parameters zuweisen
10
Array values = Enum . GetValues ( type ); // Alle Werte als Array ablegen
11
Object tempObj ;
// Temporäres Objekt
13
foreach ( Object entry in values )
// Alle Enumerationen durchlaufen
14
{
12
15
tempObj = Activator . CreateInstance ( type );
16
tempObj = entry ;
// Wert zuweisen
obj . Add ( tempObj );
// Zur Auflistung der Parameter hinzufügen
17
// Instanz erzeugen
18
}
19
objList . Add ( obj . ToArray () ); // Als Gesamtparameter abschliessen
20
continue ;
21
22
// Nächsten Parameter bearbeiten
}
}
23 }
66
6 Implementierung des Testsystems
Eine große Herausforderung war das Beachten aller Sonderfälle bei den Tests. Spezialfälle wie Arrays oder Enumerationen mussten separat behandelt werden. Für Arrays
werden automatisch leere Arrays generiert, die für die Tests genutzt werden. Für simple
Datentypen werden die Werte aus den Äquivalenzklassen genutzt. Bei mehreren Parametertypen werden alle Permutationen erzeugt.
Nachdem alle Parameter vorbereitet sind, werden die einzelnen Methoden des Testobjekts mit der Methode Invoke, die jede Instanz eines MethodInfo-Objekts enthält,
ausgeführt. Die Parameter werden dabei als Array des Typs object übergeben. Die Ausführung ist von einem try-catch-Block umgeben, um alle Exceptions abzufangen. Sollte
eine Exception auftreten, so wird diese einem TestResult-Objekt zugeordnet und steht
später zur Auswertung zur Verfügung.
6.2.3 Implementierung Use-Case „Ausgabe der Testergebnisse“
Die Ausgabe der abgefangenen Exceptions kann nach Exception oder nach Methode
gruppiert werden. Für diese Gruppierung bietet sich das neue .NET 3.5 Feature LINQ an.
Das folgende Listing zeigt die Gruppierung der Testergebnisse nach Exceptions (siehe
Listing 6.2):
Listing 6.2: Gruppierung von Objekten mit LINQ
1 var query = from result in Test . Instance . Results
2
where result . HasException
3
group result by result . Exception . GetType () into g
4
select g;
Die Darstellung geschieht in einem Steuerelement, welches eine Mischung zwischen
TreeView und ListView darstellt (siehe Abbildung 6.6):
Abbildung 6.6: Darstellung von gruppierten Ergebnissen
Im oberen Teil ist die Gruppierung nach der geworfenen Exception dargestellt. Im unteren Teil ist die Gruppierung nach der Methode dargestellt, die die Exception geworfen
hat.
67
6 Implementierung des Testsystems
6.2.4 Implementierung Use-Case „Exceptions verwalten“
Die Klasse ExceptionManagement verwaltet die Exceptions, die bei der Ausgabe
der Testergebnisse ignoriert werden. Als Objekt für die Speicherung bietet sich ein
Dictionary <Type, bool> an. Es beinhaltet Schlüsselwertpaare und speichert so zu
jedem Exception-Typ einen booleschen Wert, der angibt, ob die Exception berücksichtigt
werden soll. Mit der Methode IsSubclassOf des Objektes System.Type werden rekursiv alle abgeleiteten Typen des Typs System.Exception ermittelt (siehe Listing 6.3):
Listing 6.3: ExceptionManagement. Auflistung aller Exceptions des Namespace System
1 private ExceptionManagement ()
2 {
3
exceptions = new Dictionary < Type , bool >() ;
4
List < Type > list = new List < Type >() ;
5
Assembly a = typeof ( Exception ). Assembly ;
6
Type [] types = a. GetTypes () ;
7
foreach ( Type t in types )
8
{
9
// Exception - Assembly ermitteln
// Alle enthaltenden Typen ermitteln
// Ist Typ t abgeleitet von System . Exception ?
10
if (t. IsSubclassOf ( typeof ( System . Exception ) ))
11
list . Add ( t );
// Wenn ja , füge ihn der Auflistung hinzu
12
}
13
list . Sort ( new Comparison < Type >( ( t1 , t2 ) => t1 . FullName . CompareTo ( t2 .
14
foreach ( var item in list )
FullName ) ) );
15
// Sortiere Exceptions nach Name
exceptions . Add ( item , true );
16
17
// Folgende Exceptions standardmäßig ausschalten
18
exceptions [ typeof ( ArgumentException )] = false ;
19
exceptions [ typeof ( ArgumentNullException )] = false ;
20
exceptions [ typeof ( ArgumentOutOfRangeException )] = false ;
21
exceptions [ typeof ( NotImplementedException )] = false ;
22
exceptions [ typeof ( NotSupportedException )] = false ;
23 }
6.2.5 Implementierung Use-Case „Äquivalenzklassen verwalten“
Die Äquivalenzklassen werden in einer XML-Datei abgelegt, um auch ein externes
Bearbeiten zu ermöglichen. Der Namespace System.Xml bietet Objekte für das Laden,
Analysieren sowie Schreiben von XML-Daten an. Sollte beim ersten Start des Programmes keine Äquivalenzklassen-Datei existieren, so wird die Datei „equivalence.xml“ aus
den internen Ressourcen herauskopiert und in dem Verzeichnis von AutoTest.Net angelegt. Auf eine interne Ressource wird wie folgt zugegriffen (siehe Listing 6.4):
68
6 Implementierung des Testsystems
Listing 6.4: Zugriff auf interne eingebettete Ressourcen
1 XmlDocument xmlDoc = new XmlDocument () ;
2 Assembly asm = Assembly . GetExecutingAssembly () ; // aktuelle Assembly laden
3 Stream XMLStream = asm . GetManifestResourceStream ( asm . GetName () . Name + ".
equivalence . xml " );
4 XmlReader xmlr = XmlReader . Create ( XMLStream ); // Erstelle Stream der xml - Datei
5 try
6 {
7
if (! File . Exists ( fileName ))
8
{
9
10
// Falls Datei nicht vorhanden
xmlDoc . Load ( xmlr );
// Interne Ressource laden
xmlDoc . Save ( fileName );
// Datei anlegen
11
}
12
xmlDoc . Load ( fileName );
// Externe Datei laden
13 }
14 catch ( Exception ex )
// Im Fehlerfall
15 {
16
Logger . Error ( ex . Message );
// Fehler protokollieren
17
return new List < EquivalenceClass >() ;
// Leere Äquivalenzklassen zurückgeben
18 }
Beim Hinzufügen eines neuen Wertes zu einer Äquivalenzklasse kann ein Zufallswert
im angegebenen Typ generiert werden. Die folgende Abbildung zeigt die Eingabemaske
zum Anlegen neuer Äquivalenzklassen-Werte nach betätigen des „Zufallswert“-Buttons,
welcher einen Zufallswert in die Textbox schreibt (siehe Abbildung 6.7):
(a) Zufallswert DateTime
(b) Zufallswert Double
Abbildung 6.7: Hinzufügen eines Wertes zu einer Äquivalenzklasse
Eine Herausforderung war das Erzeugen zufälliger Werte beim Datentyp double. Die
Klasse System.Random erzeugt Double-Zufallszahlen zwischen 0 und 1. Für Werte in
einem anderen Min-Max-Bereich wird folgende Berechnung durchgeführt:
(Max − Min) ∗ rnd + Min
mit rnd = Zufallszahl zwischen 0 und 1. Für Max = Double.MaxValue und Min = Double.MinValue ergibt der Ausdruck (Max - Min) den Wert Infinity, welcher Unendlich symbolisiert. Durch weiterführende Additionen und Subtraktionen verändert sich
dieser Wert nicht mehr, weswegen die Double-Zufallszahlen immer das konstante Sym-
69
6 Implementierung des Testsystems
bol Infinity wiederspiegelten. Aus diesem Grund wurde eine Long-Zufallszahl generiert und mittels double rn = BitConverter.Int64BitsToDouble( longValue );
direkt im Speicher in einen Double-Wert konvertiert. Somit steht der gesamte DoubleBereich zur Verfügung und jede Zahl konnte generiert werden.
6.3 Fertigstellung von AutoTest.Net
Nachdem die Methoden implementiert wurden, entstanden erste Herausforderungen
bezüglich der Benutzbarkeit der Software. Objekte und detaillierte Informationen zu
Objekten müssen auch bei größenveränderbaren Masken übersichtlich dargestellt werden. Weiterhin muss zu jedem Zeitpunkt klar werden, wo sich der Benutzer befindet
und welche Schritte als nächstes zu tun sind. Das folgende Bild zeigt das Hauptprogramm mit Funktionalitäten wie dem Laden und Analysieren des Objektes System.
ComponentModel.BackgroundWorker der Assembly „System.dll“ sowie den Arbeitsfluss, den die Software dem Benutzer vorgibt, um ihn durch das Programm zu leiten
(siehe Abbildung 6.8):
Abbildung 6.8: Analyse des Backgroundworkers mit AutoTest.Net 2008
Nach dem Laden einer Assembly stehen alle enthaltenen Klassen zur Verfügung. Nachdem ein zu testender Typ ausgewählt wurde, erscheint dieser in der Auflistung auf der
rechten Seite. Gleichzeitig füllen sich beide Auflistungen im unteren Bereich der Anwendung mit den gefundenen Konstruktoren sowie den gefundenen Methoden. Nach dem
70
6 Implementierung des Testsystems
Auswählen der zu testenden Methoden und Konstruktoren können die Tests gestartet werden. Sofort wird die Ergebnissmaske angezeigt, zu Beginn allerdings ohne Ergebnisse.
Während der Tests wird der Fortschritt in % mittels eines „Progressbars“ dargestellt, der
sich auf der Ergebnissmaske befinden. Sind die Tests abgeschlossen, werden die Ergebnisse aufgelistet.
71
Sollanalyse - Testen des Testsystems
[7]
7.1
In diesem Kapitel werden die Ergebnisse, die mit AutoTest.Net erzielt wurden, darge-
Testergebnisse
stellt. Getestet werden eigens programmierte Test-Assemblies sowie vorhandene .NET-
mit selbstent-
Assemblies.
wickelter
Test-Assembly
7.1 Testergebnisse mit selbstentwickelter Test-Assembly
7.2
Testergebnisse
Zu Beginn wird eine Assembly mit einer Klasse TestClass entwickelt. Mit dieser
Assembly sollen folgende Fehler gefunden werden:
• Zugriffe auf Objekte, die null (nothing in Visual Basic) sind
• Überlaufsfehler / Umwandslungsfehler
• Zugriff auf nicht vorhandene Klassenmember
Sollte kein Konstruktor angegeben sein, so enthält eine Klasse automatisch einen öffentlichen Standardkonstruktor. Die erste Test-Methode GetStringLength soll die Länge eines Strings zurückgeben. Die Länge des Strings wird mittels der Property Length
ermittelt (siehe Listing 7.1):
Listing 7.1: Test-Assembly (C#) - Methode GetStringLength
1 namespace TestLib
2 {
3
public class TestClass
4
{
5
public int GetStringLength ( string s )
6
{
7
return s. Length ;
8
9
// Länge des Strings zurückgeben
}
}
10 }
72
mit SystemAssembly
7.3
Vergleich mit
NUnit
7.4
Zusammenfassung
7 Sollanalyse - Testen des Testsystems
Sollte allerdings null übergeben werden, was ein gültiger Wert für einen String ist, so
wird eine System.NullReferenceException geworfen. Die folgende Abbildung zeigt
die Auflistung der Ergebnisse (siehe Abbildung 7.1):
Abbildung 7.1: Ergebnisse des Tests der Methode GetStringLength
Der Test verlief erfolgreich und der vermeidliche Fehler wurde von AutoTest.Net erkannt. Zwei weitere Methoden, Round und ArrayTest, sollen getestet werden. Die Methode Round beinhaltet eine Umwandlung von einem großen in einen kleineren Datentyp
und erinnert an die Funktion, die die „Ariane 5“-Rakete zum Absturz brachte (vgl. Abschnitt 2.3.2 auf Seite 13). Die Methode ArrayTest iteriert durch ein Array vom Typ
string und verarbeitet die einzelnen Werte. Diese Methoden sind durchaus in der Praxis denkbar und sollen zeigen, dass auch nicht offensichtliche Fehler gefunden werden
können (siehe Listing 7.2):
Listing 7.2: Test-Assembly (C#) - Methoden GetStringLength, Round und ArrayTest
1 using System ;
2 namespace TestLib
3 {
4
public class TestClass
5
{
6
public int GetStringLength ( string s )
7
{
8
return s. Length ;
9
// Länge des Strings zurückgeben
}
10
11
public int Round ( double roundValue )
12
{
13
// Double - Wert nach Ganzzahl runden
return ( int ) Math . Round ( ( decimal ) roundValue );
14
}
15
16
public void ArrayTest ( string [] values )
17
{
// Durchiterieren aller Werte des Arrays
18
foreach ( string item in values )
19
{
20
Console . WriteLine ( item );
21
}
22
23
}
}
24 }
73
7 Sollanalyse - Testen des Testsystems
Der Fehler in der Methode Round ist die Umwandlung von double nach decimal. Für
kleine Werte, die auch im Bereich von decimal liegen, funktionieren die Rundungen.
Für zu große oder zu kleine Werte schlägt diese Methode fehl. Die Methode ArrayTest
stürzt ab, wenn null übergeben wird. Das Resultat der Tests wird von AutoTest.Net wie
folgt dargestellt (siehe Abbildung 7.2):
Abbildung 7.2: Ergebnisse des Tests der Methoden GetStringLength, Round und ArrayTest
Das nächste Beispiel wird in Visual Basic dargestellt. Die Klasse VBTestClass beinhaltet die Methode DateToString, welche ein DateTime-Objekt erwartet und das enthaltene Datum als String zurückgibt (siehe Listing 7.3).
Listing 7.3: Test-Assembly (VB) - Methode DateToString
1 Public Class VBTestClass
2
3
4
Function DateToString ( ByVal dateParam ) ’As DateTime
DateToString = dateParam . Date . ToString ()
End Function
5 End Class
Der Fehler in diesem Beispiel ist die fehlende Typspezifizierung hinter dem Parameter
„dateParam“ („As DateTime“). Der Compiler interpretiert diesen Parameter automatisch
als object, weswegen kein Compilerfehler entsteht. AutoTest.Net erkennt diesen Fehler, weil ein erwartetes Objekt mit new erzeugt und als Parameterwert verwendet wird.
Ein Objekt vom Typ object beinhaltet allerdings keinen Member „Date“, weshalb eine
MissingMemberException geworfen wird (siehe Abbildung 7.3):
Abbildung 7.3: Ergebnisse des Tests der Methode DateToString
Durch das Hinzufügen der Typsicherheit wird diese Methode wesentlich robuster.
74
7 Sollanalyse - Testen des Testsystems
7.2 Testergebnisse mit System-Assembly
Nachdem die ersten Ergebnisse mit simplen Assemblies vorhanden sind, sollen nun
komplexe Objekte aus dem Namespace System getestet werden. Dieser Namespace beinhaltet von insgesamt 1955 Typen exakt 1367 Klassen. Die Klasse ByteConverter wurde
mit den folgenden Resultaten getestet (siehe Abbildung 7.4):
Abbildung 7.4: Ergebnisse der Tests des Objektes ByteConverter
Die Methoden FromString und ToString werfen insgesamt 3 unterschiedliche Exceptions bei ungültigen Parametern. Eine NullReferenceException deutet auf ein
unberücksichtigtes Fehlverhalten hin, wohingegen eine FormatException und eine
InvalidCastException durchaus für diese Parameter beabsichtigt sein könnten. Im
Zweifelsfall muss der Entwickler seine Methoden mit diesen Parametern aufrufen und
den Code verfolgen. Die ArgumentException und ArgumentNullException wurden
hierbei nicht aufgelistet, da sie zu den erwarteten Exceptions gehören.
7.3 Vergleich mit NUnit
Um die beiden Methoden der selbsterstellten Test-Assembly GetStringLength(
string) und Round(double) (siehe Listing 7.2 auf Seite 73) mit NUnit zu testen, muss
mittels des Assistenten ein Test-Projekt erzeugt werden. Das Projekt ist zwar schnell erzeugt, aber das korrekte Einsetzen von Testwerten und erwarteten Werten nimmt relativ
viel Zeit in Anspruch. Das sorgfältige Implementieren aller Testdaten für diese beiden
75
7 Sollanalyse - Testen des Testsystems
Methoden, die in AutoTest.Net hinterlegt sind, dauert ca. 10 Minuten. Mit AutoTest.Net
können diese Fehler in weniger als 10 Sekunden gefunden werden, da die Datei lediglich
geladen und die zu testenden Methoden ausgewählt werden müssen. Im Durchschnitt liegen die Zeiten für Tests einer Klasse mit allen Konstruktoren und Methoden zwischen
wenigen Sekunden und ca. fünf Minuten. Abgesehen vom zeitlichen Aufwand ist auch
der Umfang ein erwähnenswertes Kriterium. Das Listing dieser NUnit-Tests ist aufgrund
der Größe im Anhang zu finden (siehe Listing 1 auf Seite XIII).
7.4 Zusammenfassung
Die Testreihen stellen einen großen Erfolg für AutoTest.Net dar. Der Aufwand, mit
dem Abstürze von Methoden gefunden werden können, ist im Gegensatz zum Aufwand
des Findens der Abstürze mit NUnit minimal. Selbst ein Fehler in Form dessen, der die
„Ariane 5“-Rakete zum Absturz brachte, konnte mit AutoTest.Net identifiziert werden.
Die am häufigsten auftretende Exception ist die NullReferenceException. Besonders
bei String-Parametern wird dies oft übersehen. Tests können auf Knopfdruck ohne besondere Vorbereitung durchgeführt werden und geben dem Entwickler einen guten Anhaltspunkt für nicht bedachte Fälle.
76
Fazit
[8]
8.1
Im Rahmen dieser Arbeit wurden viele Softwarefehler mit teilweise fatalen Auswir-
Zusammen-
kungen vorgestellt und deren Ursachen analysiert. Ebenfalls wurden Fehler aus der Theo-
fassung
rie der Programmierung erörtert. Der Kern der Arbeit beschäftigte sich aber mit der Ent-
8.2
wicklung eines eigenen Testsystems.
Grenzen
8.3
Ausblick
8.1 Zusammenfassung
8.4
Es wurde gezeigt, welche Auswirkungen Softwarefehler haben können. Die Implementierung von AutoTest.Net, das Testsystem, welches potentielle Fehler ohne viel Aufwand finden kann, erfolgte größtenteils ohne Probleme. Nach dem mehrfachen Benutzen
von AutoTest.Net mit unterschiedlichen Testobjekten wurde AutoTest.Net immer weiter
optimiert und benutzerfreundlicher gestaltet. Die Ergebnisse, die mit AutoTest.Net erzielt
werden konnten, waren weitaus aussagekräftiger, als zu Beginn geplant. Die Einfachheit,
mit der die Tests durchgeführt werden können, überragt alle bisher vorgestellten Testwerkzeuge.
8.2 Grenzen
Durch das Verfahren, auf dem AutoTest.Net basiert, ist es nicht möglich, Rückschlüsse
auf die Ausgabewerte, bezogen auf die Eingangswerte, zu ziehen. Somit ist das erfolgreiche Durchlaufen eines Tests bei weitem keine Aussage dafür, dass die Methode ordnungsgemäß arbeitet. Die Rückgabewerte von Methoden werden nicht berücksichtigt.
Weiterhin sind unerwartet auftretende Fenster wie MessageBoxen ein Problem, da so die
Tests unterbrochen werden. Dieses Problem wurde von anderen Testwerkzeugen bereits
gelöst.
77
Schlusswort
8 Fazit
8.3 Ausblick
AutoTest.Net birgt noch viel Potential. Sollte ein Einsatz bei Firmen stattfinden, so
werden sich viele weitere Wünsche an die Software ergeben. Die Liste von optionalen
Features (vgl. Abschnitt 5.2 auf Seite 55) kann zur Komplettierung der Software gänzlich umgesetzt werden. Auch bei Optimierungen gibt es mehrere Möglichkeiten. Bisher
werden alle Tests in einem Thread abgearbeitet. Dies könnte durch mehrere Threads stärker parallelisiert und effizienter gestaltet werden. Die Rückgabewerte von aufgerufenen
Methoden könnten ausgegeben und durch den Tester analysiert werden. Die Anbindung
an datengetriebene Tests mithilfe einer Datenbank könnte ebenfalls umgesetzt werden.
Durch die Anbindung einer Datenbank würde sich aber das 2-Schichten-Modell zu einem 3- oder sogar 5-Schichten-Modell entwickeln, je nach Grad der Komplexität.
8.4 Schlusswort
Die größten Herausforderungen dieser Diplomarbeit waren das Planen von AutoTest.Net sowie das Behandeln der bei der Implementierung auftretenden Ausnahmefälle.
AutoTest.Net kann vielen Entwicklern nützlich sein, ihre Software zu optimieren. Es zeigt
ihnen unbedachte Fälle in ihrer Software, die später beim Benutzer fatale Folgen haben
könnten. Dieses Testsystem liefert einen entscheidenden Beitrag, zukünftige Softwarelösungen, sicherer, effektiver und fehlerfreier zu machen.
„There are no significant bugs in our released software that any significant number of users want fixed.“
Bill Gates
78
Literaturverzeichnis
[Bal05] H. Balzert, Lehrbuch der Objektmodellierung - Analyse und Entwurf mit der
UML 2.0.
Heidelberg: Elsevier - Spektrum Akademischer Verlag, 2005. X,
XI
[Bal08] ——, “Software-Qualitätssicherung,” Ruhr-Universität Bochum, März 2003.
Zugriff: 23 Juni 08. [Online]. Available: http://www.inf.fu-berlin.de/inst/
ag-se/teaching/V-SWT-2003/66_2LE_19tw.pdf 30, 32, 51
[Bas08] V. R. Basili, per Mail, Department of Computer Science and Institute for Advanced Computer Studies - University Maryland, 19. Juni 2008. 8
[CA05] K. Cwalina and B. Abrams, Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series). Addison-Wesley Professional, September 2005. 43
[Dai08] A. Dais, “Softwarequalität und Test von .NET-Anwendungen,” .Net
Developers Group Stuttgart, Mai 2005. Zugriff: 28. Juni 08. [Online]. Available: http://www.devgroup-stuttgart.de/Download/2005-05-25/
DevgroupVortragDais.pdf 33
[Edd08] M. Eddington, “Peach 2 Tutorial,” peachfuzzer.com, Juni 2008. Zugriff:
24. Juni 08. [Online]. Available: http://peachfuzzer.com/docs/Peach%202%
20Tutorial.pdf 41
[Ger08] M. Gerke, “Vorlesung „Software-Engineering“,” FHDW Hannover, Januar
2007. Zugriff: 09 Juni 08. [Online]. Available: http://www.glenesoft.com/
fhdw/OOTest/PrintAll.shtml 9, 18, 19, 23, 25, 30, 32
[Gie08] I. Giese, “Softwarezuverlässigkeit gestern, heute und morgen,” GSI
Darmstadt, Februar 2002. Zugriff: 12 Juni 08. [Online]. Available:
http://www-aix.gsi.de/~giese/swr/allehtml.html 9, 10, 11, 12, 13, 14, VIII
I
[GK08] M. Gallaher and B. Kropp, “The Economic Impacts of Inadequate
Infrastructure for Software Testing,” RTI International for National
Institute of Standards & Technology, Mai 2002 ch. 1 - Introduction of
Software Quality and Testing. Zugriff: 18 Juni 08. [Online]. Available:
http://www.rti.org/abstract.cfm?pid=5272 22
[Gla08] C. Glass, “Berühmt berüchtigte Softwarefehler - Mars Climate Orbiter
und Mars Polar Lander,” Universität Koblenz, September 2003. Zugriff:
15. Juni 08. [Online]. Available: http://www.uni-koblenz.de/~beckert/Lehre/
Seminar-Softwarefehler/Ausarbeitungen/glass.pdf 16
[HT04] A. Hunt and D. Thomas, Pragmatic Unit Testing in C# with Nunit.
The
Pragmatic Programmers; 1st edition, Mai 2004. 41
[Huc08] T. Huckle, “Kleine BUGs, große GAUs - Softwarefehler und ihre Folgen,”
Technische Universität München, März 2003. Zugriff: 09. Juni 08. [Online].
Available: http://www5.in.tum.de/~huckle/bugsn.pdf 13
[IS08] A. S. Ina Schieferdecker, “Mythos „deutsche Qualität“ – schaffen
wir ihn auch für unsere Software zu etablieren?” TU Berllin/Uni
Bremen/Fraunhofer FOKUS, Oktober 2007. Zugriff: 12. Juni 08. [Online].
Available: http://www.cs.tu-berlin.de/frauenportal/data/VortragTestenv2.pdf
12, VIII
[Kea08] D. Kean, per Mail, Microsoft FxCop-Entwickler, 14. März 2008, Microsoft
Code Analysis Team. 42
[Kre08] J. Kresowaty, “FxCop: Writing Your Own Custom Rules (DRAFT),” Januar
2008. Zugriff: 05 Juni 08. [Online]. Available: http://www.binarycoder.net/
fxcop/pdf/fxcop.pdf 42, 43
[Lin85] H. Lin, “The development of software for ballistic-missile defense,” Sci. Am.,
vol. 253, no. 6, pp. 46–53, 1985. 17
[Mer08] A. Mertgen, “Debugging,” Technische Universität Berlin, Juni 2008. [Online].
Available: https://www.isis.tu-berlin.de/course/view.php?id=1016&topic=3 7
[MFS90] B. P. Miller, L. Fredriksen, and B. So, “An Empirical Study of the Reliability
of UNIX Utilities,” Commun. ACM, vol. 33, no. 12, pp. 32–44, 1990. 34, 35
[Mil08] B. P. Miller, per Mail, The University of Wisconsin, 20. Januar 2008. 34
II
[Möl08] R. Möller, “Vorlesung „Software-Engineering“,” Technische Universität
Hamburg, Mai 2004, ch. 6 - Qualität-Metriken-Tests. Zugriff: 15. Juni
08. [Online]. Available: http://www.sts.tu-harburg.de/~r.f.moeller/lectures/
se-ss-04.html 19, 20, 21, 22, 23, 24, 26, 27
[NSS07] T. Northrup, S. J. Stein, and M. A. Stoecker, Anwendungsentwicklung für
Windows-Clients mit .NET Framework 2.0.
Redmond: Microsoft Press,
2007. XI
[NWR06] T. Northrup, S. Wildermuth, and B. Ryan, Grundlagen der Anwendungsentwicklung mit .NET Framework 2.0.
Redmond: Microsoft Press, 2006. 21,
X
[Pfe08] M.
25,”
Pfeifer,
“Berühmt
Universität
berüchtigte
August
Koblenz,
Softwarefehler
2003.
Zugriff:
-
Therac15.
Ju-
ni 08. [Online]. Available: http://www.uni-koblenz.de/~beckert/Lehre/
Seminar-Softwarefehler/Ausarbeitungen/pfeifer.pdf 16
[PGL08] A. K. Patrice Godefroid and M. Y. Levin, “Grammar-based Whitebox
Fuzzing,” Microsoft Research, November 2007. Zugriff: 25. Juni 08.
[Online]. Available: http://research.microsoft.com/research/pubs/view.aspx?
tr_id=1397 42
[Pol08] A. Poller, “Approaches for Automated Software Security Evaluations,” Ph.D.
dissertation, Chemnitz University of Technology, Oktober 2006. Zugriff:
17 Juni 08. [Online]. Available: http://archiv.tu-chemnitz.de/pub/2006/0187/
data/AutoSecEval.pdf 34
[See08] S. Seefeld, “Unit-Test - Theorie und Praxis,” INGTES AG, Nov 2006. Zugriff:
28. Juni 08. [Online]. Available: http://www.ingtes.ch/fileadmin/user_upload/
media/UnitTest_Theorie_und_Praxis.pdf 33
[SGA07] M. Sutton, A. Greene, and P. Amini, Fuzzing: Brute Force Vulnerability Discovery. Addison-Wesley Professional; 1 edition, Juli 2007, ch. 21 - Fuzzing
Frameworks. 38
[Sil08] B. Silberhorn, “White-Box-Test,” Fachhochschule Augsburg, Mai 2006.
Zugriff: 28. Juni 08. [Online]. Available: http://www2.fh-augsburg.de/
informatik/master/vorlesungen/swt/script/ss2006/testen/whitebox.pdf 33
III
[SL02] A. Spillner and T. Linz, Basiswissen Softwaretest. Heidelberg: Dpunkt Verlag, 2002. 24
[Spi08] A. Spillner, “Das W-Modell,” Hochschule Bremen, Mai 2003. Zugriff: 21. Juni 08. [Online]. Available: http://www.gi-hb-ol.de/uta/gi-rg/
WModellSpillner.pdf 28, 29
[sR08] Åsmund Realfsen, “Analyse der Softwarefehler und die Erstellung von
Software zur Qualitätsverbesserung des OpenACS-Assessmentpakets,”
Wirtschaftsuniversität Wien, Juni 2006. Zugriff: 18 Juni 08. [Online]. Available: http://epub.wu-wien.ac.at/dyn/virlib/bakkWI/showentry?ID=
epub-wu-01_aa2 7, 8
[TS04] D. A. K. Timo Schmitt, Ralf Heid, “Grundlagenwissen Modellbasierte Spezifikation und Testen,” Eine Publikation des CBTesten Konsortiums, September
2004. 23, 24, 25, 26, 27
[Vog08] U. Voges, “Definitionen von Begriffen im Kontext ‚Sicherheit (safety)’,” Forschungszentrum Karlsruhe, August 2002. Zugriff: 12. Juni 08.
[Online]. Available: http://www.m-lehrstuhl.de/veranstaltung/GI_WS_07_
02/Voges.doc 7, 19, 20
[vV08] E. van Veenendaal, “Standard glossary of terms used in Software Testing,”
International Software Testing Qualifications Board, Dezember 2007.
Zugriff: 23 Juni 08. [Online]. Available: http://www.istqb.org/downloads/
glossary-current.pdf 19, 20, 28, IX, XI
[Wei08] D. Weinstein, “Fuzzing Fundmentals,” Redmond Developer News, Juni 2007.
Zugriff: 22. Juni 08. [Online]. Available: http://reddevnews.com/techbriefs/
article.aspx?editorialsid=261 36
[Wel08] D. Wells, per Mail, TestComplete-Entwickler, 25. März 2008, AutomatedQA
Entwicklerteam. 48
[Wil04] K. B. Williams, Grace Hopper: Admiral of the Cyber Sea.
brary of Naval Biography, 2004. 6, 7
IV
Annapolis: Li-
Abbildungsverzeichnis
2.1
Erwartete Fehlerrate im Programmcode nach Basili . . . . . . . . . . . .
8
3.1
Allgemeines V-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2
Allgemeines W-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1
NUnit - Erfolgreicher Gesamttest . . . . . . . . . . . . . . . . . . . . . . 39
4.2
NUnit 2008 - Generierung von Testklassen . . . . . . . . . . . . . . . . . 40
4.3
RanorexSpy liest Daten aus . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.4
Properties des Buttons 2 des Windows-Taschenrechners, ausgelesen von
TestComplete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.1
Anwendungsfalldiagramm für AutoTest.Net . . . . . . . . . . . . . . . . 50
5.2
Log-Bereich in AutoTest.Net 2008 . . . . . . . . . . . . . . . . . . . . . 56
5.3
Der Log-Bereich in AutoTest.Net 2008 . . . . . . . . . . . . . . . . . . . 56
5.4
Verwaltungsbereich für die Exceptions in AutoTest.Net 2008 . . . . . . . 57
5.5
Equivalenzklasse für AutoTest.Net . . . . . . . . . . . . . . . . . . . . . 58
5.6
Exceptionverwaltungsklasse für AutoTest.Net . . . . . . . . . . . . . . . 59
5.7
Klassendiagramm für AutoTest.Net . . . . . . . . . . . . . . . . . . . . . 61
6.1
Anwendungsschichten von AutoTest.Net . . . . . . . . . . . . . . . . . . 62
6.2
GUI von AutoTest.Net . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.3
Erstes Layout von AutoTest.Net 2008 . . . . . . . . . . . . . . . . . . . . 64
6.4
Unterstützende Snaplines beim Bau einer Maske . . . . . . . . . . . . . 64
6.5
Maske zur Analyse der Assembly . . . . . . . . . . . . . . . . . . . . . . 65
6.6
Darstellung von gruppierten Ergebnissen . . . . . . . . . . . . . . . . . . 67
6.7
Hinzufügen eines Wertes zu einer Äquivalenzklasse . . . . . . . . . . . . 69
6.8
Analyse des Backgroundworkers mit AutoTest.Net 2008 . . . . . . . . . . 70
7.1
Ergebnisse des Tests der Methode GetStringLength . . . . . . . . . . . . 73
7.2
Ergebnisse des Tests der Methoden GetStringLength, Round und ArrayTest 74
V
7.3
Ergebnisse des Tests der Methode DateToString . . . . . . . . . . . . . . 74
7.4
Ergebnisse der Tests des Objektes ByteConverter . . . . . . . . . . . . . 75
VI
Tabellenverzeichnis
2.1
Programme mit der Anzahl der Quellcodezeilen und Fehler . . . . . . . .
3.1
Liste der Qualitätsmerkmale . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2
Liste der beim Test 1990 abgestürzten Programme . . . . . . . . . . . . . 35
3.3
Liste der getesteten Systeme . . . . . . . . . . . . . . . . . . . . . . . . 35
5.1
Liste der optionalen Features . . . . . . . . . . . . . . . . . . . . . . . . 55
VII
9
Listings
1.1
Beispiel für Property . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2
Beispiele für ein Attribut . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.3
Beispiele für Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.1
Ausschnitt aus dem FORTRAN-Steuerprogramm von Mariner 1 (Gie08) . 11
2.2
Beispiele für Tippfehler und deren Auswirkungen (IS08) . . . . . . . . . 12
2.3
Beispiele für Tippfehler in C-Schleifen . . . . . . . . . . . . . . . . . . . 12
2.4
Auszug aus dem Trägheitsnavigationssystem der „Ariane 5“ (Gie08) . . . 13
2.5
Beispiel für sichere Handhabung von Überläufen . . . . . . . . . . . . . 15
4.1
Beispiel eines TestComplete-Tests des Taschenrechners, ob 2 + 5 = 7 ist . 47
5.1
Singleton-Pattern der ExceptionManagement-Klasse . . . . . . . . . . . 59
6.1
Speichern aller Werte einer Enumeration . . . . . . . . . . . . . . . . . . 66
6.2
Gruppierung von Objekten mit LINQ . . . . . . . . . . . . . . . . . . . 67
6.3
ExceptionManagement. Auflistung aller Exceptions des Namespace System 68
6.4
Zugriff auf interne eingebettete Ressourcen . . . . . . . . . . . . . . . . 68
7.1
Test-Assembly (C#) - Methode GetStringLength . . . . . . . . . . . . . . 72
7.2
Test-Assembly (C#) - Methoden GetStringLength, Round und ArrayTest . 73
7.3
Test-Assembly (VB) - Methode DateToString . . . . . . . . . . . . . . . 74
1
NUnit-Klasse, welche die Testmethoden mit allen Werten der Äquivalenzklassen testet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XIII
VIII
Glossar
A
Äquivalenzklasse
Eine Menge von Testdaten, bei der jeder Wert als Eingabe für ein
Testobjekt gleichartiges Sollverhalten zeigt (Äquivalenzklasse von Eingabewerten) (vV08), S. 23.
Assembly
Eine mit .NET übersetzte Datei, welche typischerweise die Endung .exe
oder .dll trägt, S. 2.
Attribut
Ein Attribut ist eine Metainformation und kann auf Klassen, Methoden oder
Properties angewendet werden, S. 4.
C
Compiler
Ein .Net-Sprachcompiler übersetzt den Quellcode in eine platformunabhängige Zwischensprache, vergleichbar mit Java-Bytecode. Diese Sprache wird
als CIL (Common Intermediate Language), MSIL (Microsoft Intermediate Language) oder einfach IL (Intermediate Language) bezeichnet. Bei der
Ausführung übersetzt ein JIT-Compiler (Just In Time-Compiler) den in der
Zwischensprache vorliegenden Quellcodes der Assemblies in Maschinencode, S. 3.
E
Exception
Eine Exception ist ein Objekt, welches bei unerwarteten Ereignissen geworfen wird und die normale Ausführung unterbricht. Dieses Objekt muss später behandelt werden, da sonst die gesamte Ausführung abgebrochen wird.
Alle Exception-Objekte haben die Klasse Exception als Basistyp, S. 14.
IX
F
Fuzzing
Eine Technik, um mit zufällig generierten Werten, welche allerdings einer
bestimmten Logik unterliegen, eine Software zum Fehlverhalten zu führen,
S. ii.
H
Handle
Ein Handle ist ein Objekt, welches genutzt wird, um andere Objekte zu steuern. Häufig wird ein Handle genutzt, um Ressourcen zu initialisieren oder
freizugeben, S. 33.
I
Introspection
Eine Technik, welche ähnlich arbeitet wie Reflection, allerdings mit wei-
terführenden Möglichkeiten der Assemblyanalyse, S. 5.
N
Namespace
Ein Namespace (dt. Namensraum) ist in .NET sowie in der UML ein gebräuchlicher Begriff. Es handelt sich um einen Bereich, in dem jedes Element einen eindeutigen Namen besitzen muss. In unterschiedlichen Namespaces kann der gleiche Name in unterschiedlicher Bedeutungen verwendet
werden (Bal05, S. 538), S. 3.
P
Properties
Eine Property ist ein Bestandteil eines Objektes in .NET. Properties sind
interne Felder, welche gekapselt und nach außen geführt werden, S. 3.
R
Reflection
Eine Fähigkeit des .NET Frameworks, um erstens Typeninformationen im
Typensystem abzufragen und auszuwerten, und zweitens Code dynamisch
zu erstellen (NWR06, S. 761), S. 4.
X
S
Singleton
Das Singleton-Muster (engl. singleton pattern) ist ein objektbasiertes Erzeugungsmuster, welches sicherstellt, dass genau eine Instanz einer Klasse
existiert, auf die global zugegriffen werden kann. Diese Klassen haben nur
private Konstruktoren (Bal05, S. 544), S. 59.
Snapline
Hilfslinien, die von Microsoft Visual Studio zur Unterstützung für die genauere Anordnung von Steuerelementen angezeigt werden (NSS07, S. 677),
S. 64.
T
Testobjekt
Ein zu testendes Softwareobjekt (Funktion/Methode, Modul/Klasse, Komponente/Teilsystem, System). Das Testobjekt soll für den Test nicht verändert werden und alle nötigen Umgebungskomponenten sollten bereits ausreichend getestet sein (vV08), S. 19.
Testorakel
Informationsquelle zur Ermittlung der jeweiligen Sollergebnisse eines Testfalls (kann beispielsweise die Anforderungsdefinition sein) (vV08), S. 18.
Testtreiber
Programm bzw. Werkzeug, das es ermöglicht, ein Testobjekt ablaufen zu
lassen, mit Testdaten zu versorgen und Ausgaben/Reaktionen des Testobjekts entgegenzunehmen (vV08), S. 19.
U
UML
UML (Unified Modeling Language) ist eine als Standard akzeptierte Modellierungssprache, die graphische Notationen für statische Strukturen und
dynamische Abläufe in Software enthält (Bal05, S. 546), S. 4.
XI
Index
Äquivalenzklasse, IX, XIII, 23, 24, 49,
Testorakel, XI, 18, 19, 33
50, 52, 54, 56–58, 60, 63, 65–
Testtreiber, XI, 19, 33, 36, 37
69
UML, X, XI, 4, 49
Assembly, IX, 2–4, 38, 42, 43, 49–52,
57, 65, 66, 70, 72, 75
Attribut, IX, 4, 39
Compiler, IX, 3, 6, 10, 11, 14
Exception, IX, 14, 50, 52–54, 56, 59,
65–68, 75, 76
Fuzzing, ii, X, 2, 3, 30, 34, 36, 41, 42,
49
Handle, X, 33
Introspection, X, 5, 42, 43
Namespace, X, 3, 5, 53, 54, 56, 62, 68,
75
Properties, IX, 3, 4, 39, 41, 43, 46, 60,
65
Property, X
Reflection, X, 4, 5, 42, 43, 49, 66
Singleton, XI, 59, 60
Snapline, XI, 64
Testobjekt, XI, 19, 20, 33, 41, 67
XII
Anhang
Listings
Listing 1: NUnit-Klasse, welche die Testmethoden mit allen Werten der Äquivalenzklassen testet
1 using TestLib ;
2 using Microsoft . VisualStudio . TestTools . UnitTesting ;
3 namespace TestProject1
4 {
5
[ TestClass () ]
6
public class TestClassTest
7
{
8
private TestContext testContextInstance ;
9
10
public TestContext TestContext
11
{
12
get
13
{
14
return testContextInstance ;
15
}
16
set
17
{
18
testContextInstance = value ;
19
20
}
}
21
22
[ TestMethod () ]
23
public void GetStringLengthTest ()
24
{
25
TestClass target = new TestClass () ; // TODO : Passenden Wert
26
string s = " muh und bla "; // TODO : Passenden Wert initialisieren
27
int expected = 11; // TODO : Passenden Wert initialisieren
28
int actual ;
29
actual = target . GetStringLength ( s );
30
Assert . AreEqual ( expected , actual );
initialisieren
31
XIII
32
s = ""; // TODO : Passenden Wert initialisieren
33
expected = 0; // TODO : Passenden Wert initialisieren
34
actual = target . GetStringLength ( s );
35
Assert . AreEqual ( expected , actual );
36
37
s = "0"; // TODO : Passenden Wert initialisieren
38
expected = 1; // TODO : Passenden Wert initialisieren
39
actual = target . GetStringLength ( s );
40
Assert . AreEqual ( expected , actual );
41
42
s = " 54256246542 "; // TODO : Passenden Wert initialisieren
43
expected = 11; // TODO : Passenden Wert initialisieren
44
actual = target . GetStringLength ( s );
45
Assert . AreEqual ( expected , actual );
46
47
s = " string "; // TODO : Passenden Wert initialisieren
48
expected = 6; // TODO : Passenden Wert initialisieren
49
actual = target . GetStringLength ( s );
50
Assert . AreEqual ( expected , actual );
51
52
s = " STRING "; // TODO : Passenden Wert initialisieren
53
expected = 6; // TODO : Passenden Wert initialisieren
54
actual = target . GetStringLength ( s );
55
Assert . AreEqual ( expected , actual );
56
57
s = " String with spaces "; // TODO : Passenden Wert initialisieren
58
expected = 18; // TODO : Passenden Wert initialisieren
59
actual = target . GetStringLength ( s );
60
Assert . AreEqual ( expected , actual );
61
62
s = null ; // TODO : Passenden Wert initialisieren
63
expected = 0; // TODO : Passenden Wert initialisieren
64
actual = target . GetStringLength ( s );
65
66
Assert . AreEqual ( expected , actual );
}
67
68
[ TestMethod () ]
69
public void RoundTest ()
70
{
71
TestClass target = new TestClass () ; // TODO : Passenden Wert
72
double roundValue = 0F; // TODO : Passenden Wert initialisieren
73
int expected = 0; // TODO : Passenden Wert initialisieren
74
int actual ;
75
actual = target . Round ( roundValue );
76
Assert . AreEqual ( expected , actual );
initialisieren
77
78
roundValue = 1.0; // TODO : Passenden Wert initialisieren
79
expected = 1; // TODO : Passenden Wert initialisieren
80
actual = target . Round ( roundValue );
81
Assert . AreEqual ( expected , actual );
XIV
82
83
roundValue = -1.0; // TODO : Passenden Wert initialisieren
84
expected = -1; // TODO : Passenden Wert initialisieren
85
actual = target . Round ( roundValue );
86
Assert . AreEqual ( expected , actual );
87
88
roundValue = -1.7976 E +308; // TODO : Passenden Wert initialisieren
89
expected = 1000; // TODO : Passenden Wert initialisieren
90
actual = target . Round ( roundValue );
91
Assert . AreEqual ( expected , actual );
92
93
}
}
94 }
XV
DVD Inhalt
/DOT.NET 3.5
/dotNetFx35setup.exe
/Quellen
/bin
/AutoTest.Net.exe
/src
/AutoTest.Net.sln
/Diplomarbeit.pdf
Zum Ausführen notwendiges .NET-Framework 3.5
Installationsdatei für Microsoft .NET 3.5 Framework
Benutze, digital vorhandene Quellen
vorkompilierte Anwendung
Startdatei für AutoTest.Net
Quellcode zu AutoTest.Net
Projektdatei für das Projekt AutoTest.Net
Diplomarbeit in digitaler Form
Um die Anwendung ausführen zu können, ist eine Installation des Microsoft .NETFramework 3.5 erforderlich. Für das Öffnen des Projektes und das Kompilieren des
Quellcodes ist zudem eine Installation von Microsoft Visual Studio 2008 nötig. Eine Bedienungsanleitung für die Software befindet sich in der Software unter dem Menupunkt
„Hilfe“.
XVI