Programmiersprachen: Konzepte, Entwicklung & Testverfahren
Eingeordnet in Informatik
Geschrieben am in Deutsch mit einer Größe von 24,88 KB
In der Codierungsphase werden die Quellprogramme entwickelt.
Programmiersprachen: Generationen & Merkmale
Generationen von Programmiersprachen
Sprachen der ersten Generation (1GL)
Sprachen der ersten Generation (Maschinensprachen) haben ein sehr niedriges Abstraktionsniveau und sind direkt an die Hardware-Architektur des Computers gebunden.
Sprachen der zweiten Generation (2GL)
Sprachen der zweiten Generation (Assemblersprachen) sind ebenfalls Low-Level, bieten aber symbolische Repräsentationen von Maschineninstruktionen und sind etwas unabhängiger von der spezifischen Computerstruktur. Sie waren die ersten Sprachen, die eine gewisse Abstraktion boten. Beispiele sind frühe Assembler.
Sprachen der dritten Generation (3GL)
Dies sind Hochsprachen, die problemorientierter sind und eine höhere Abstraktionsebene bieten. Beispiele sind Fortran, COBOL, Algol und BASIC. Man unterscheidet zwei Hauptansätze:
1. Imperative Programmierung
Stark typisierte Sprachen, bei denen eine klare Unterscheidung zwischen der Deklaration und Verwendung jedes Datentyps besteht, was die Überprüfung auf mögliche Inkonsistenzen ermöglicht.
- Pascal: Starre Typisierung, defiziente separate Kompilierung.
- Modula-2: Trennt die Spezifikation des Moduls von seiner Implementierung, ermöglicht sichere Modul-Erstellung und verbirgt Implementierungsdetails.
- C: Flexibel, wenige Einschränkungen bei Datentypen.
- Ada: Ermöglicht die Definition generischer Elemente, parallele Programmierung sowie Synchronisation und Zusammenarbeit von Tasks.
2. Objektorientierte, funktionale oder logische Programmierung
Diese Sprachen sind oft auf das jeweilige Anwendungsfeld ausgerichtet:
- Smalltalk: Vorreiter der objektorientierten Programmiersprachen.
- C++: Erweitert C um Konzepte wie Klassen, Vererbung und Polymorphismus.
- Eiffel: Eine vollständig objektorientierte Sprache, die generische Klassen, Mehrfachvererbung und Polymorphismus erlaubt.
- Lisp: Vorreiter der funktionalen Sprachen. Daten werden in Listen organisiert und von meist rekursiven Funktionen verarbeitet.
- Prolog: Vertreter der logischen Sprachen, oft im Bau von Expertensystemen verwendet.
Sprachen der vierten Generation (4GL)
Diese Sprachen bieten eine noch höhere Abstraktionsebene. Sie sind in der Regel nicht für allgemeine Zwecke gedacht und werden nicht für die Entwicklung komplexer Anwendungen empfohlen, da sie oft ineffizienten Code generieren. Ihre Anwendung richtet sich nach praktischen Bedürfnissen:
- Datenbanken: Benutzer können Reports, Listen und Zusammenfassungen bei Bedarf selbst generieren (z.B. SQL).
- Programm-Generatoren: Ermöglichen die Erstellung einfacher abstrakter Elemente in einem bestimmten Anwendungsbereich, ohne die in 3GL-Sprachen benötigten Details. Die meisten Management-Anwendungen wurden in COBOL generiert. In jüngerer Zeit wurden CASE-Tools für objektorientiertes Design entwickelt, die für Anwendungen verwendet werden können und Programme in C, C++ oder Ada erzeugen.
- Berechnung: Tabellenkalkulationen, Simulation und Steuerungsdesign.
- Sonstige: Werkzeuge für die formale Spezifikation und Verifikation von Programmen, Simulationssprachen, Prototyping-Sprachen.
Merkmale von Programmiersprachen
Kontrollstrukturen
Strukturierte Programmierung umfasst:
- Sequenz
- Allgemeine Auswahl (if-then-else)
- Fallauswahl (switch-case)
- Wiederholungsschleifen (while, for)
- Schleifenzähler
- Endlosschleifen
- Definition von Unterprogrammen (Funktionen und Prozeduren), die oft rekursiv definiert werden können, was zu klarerem und einfacherem Code führt.
Ausnahmebehandlung (Exception Handling)
Ausnahmen sind Fehler oder außergewöhnliche Ereignisse während der Programmausführung. Ursachen können sein:
- Menschliche Fehler: Falsche Dateneingabe, die z.B. zu einer Division durch Null führt.
- Hardware-Fehlfunktionen: Störungen oder begrenzte Ressourcenkapazität.
- Software-Fehler: Defekte in der Software, die in Tests nicht entdeckt wurden.
- Fehlerhafte Eingabedaten: Werte außerhalb des zulässigen Bereichs.
Eine korrigierende Maßnahme des Betriebssystems ist oft der Programmabbruch, um Schlimmeres zu verhindern. Dies ist jedoch meist nicht akzeptabel. Stattdessen wird die Programmausführung zu einer geeigneten Fehlerbehandlungsroutine umgeleitet. In Ada gibt es vordefinierte Behandlungen für einige Ausnahmen. Die Behandlung analysiert die Ursache der Ausnahme, meldet sie und stellt den Systemzustand wieder her, um die Ausführung fortzusetzen. In Ada kann nach der Behandlung die Ausführung der betroffenen Einheit komplett übersprungen werden. In PL/1 wird die Ausführung sofort nach dem Punkt wieder aufgenommen, an dem die Ausnahme ausgelöst wurde. Tritt eine Ausnahme in einer Einheit ohne geplante Behandlung auf, propagiert sie zur aufrufenden Einheit. Diese Ausbreitung setzt sich fort, bis die äußerste Einheit erreicht ist, wo dann eine Standardbehandlung erfolgt, falls keine andere definiert ist.
Nebenläufigkeit (Concurrency)
Jede Sprache handhabt Nebenläufigkeit unterschiedlich:
- Koroutinen: Aufgaben, die Applets ähneln und sich die Ausführungskontrolle gegenseitig übergeben. Verwendet in Modula-2.
- Fork-Join: Eine Aufgabe kann die gleichzeitige Ausführung einer anderen Aufgabe mittels
fork
starten. Die Nebenläufigkeit endet mit einemjoin
, das von derselben Aufgabe aufgerufen wird, die denfork
ausgeführt hat, wodurch beide Aufgaben wieder zusammengeführt werden. Verwendet in UNIX und PL/1. - COBEGIN-COEND: Alle Aufgaben, die gleichzeitig laufen sollen, werden innerhalb von
COBEGIN
undCOEND
deklariert. Die Nebenläufigkeit endet, wenn alle Aufgaben beendet sind. Verwendet in Algol68. - Prozesse: Jede Aufgabe wird als Prozess deklariert. In einigen Systemen (z.B. Concurrent Pascal) laufen alle deklarierten Prozesse parallel seit Programmstart, und es können keine neuen gestartet werden. In Ada kann ein Prozess jederzeit gestartet werden.
Zur Synchronisation und Zusammenarbeit zwischen Aufgaben gibt es:
- Mechanismen bei gemeinsamem Speicher (Tasks laufen auf demselben Computer (Multiprogramming) oder verschiedenen Computern mit gemeinsamem Speicher (Multiprocessing)):
- Semaphoren
- Bedingte kritische Regionen
- Monitore
- Nachrichtenaustausch (Message Passing) (Tasks laufen auf Computern ohne gemeinsamen Speicher, z.B. verteilte Prozesse in einem Netzwerk):
- Communicating Sequential Processes (CSP)
- Remote Procedure Calls (RPC)
- Ada Rendezvous
Datenstrukturen
Einfache Datentypen
Beispiele sind: Integer, Real, Aufzählungstypen. Teilbereichstypen (Subrange Types) können den Wertebereich eines ordinalen Datentyps einschränken, um einen neuen Typ zu erstellen.
Zusammengesetzte Datentypen
Beispiele sind: Verbünde (Records/Structs), Felder (Arrays), Vektoren, dynamische Daten (Zeiger).
Konstanten
Es können benannte symbolische Konstanten deklariert werden. In Modula-2 können zusammengesetzte Typen nicht als Konstanten deklariert werden, was in Ada jedoch möglich ist.
Typüberprüfung (Type Checking)
Die Operationen, die mit Daten eines Programms durchgeführt werden können, hängen vom Grad der Typüberprüfung ab. Es gibt 5 Stufen:
- Stufe 0 (Ohne Typen): Sprachen, in denen keine neuen Datentypen deklariert werden können (z.B. BASIC, COBOL, Lisp, APL).
- Stufe 1 (Automatische Typisierung): Der Compiler entscheidet, welcher Typ am besten für die einzelnen Daten geeignet ist und ändert ihn bei Bedarf.
- Stufe 2 (Schwache Typisierung): Konvertierung ist ebenfalls automatisch, aber nur zwischen ähnlichen Typen.
- Stufe 3 (Starre Typisierung): Daten müssen vorher deklariert werden. Ein "Fluchtweg" ist die separate Kompilierung, bei der deklarierte Typen in einem Modul von denen in einem anderen Modul abweichen können (z.B. Pascal).
- Stufe 4 (Starke Typisierung): Es gibt kein Entrinnen; der Programmierer muss alle Typkonvertierungen explizit vornehmen. Die Typüberprüfung erfolgt beim Kompilieren, Laden und Ausführen (z.B. Ada, Modula-2).
Abstraktionen und Objekte
Funktionale Abstraktion
In allen Sprachen müssen Name und Schnittstelle einer Abstraktion definiert werden. Es bedarf des Codes, der die Operation durchführt, und eines Mechanismus zur Verwendung der Abstraktion. Normalerweise verbirgt jede Sprache die Implementierung der Operation vor denen, die sie verwenden. Pascal, Modula-2 und Ada haben Block-Sichtbarkeit, d.h., innere Blöcke können Daten in äußeren Blöcken sehen oder ändern. In Fortran sind Abstraktionen von innen nach außen und umgekehrt völlig undurchsichtig.
Abstrakte Datentypen (ADT)
Daten (Attribute der Datenabstraktion) und die Operationen zu ihrer Verwaltung sollten gruppiert werden. Es muss einen Mechanismus zur Datenkapselung geben, der den direkten Zugriff auf interne Details verhindert.
Objekte
Modula-2 verfügt nicht über Mechanismen für Vererbung oder Polymorphismus, was die Nutzung im objektorientierten Design erschwert. Ada unterstützt Polymorphismus durch Überladen, aber nicht durch späte Bindung (delayed binding), was die objektorientierte Programmierung kompliziert. Geeignete Sprachen für objektorientierte Programmierung sind: Smalltalk, Object Pascal, Eiffel, Objective-C, C++.
Modularität
Eine grundlegende Eigenschaft ist die separate Kompilierung, die es ermöglicht, den Code für jedes Modul separat zu kompilieren. Eine sichere Kompilierung überprüft, ob die Verwendung eines Elements mit seiner Definition übereinstimmt. Fortran und Pascal bieten eine unsichere Kompilierung. Ada und Modula-2 ermöglichen eine sichere Kompilierung. In Modula-2 werden Schnittstellen in Definitionsmodulen (DEFINITION MODULE
) und Implementierungen in Implementierungsmodulen (IMPLEMENTATION MODULE
) codiert. In Ada gibt es Paketspezifikationen (package
) und Paketkörper (package body
). ANSI-C-Compiler kompilieren nicht vollständig sicher, was Raum für Fehler lässt, insbesondere wenn unterschiedliche Schnittstellenmodule mit der Kopie im Programm verwendet werden.
Kriterien zur Sprachauswahl
Wichtige Kriterien sind:
- Anforderungen des Kunden / Anwendungstyp: Neue Sprachen sind oft mit einem bestimmten Anwendungsbereich verbunden. Assembler sollte nur für Codeteile verwendet werden, die spezielle Hardware erfordern.
- Verfügbarkeit und Entwicklungsumgebung: Prüfen, welche Compiler für die Zielhardware verfügbar sind. Leistungsfähigere Werkzeuge erleichtern die Entwicklung.
- Vorhandene Erfahrung (Experience): Die Erfahrung des Entwicklungsteams mit bestimmten Sprachen.
- Wiederverwendbarkeit: Verfügbarkeit von Bibliotheken und Werkzeugen zu deren Organisation.
- Portabilität: Hängt von der Existenz eines Sprachstandards ab, der von allen Compilern unterstützt wird.
- Verwendung mehrerer Sprachen: Ist nicht ratsam, kann aber manchmal die Codierung erleichtern.
Methodische Aspekte der Programmierung
Codierungsstandards und Stilrichtlinien
Es sollten Regeln für einen einheitlichen Codierungsstil festgelegt werden, die vom gesamten Team eingehalten werden. Diese müssen folgende Angaben enthalten:
- Format und Inhalt der Header jedes Moduls.
- Format und Inhalt für jede Art von Kommentar.
- Verwendung von Namen (Namenskonventionen).
- Einrückungsstil (Encolumnado).
Zusätzlich sollten folgende Einschränkungen festgelegt werden:
- Maximale Anzahl von Unterprogrammen pro Modul.
- Maximale Verschachtelungstiefe von Unterprogrammen.
- Vermeidung von übermäßig vielen Argumenten für Funktionen/Prozeduren.
Standardisierte Fehlerbehandlung
Begriffe:
- Softwarefehler (Defect/Bug/Errata): Ein Element des Programms ist nicht korrekt und verursacht ein (teilweise) falsches Ergebnis.
- Fehlerzustand (Error): Ein unzulässiger Zustand eines Programms, der als Ergebnis eines Defekts erreicht werden kann.
- Ausfall (Failure): Das Programm liefert nicht das erwartete Ergebnis oder Verhalten.
Haltungen zur Fehlerbehandlung:
- Keine Fehler annehmen: Es wird vorausgesetzt, dass die eingegebenen Daten korrekt sind und das Programm keine Mängel aufweist. (Dies ist unrealistisch).
- Fehlervermeidung (Defensive Programmierung): Das Programm behandelt eingegebene Daten und Argumente systematisch als verdächtig. Es verhindert falsche Ergebnisse oder führt nicht zu fehlerhaften Zuständen. Ein Vorteil ist die Vermeidung der Fehlerfortpflanzung und die erleichterte Diagnose.
- Fehlerbehebung: Das Programm wird in einen korrekten Zustand zurückversetzt, und die Fehlerfortpflanzung wird verhindert. Dies umfasst zwei Schritte:
- Fehlererkennung: Es muss definiert werden, welche Situationen als fehlerhaft gelten, und entsprechende Überprüfungen an bestimmten Programmpunkten müssen implementiert werden.
- Fehlerwiederherstellung:
- Vorwärtsgerichtete Fehlerkorrektur (Forward Recovery): Zeigt Art und Natur des Fehlers an. Es werden Maßnahmen ergriffen, um den Programmzustand zu korrigieren und die Ausführung fortzusetzen. Diese Regelung kann durch den Mechanismus der Ausnahmebehandlung programmiert werden.
- Rückwärtsgerichtete Fehlerkorrektur (Backward Recovery): Korrigiert den Programmzustand, indem es ihn in einen Zustand vor dem Auftreten des Fehlers zurückversetzt. Dies ist der Modus Operandi von transaktionsbasierten Systemen, bei denen Datenkonsistenz gewahrt bleibt.
Systeme, die eine Vorhersage oder Fehlerbehandlung implementieren, werden als fehlertolerant bezeichnet.
Wirtschaftliche Aspekte
Bei der aktuellen Leistungsfähigkeit von Hardware sollte Klarheit im Code nicht zugunsten geringfügiger Effizienzsteigerungen geopfert werden.
Speichereffizienz
Es ist oft ausreichend, einen Compiler mit Optionen zur Speicherkomprimierung zu verwenden und zeiteffiziente Algorithmen einzusetzen.
Zeiteffizienz
Speziell in Echtzeitsystemen ist die Verwendung effizienter Algorithmen entscheidend. Manchmal wird eine geringere Speichereffizienz in Kauf genommen, um die Zeiteffizienz durch Techniken zu verbessern wie:
- Tabellarisierung komplexer Berechnungen.
- Verwendung von Makros.
- Schleifenabwicklung (Loop Unrolling).
- Herausziehen von invariantem Code aus Schleifen (Loop-invariant code motion).
- Vermeidung von Fließkommaoperationen, wenn möglich.
Softwareportabilität
Faktoren sind die Anwendung von Standards und die Isolation hardwarespezifischer Teile in dedizierten Modulen.
Techniken für Modultests (Unit Tests)
Bei kritischer Software können die Testkosten erheblich sein. Es müssen Modultests, Integrationstests und vollständige Systemtests durchgeführt werden.
Ziele von Softwaretests
Das Hauptziel ist, Fehlfunktionen und Defekte im Programm aufzudecken. Es wird angestrebt, mit geringstem Aufwand die größtmögliche Anzahl von Defekten zu finden. Tests sollten automatisiert werden, was die Erstellung einer Testumgebung erfordert, die bei jedem Durchlauf vordefinierte, wiederholbare Bedingungen gewährleistet. Man unterscheidet Black-Box-Tests und Clear-Box-Tests (White-Box-Tests).
Black-Box-Tests
Diese basieren auf der Input-Output-Spezifikation der Software, ohne Kenntnis der internen Struktur. Es ist oft die einzige Strategie, die der Kunde nachvollziehen kann. Um eine umfassende und kohärente Prüfung zu gewährleisten, helfen folgende Mittel bei der Entwicklung von Testfällen:
- Äquivalenzklassenpartitionierung: Eingabedaten werden in Äquivalenzklassen eingeteilt, und für jede Klasse werden Testfälle definiert.
- Grenzwertanalyse: Konzentriert sich auf Bereiche nahe den Grenzen des Eingabe-/Ausgaberaums, da hier Fehler oft schwerwiegende Auswirkungen haben können (z.B. Ressourcenanfragen an Grenzen, die nicht berücksichtigt wurden).
- Versionsvergleich: Verschiedene Versionen desselben Programms, entwickelt von unterschiedlichen Programmierern, werden denselben Tests unterzogen. Die Ergebnisse werden verglichen. Wenn die Ergebnisse übereinstimmen, kann eine der Versionen ausgewählt werden. Dies bietet keine absolute Sicherheit, da ein Fehler in der Spezifikation zu gleichen Fehlern in allen Versionen führen kann.
- Nutzung von Intuition: Personen außerhalb der Modulentwicklung bringen oft eine distanziertere und frische Perspektive ein.
Clear-Box-Tests (White-Box-Tests)
Diese Tests berücksichtigen die interne Struktur des Moduls. Ziel ist es, das Programm alle möglichen Ausführungspfade durchlaufen zu lassen und alle Codeelemente zu aktivieren. Wenn nur Black-Box-Tests durchgeführt werden, bleiben viele Pfade ungetestet. Black-Box- und Clear-Box-Tests sollten sich ergänzen und niemals ausschließen.
Methoden der logischen Abdeckung:
- Bestimmung einer Reihe von Basispfaden, die alle Zeilen des Flussdiagramms abdecken. Die Anzahl der Basispfade (N) kann oft durch die Formel
N = Anzahl der einfachen Prädikate + 1
bestimmt werden.
Es gibt verschiedene Abdeckungsgrade:
- Level 1 (Anweisungsüberdeckung / Statement Coverage): Testfälle werden entwickelt, um jeden Basispfad mindestens einmal separat auszuführen (jede Anweisung wird mindestens einmal ausgeführt).
- Level 2 (Zweigüberdeckung / Branch Coverage): Testfälle werden entwickelt, um alle Kombinationen der wichtigsten Pfade paarweise auszuführen (jeder Zweig einer Entscheidung wird mindestens einmal durchlaufen).
- Level 3 (Pfadüberdeckung / Path Coverage): Testfälle decken eine erhebliche Anzahl möglicher Pfadkombinationen ab. Ein Mindesttest jedes Moduls muss Level 1 gewährleisten.
Clear-Box-Tests können jedoch nicht das Fehlen eines Codefragments erkennen.
Schleifentests (Loop Testing)
Es müssen Tests für verschiedene Schleifenszenarien entwickelt werden:
- Schleifen mit unbegrenzter Anzahl von Wiederholungen: Führen Sie die Schleife 0, 1, 2, einige und viele Male aus.
- Schleifen mit maximaler Anzahl (M) von Wiederholungen: Führen Sie die Schleife 0, 1, 2, einige, M-1, M, M+1 Mal aus.
- Verschachtelte Schleifen:
- Testen Sie die innerste Schleife, während alle äußeren Schleifen eine minimale Anzahl von Iterationen durchlaufen.
- Für die nächste Verschachtelungsebene: Äußere Schleifen mit minimaler Anzahl von Iterationen, die aktuell betrachtete Schleife mit typischen Werten und innere Schleifen mit minimalen Werten.
- Wiederholen, bis alle Ebenen abgedeckt sind.
- Verkettete Schleifen: Wenn sie voneinander unabhängig sind, testen Sie sie separat nach den oben genannten Kriterien.
Auch hier ist die Nutzung von Intuition hilfreich.
Schätzung unentdeckter Fehler
Es ist unmöglich zu beweisen, dass ein Modul fehlerfrei ist. Um die Anzahl der unentdeckten Mängel zu schätzen (Fehler-Seeding-Methode):
- Notieren Sie die Anzahl der Fehler (Einitial), die initial bei Tests gefunden werden.
- Korrigieren Sie diese Fehler.
- Führen Sie eine bekannte Anzahl künstlicher Fehler (Eseeded) in das Modul ein.
- Unterziehen Sie diese modifizierte Version des Moduls denselben Testfällen (oder einem neuen Satz) und zählen Sie die Anzahl der gefundenen künstlichen Fehler (Efound_seeded) sowie die Anzahl neu gefundener echter Fehler (Efound_new).
- Unter der Annahme, dass das Verhältnis der gefundenen zu den gesamten künstlichen Fehlern dem Verhältnis der gefundenen zu den gesamten echten Fehlern entspricht, kann man die Anzahl der verbleibenden unentdeckten Fehler schätzen.
Wennk = Efound_seeded / Eseeded
die Entdeckungsrate ist, dann ist die geschätzte Gesamtzahl der initialen FehlerEtotal_initial = Einitial / k
.
Die Anzahl der verbleibenden Fehler wäre dannEremaining = Etotal_initial - Einitial
.
Hinweis: Die ursprüngliche Beschreibung war etwas unklar, dies ist eine gängige Interpretation der Fehler-Seeding-Methode.
Integrationsstrategien
Diese dienen der Bereinigung von Fehlern, die bei der Integration verschiedener Module auftreten. Es gibt mehrere Möglichkeiten:
- Big-Bang-Integration: Die Integration aller Module erfolgt in einem einzigen Schritt. (Hohes Risiko, schwierige Fehlersuche)
- Absteigende Integration (Top-Down): Man beginnt mit einem Hauptmodul. Untergeordnete Module werden zunächst durch Platzhalter (Stubs) simuliert. Diese Stubs werden dann schrittweise durch die realen Module ersetzt und getestet.
- Vorteil: Die Grundfunktionalität der Anwendung ist früh sichtbar.
- Nachteil: Paralleles Arbeiten ist begrenzt; das Testen spezieller Situationen in unteren Modulen kann schwierig sein, bis diese implementiert sind. Stubs müssen so einfach wie möglich sein.
- Aufsteigende Integration (Bottom-Up): Alle Module der untersten Ebene werden separat und parallel codiert und getestet. Es werden Testtreiber (Driver) geschrieben, damit sie unabhängig oder in einfachen Kombinationen funktionieren.
- Vorteil: Erleichtert paralleles Arbeiten und das Testen von Sondersituationen in den unteren Modulen.
- Nachteil: Der Gesamtbetrieb der Anwendung ist erst spät, gegen Ende der Integration, sichtbar.
- Sandwich-Integration: Eine Kombination aus Top-Down für die oberen Module und Bottom-Up für die unteren Module. Gilt oft als beste Lösung.
Systemtests
Dies sind Black-Box-Tests des gesamten Systems. Man unterscheidet folgende Testarten:
Zielorientierte Tests
- Wiederherstellungstests (Recovery Testing): Überprüfen die Fähigkeit des Systems, sich von Ausfällen zu erholen.
- Sicherheitstests (Security Testing): Überprüfen Schutzmechanismen gegen unautorisierten Zugriff.
- Stresstests (Stress Testing): Überprüfen die Leistung und Stabilität des Systems unter außergewöhnlichen Lastbedingungen (Sensibilität für Ausnahmesituationen).
- Performancetests: Überprüfen die Behandlung spezifischer Singularitäten durch das System und die Leistung der verwendeten Algorithmen, insbesondere bei zeitkritischen Abläufen.
- Kontexttests/Usability-Tests: Überprüfen die Systemleistung in Bezug auf Benutzerfreundlichkeit und spezifische Anwendungsfälle.
Alpha-Tests
Dies sind die ersten Tests, die in einer kontrollierten Umgebung durchgeführt werden, wobei der Benutzer Unterstützung vom Entwicklungsteam erhält.
Beta-Tests
Ein oder mehrere Benutzer arbeiten mit dem System in ihrer gewohnten Umgebung ohne direkte Unterstützung des Entwicklungsteams, um festzustellen, ob Probleme auftreten.