Monkey-Testing (eng. Monkey bedeutet Affe) ist auch bekannt als Zufallstesten (eng. random testing) oder stochastisches Testen (eng. stochastic testing). Dabei erzeugt ein Programm (der Affe/Monkey) eine Reihe von zufälligen Eingaben. Was der Monkey dabei tut ist bekannt als Negativtest, Provokationstest, Robustheitstest oder Stresstest.

Der Name leitet sich vom Infinite-Monkey-Theorem her:

Wenn ein Affe nur lange genug auf einer Schreibmaschine tippt, schreibt er irgendwann alle Werke von Shakespeare.

aus Wikipedia

Dieses Theorem wiederum ist nur eine bildhafte Veranschaulichung des Borel-Cantelli-Lemmas. Eine andere Formulierung besagt z.B. das Pi die komplette Bibel in jeder Version, sowie eigentlich alle anderen Bücher die jemals geschrieben wurden oder jemals noch geschrieben werden enthält. Pi ist eine irrationale und transzendente Zahl, d.h. dass sie unendlich viele Stellen nach dem Komma hat. Dass wiederum bedeutet, dass jede beliebige (auch beliebig lange) Zahlenkombination in Pi enthalten ist.

"Zufällige Eingaben" bedeutet je nach Situation etwas anderes. Beim bekannten Fall vom Android Monkey erzeugt der Monkey eine Reihe von pseudo-zufälligen Benutzeraktionen wie Klicks, Touches, Gesten und System-Ereignissen. Dabei sucht der Monkey nach Crashes, unbehandelten Exceptions und danach, dass die Anwendung nicht mehr reagiert. All dies wird dem Entwickler dann gemeldet.

Einen einfachen Monkey kann man in ein paar Zeilen selbst Programmieren. Hier ein Beispiel in Python (nutzt PyUserInput):

import random, string
from random import randint
from pymouse import PyMouse
from pykeyboard import PyKeyboard

x_dim = 800
y_dim = 600
m = PyMouse()
k = PyKeyboard()
while True:
    m.click(randint(1, x_dim), randint(1, y_dim), 1)
    k.type_string(''.join(random.choice(string.lowercase) 
        for i in range(randint(0,100))))

In Zeile 1 bis 4 sind die entsprechenden Imports. In Zeile 6 und 7 werden die Dimensionen des Bildschirms festgelegt (hier 800x600). In Zeile 8 und 9 werden die entsprechenden Objekte (Maus und Tastatur) zur Interaktion erzeugt. Zeile 10 packt die beiden folgenden Anweisungen in eine Endlosschleife: In Zeile 11 wird auf eine zufällige Koordinate innerhalb des durch die Bildschirmdimensionen festgelegten Bereiches geklickt. Und dann ein zufälliger String mit einer zufälligen Länge zwischen 0 und 100 Zeichen erzeugt. Achtung: Wenn Sie das Programm genau so ausführen, verlieren Sie die Kontrolle über Ihren Computer!

Dies wäre ein Beispiel eines "dummen" oder "ignoranten" Monkeys. In diesem Fall haben die Interaktionen nichts mit dem üblichen Benutzerverhalten zu tun. Der Monkey hat auch keine Kontextinformationen wie z.B. welche Aktionen gerade möglich sind, oder wie er einen bestimmten Zustand der Anwendung erreicht hat. Und wenn ein Crash erzeugt wird, hat der Entwickler nichts außer dem StackTrace oder vielleicht einem Memory Dump - genau so, als wäre das Programm bei einem Endbenutzer abgestürzt. Diese Art des Testens stellt typisches Robustheitstesten dar. Es ist sehr sinnvoll Programme auf diese Art zu testen: Programmierer haben bei der Erstellung des Programmes häufig einen "Ablaufplan" im Kopf, eine bestimmte vorgesehene Art, auf die man das Programm verwendet. Wenn nun aber Tausende oder sogar Millionen von Benutzern das Programm verwenden, wird sicherlich jemand dabei sein, der es auf eine nicht vorgesehene Art verwendet. Ein solcher Nutzer wird von Programmierern auch abschätzig DAU genannt: Dümmster Anzunehmender User. Dabei kann jeder ein DAU sein, wenn seine Intuition von der des Programmierers abweicht.

Dumme Monkeys können schon sehr effektiv sein. Wenn Sie das nicht glauben, lassen Sie ihn mal auf Ihrem Lieblingsspiel oder einer Anwendung laufen. Mit ziemlicher Sicherheit wird das Programm nach ein paar Stunden abstürzen!

Etwas vergleichbares hat das Team von Netflix mit dem Chaos Monkey implementiert — wenn auch nicht auf der Ebene der Benutzeroberfläche: Der Chaos Monkey terminiert zufällig ausgewählte Prozess und Dienste in der Systemlandschaft von Netflix. Dies stellt sicher, dass die anderen Dienst trotzdem funktionieren, d.h. dass z.B. trotzdem Suchergebnisse angezeigt werden, auch wenn das Bewertungssystem ausfällt.

Es gibt ein paar einfache Möglichkeiten um den Monkey etwas intelligenter zu machen:

  • Wie bereits erwähnt hat der Entwickler nach einem Crash nur wenige Informationen wie der Crash erzeugt wurde. Indem man die ausgeführten Aktionen mitloggt ist schon viel getan. Noch wesentlich hilfreicher ist die Anwendung von Delta Debugging um eine minimierte Schrittfolge zur Crash-Erzeugung zu erhalten.
  • Es sollte sichergestellt werden, dass der Monkey nur das zu testende Programm (genannt SUT - System under Test) anklickt. Wenn das Fenster minimiert wird oder die Anwendung beendet wird oder abstürzt, macht sich der Monkey über das Betriebssystem her (wahrscheinlich keine so gute Idee...).
  • Wenn ein Crash stattfindet oder das Programm beendet wird, kann der Monkey nicht weiter testen. Da solche Tests üblicherweise unbeaufsichtigt über Nacht ablaufen, geht dabei wertvolle Testzeit verloren. Deshalb sollte der Monkey das Programm neu starten könnnen.

Dieser einfache Monkey kann theoretisch die ganze Anwendung testen. Bei größeren Anwendungen müsste er um tiefer verborgene Probleme zu finden jedoch tatsächlich unendlich lange testen.

Auftritt des intelligenten Monkeys (eng. smart monkey). Dieses Programm verfügt über Kontextinformationen und weiß:

  • was er im derzeitigen Zustand der Anwendung alles tun kann,
  • was er getan hat um diesen Zustand zu erreichen,
  • wie sich der derzeitige Zustand der Anwendung auszeichnet.

Obwohl es verschiedene Möglichkeiten gibt, um dies zu erreichen wird der Monkey nun wesentlich komplizierter. Eine Möglichkeit besteht darin, dem Monkey das Zustandsübergangsdiagramm der Anwendung z.B. in Form eines endlichen Automaten zur Verfügung zu stellen. Dies macht den Monkey deutlich effektiver und er kann dann sogar Fehler als Differenz zwischen Zustandsdiagramm und tatsächlicher Anwendung feststellen. Dies bedeutet allerdings auch wieder erheblichen Mehraufwand, weil dieses Diagramm zuerst in einer verwertbaren Form erstellt werden muss. Außerdem muss der Monkey nun beigebracht bekommen, wo sich die Bedienelemente der Anwendung befinden. Im Prinzip überschreitet man hier die Schwelle zum modellbasierten Testen.

Eine andere Möglichkeit besteht darin, dem Monkey zu ermöglichen die Bedienelemente der Anwendung selbstständig als solche zu erkennen, entweder mittels Bilderkennungsverfahren (schwierig) oder indem der Monkey Zugriff auf die Programminformationen erhält. Als Beispiel wollen wir eine WebAnwendung testen und nutzen hierfür Selenium.

import sys, random, string
from random import randint
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

browser = webdriver.Firefox()
browser.get('https://www.retest.de')
while True:
    links = browser.find_elements_by_tag_name('a')
    link = links[randint(0, len(links) - 1)].click()
    inputs = browser.find_elements_by_tag_name('input')
    if len(inputs) > 0:
        inputData = ''.join(random.choice(string.lowercase) 
        	for i in range(randint(0,100)))
        inputs[randint(0, len(inputs) - 1)].send_keys(inputData)
browser.close()

Zeilen 1 bis 4 sind wieder Imports. In Zeile 6 wird Firefox gestartet und in Zeile 7 die Adresse www.retest.de geladen. Diesmal ist die eigentliche Ausführung in einem try-except-block gekapselt (Zeilen 8 und 16 bis 17), um sicher zu stellen, dass Firefox bei einem Fehler auch wieder beendet wird (Zeile 18). In den Zeilen 10 und 11 wird aus allen Links der Seite einer ausgewählt und angeklickt. Und in den Zeilen 12 bis 15 wird aus allen Eingabe-Elementen der Seite eines ausgewählt und diesem ein zufälliger String mit einer zufälligen Länge zwischen 0 und 100 Zeichen übergeben.

Dieser Monkey ist dem "dummen" Monkey insoweit überlegen, als dass er bereits Bedienelemente zur Benutzereingabe vom restlichen "uninteressanten" Teil der Anwendung unterscheiden kann. Der Code ist natürlich noch sehr primitiv. Zum Beispiel wird diese Programm abstürzen, sobald ein Link oder Eingabelement ausgewählt wird, welches zwar im Quellcode vorhanden ist, aber nicht auf der Seite angezeigt wird oder deaktiviert ist. Außerdem kann die eigentlich zu testende WebAnwendung mit einem falschen Linkklick verlassen werden, sodass ab dann irgendwelche WebSeiten getestet werden.

Indem man Seiten-URLs als Zustände der Anwendung auffasst, kann dieser Monkey relativ schnell erweitert werden, sodass er sich selbst ein Zustandsdiagramm der Anwendung erstellt, und hierbei Links als Zustandsübergänge modelliert. Dieser einfache Ansatz kann wiederrum erweitert werden, indem der Monkey differenziert, wann Bedienelemente auf einer Seite nicht vorhanden oder gesperrt sind.

Monkey-testing kann also sehr einfach implementiert werden und kann Crashes und Memory Leaks finden. Monkey-testing (ohne zugrunde liegendes Modell) testet aber nicht auf fachliche Korrektheit!

Quellen:

Tagged in : Programmierung, Testing,

Dr. Jeremias Rößler
Dr. Jeremias Rößler

hat am Lehrstuhl für Softwaretechnik an der Universität des Saarlandes promoviert und besitzt über zehn Jahre Berufserfahrung in der Entwicklung von Individualsoftware.
Folgen Sie ihm auf Twitter: