mmofacts.com

TDD und Browsergames?

gepostet vor 15 Jahre, 2 Monate von altertoby

Hi,

das vorerst letzte Kapitel von "Toby spielt mit seinem Browsergame und will neue Sachen/Techniken/... ausprobieren", diesmal mit Test Driven Development als Gast

Ich hab mich die letzten Tage zu TDD belesen und je mehr ich darüber wusste, desto mehr bin ich begeistert davon. Nachdem ich verstanden habe, wie die Schritte der Entwicklung mit TDD ablaufen (Test schreiben, Feature implementieren, Refactoring), wollte ich anfangen: Test schreiben, ja gut aber wie?

Am Besten ich mach das mal am Beispiel des Gebäudebauens. Das Interface für den Gebäudebau-Manger hat z.B. zwei Methoden: GibGebäudeAufPlaneten(int planetId) (gibt ne Liste aller gebauten Gebäude zurück) und BaueGebäude(int gebäudeId, int planetId)

Wenn ich die beiden Funktionen teste, was muss ich da alles testen? Ich kenne bisher 3 verschiedene "Test-Pattern":

  • Kontrolle des Ergebnisses, das die Methode zurück gibt
  • Überprüfen des Status des manipulierten Objektes
  • Überprüfen welche Methoden die Methode aufruft (durch Mocking)

GibGebäudeAufPlaneten:

  1. PlanetId <= 0: Alle Planeten haben eine Id größer null, also müsste ich einen Fehler erwarten
  2. PlanetId ganz groß: Planet mit dieser Id existiert nicht, auch Fehler erwartet
  3. PlanetId so wählen, dass Gebäude zurückgegeben werden und bei der Rückgabe überprüfen ob die wirklich alle auf diesem Planeten gebaut wurden

Im Prinzip ist ja hinter dieser Methode nur eine einfache Datenbank-Abfrage dahinter, reichen in einem solchen Fall diese Tests, Ergänzungen?, kann man 1 + 2. zusammenfassen?

Jedoch finde ich den Gebäudebau um einiges schwieriger. Zuerst würde ich die Funktion mit nicht validen Planten und Gebäude-Ids testen und dann mit validen. Jedoch gibt es beim Gebäudebau sehr viele Seiteneffekte: Guthaben abziehen, Platz auf Planeten verringern,... die alle nichts mehr mit dem eigentlichen Gebäudebau zu tun haben. Würde man in einem solchen Fall einfach überprüfen ob die Gebäudebau-Methode die Methoden der Planeten und Guthaben-Klasse mit den richtigen Parametern aufruft?

Zum Schluss noch die Frage nach dem Test-Framework:

Mir steht Visual Studio Team Edition zur Verfügung, könnte also das eingebaute Test-Feature nutzen. Jedoch gibt es noch viele weitere Frameworks (Nunit zb). Welches könnt ihr mir da empfehlen? (mit Begründung versteht sich)

Ich bin für jeden Tipp dankbar!

gepostet vor 15 Jahre, 2 Monate von Fobby

Deine Tests schreibst du so, dass du bei bestimmten Eingabewerten konkrete Modifikationen an den Daten beobachten kannst. Ideal dazu ist eine konstante Datenbasis vor jedem Testdurchlauf, also am besten ein Skript schreiben, das die ganze Datenbank wegwirft, neuerstellt und mit einem bekannten Satz Testdaten füllt.

Wenn du dann GibGebäudeAufPlaneten(int planetId) in deinem Test aufrufst, kannst du entweder das Wissen über deine Datenbasis nutzen und daraufhin überprüfen. Hat allerdings den Nachteil, dass der Test später zu Fehlern führen kann, wenn du für weitere Tests die Daten erweitern musst. Deswegen kannst du auch in dem Test einen Planeten erstellen und mit Gebäuden bebauen. Jetzt kommst du mit deiner GibGebäudeAufPlaneten-Methode und kontrollierst z.b. die Anzahl der Gebäude, Ausbaustufen, was auch immer du magst. Du kannst bis ins Detail prüfen, denn du hast sie ja vorher direkt eingespeist :)

BaueGebäude(int gebäudeId, int planetId) läuft ähnlich. Du überlegst dir vorher, was sich dadurch ändert, z.B. Rohstoffe und ein Eintrag in der Bauqueue. Das liest du aus, rufst die zu testende Methode auf, berechnest Differenzen, whatsoever und kannst wieder kontrollieren.

Was du noch ansprichst ist die Regel, nie mehr als genau eine Sache mit einem Test zu testen. Ich würde aber nicht bei 6 Baurohstoffen 6 Tests schreiben, denn diese Tests wären höchstwahrscheinlich so gut wie identisch und eine Trennung nutzlos.

Zu den Testframeworks: Man kann ja auf verschiedenen Ebenen testen. Manchmal wird man - gerade im Web - an die Grenzen so mancher Werkzeuge stoßen, weil man vielleicht testen möchte, ob ein Button an der richtigen Stelle platziert ist oder sich gar das JavaScript korrekt verhält. Symfony hat für Browserverhalten ein extra Testframework, wie das aussieht kannst du hier überfliegen: http://www.symfony-project.org/jobeet/1_2/Doctrine/en/09 . Hat allerdings auch seine Grenzen, ist nur nett wenn man Symfony sowieso nutzt, da sich diese Tests automatisieren lassen. Was sie selbst zusätzlich empfehlen ist http://seleniumhq.org/ . Viel Spaß beim Stöbern, ich hoffe ich konnte helfen :)

gepostet vor 15 Jahre, 2 Monate von altertoby

Klar konntest du mir helfen, thx... Stöbern mache ich dann morgen

Hab jedoch noch ein paar kleine Fragen:

Deswegen kannst du auch in dem Test einen Planeten erstellen und mit Gebäuden bebauen. Jetzt kommst du mit deiner GibGebäudeAufPlaneten-Methode und kontrollierst z.b. die Anzahl der Gebäude, Ausbaustufen, was auch immer du magst. Du kannst bis ins Detail prüfen, denn du hast sie ja vorher direkt eingespeist :)

Jetzt frage ich mich, wie ich das komfortable überprüfe bzw. die Daten erstelle. Wenn ich dies über bereits vorhandene Methoden mache, dann laufe ich Gefahr, dass in diesen Fehler sind...außerdem müsste ich mir dann merken, welche Methoden ich wie überprüfe (um einen Zirkelschluss zu vermeiden). Die nächste Methode wäre, die darunterliegende Schicht zu verwenden (sprich BLL--> DAL --> direkt DB). Dies wäre dann wohl das angebrachteste oder?

BaueGebäude(int gebäudeId, int planetId) läuft ähnlich. Du überlegst dir vorher, was sich dadurch ändert, z.B. Rohstoffe und ein Eintrag in der Bauqueue. Das liest du aus, rufst die zu testende Methode auf, berechnest Differenzen, whatsoever und kannst wieder kontrollieren.

Wie berechne ich die Differenzen? Manuell vorher auf meiner Datenbasis (was aufwendig wird, wenn sie sich ändert) oder anders?:

Gebäude.Baue(...)
var guthaben = Guthaben.Gib(...)
Assert.Equal(guthaben.Ress1, 200); 

oder

var gebäudeDaten = GebäudeDaten.Gib(...);
var guthaben = Guthaben.Gib(...);
int ress1 = guthaben.Ress1 - gebäudeDaten.Kosten.Ress1
....
Gebäude.Baue(...);
var guthabenNeu = Guthaben.Gib(...);
Assert.Equal(ress1, guthabenNeu.Ress1)

Ich hab immer die Angst, dass das bei weitem an Unit-Tests vorbeigeht und nur funktionale Test sind.

gepostet vor 15 Jahre, 2 Monate von TheUndeadable

Nur so als Feedback:

Für meinen FBK [1] nutze ich ein eigenes Test-Framework, da ich zu Visual Studio 2005 Zeiten keine Standard Edition sinnvoll zur Verwendung hatte. NUnit hatte kleinere Probleme was eigene bzw. neue AppDomains und das dynamische Einladen von Bibliotheken hatte.

Ich möchte aber auf die das von VS zur Verfügung gestellte Testframework umschwenken... Früher oder später.

Ein Test der Spiellogik sieht bei mir übrigens folgendermaßen aus: http://rafb.net/p/PUBJoa40.html

Zum Teil habe ich die Tests sogar vor der eigentlichen Implementierung geschrieben (man höre und staune) und dieses Verfahren hat sich für bestimmte Zwecke ausgezahlt.

[1] http://fbk.depon.net

gepostet vor 15 Jahre, 2 Monate von Fobby

Wenn du konsequent TDD anwendest, ist es unmöglich, dass ein Zirkeleffekt auftritt. Du müsstest mit einem Mal 2 Methoden implementieren und diese für deinen Test verwenden. Also Methoden für deinen Test verwenden, die du noch durch keinen vorherigen Test geprüft hast. Sobald du diesen Test für eine Methode aber hast, kannst du sie in folgenden Tests verwenden.

Bei deiner zweiten Frage tendiere ich zu Variante 1, allerdings solltest du absichern, dass es wirklich nach dem Bau 200 Ressourcen sein müssen. Konkret: auf jeden Fall fixtures verwenden (die feste Datenbasis) und ggf. im Test die Ressourven vor dem Bau setzen. Womit wir uns Variante 2 näher ...

Der große Nachteil der ersten Variante ist, dass der Test unweigerlich fehlschlägt, sobald du die Gebäudewerte änderst, Test 2 nicht. Vielleicht an der Stelle dann lieber Mockupobjekte verwenden anstatt der tatsächlichen Datenbank.

Ok du merkst schon, ich kann alles andere als konkret antworten. Ich habe noch nie TDD angewandt sondern genau wie du neugierig Literatur konsumiert. Also alle Angaben ohne Gewähr :)

gepostet vor 15 Jahre, 2 Monate von altertoby

Danke euch beiden, jetzt fühle ich mich gewappnet genug mich in das Abenteuer zu stürzen :)

Und wenn noch fragen kommen sollten, weiß ich ja an wen ich mich wenden kann (bzw. wo es so einen tollen Thread gab)

gepostet vor 15 Jahre, 2 Monate von Lubi

@Foby:

Dafür, dass du TDD noch nie angewendet hast, klingt das sehr nach den Gefahren, die ich Tag für Tag in der Software-Entwicklung bei TDD erlebe.

@TDD:

Die Unterscheidung, ob ein Unit-Test jetzt genau eine Methode nur prüfen darf, oder mehrere auf einmal ist nur eine Definitionssache: "Was ist eine Unit in meinem Projekt?"
Grundsätzlich tendiere ich dazu, die kleinsten Einheiten (also Methoden) einzeln zu testen (falls möglich - oft ist dafür erheblicher Initialisierungsaufwand nötig) und anschließend Klassen zu testen (mehrere Methoden im Zusammenspiel) - dadurch fallen dann die Tests auch wahrscheinlicher auf die Nase, wenn man im Kern was ändert.

Die nächste Ebene, die man dann angehen kann, sind Funktionstests - da wird es aber schnell sehr umfangreich - und dann lässt sich auch sehr viel testen. Diese Ebene ist die letzte Ebene, die ich persönlich als sinnvoll ansehe für Tests. Hier kann man Beispielsweise die gesamte Funktion Gebäude-Bau ablaufen lassen mit Prüfungen rund um Bauzeit, Ressourcenverbrauch, usw. Diese Tests sind einerseits aufwändig, aber sind gleichzeitig auch ein sehr verlässlicher Tester ;-)

Nun aber zur letzten sinnvoll automatisiert testbaren Ebene (eine mit der ich mich auch beruflich beschäftige) ist dann die GUI-Basierte Automatisierung. Auch hier kann wieder jede Menge getestet werden - im Rahmen von Browsergames sind allerdings solche Tests extrem aufwändig - da Browsergames häufig doch sehr "lebendig" sein sollen - und sich folglich auch nach der Veröffentlichung noch verändern.

IMHO denke ich grundsätzlich testen ja, aber man sollte es gerade bei Browsergames nicht übertreiben. Die schweren Schnitzer sollten einem sowieso auffallen - und die spezielleren Fehler sind dann die, die man meist auch in den Tests übersieht :-)

gepostet vor 15 Jahre, 2 Monate von knalli

Nach dem V-Modell kann man gem. ISTQB in Stufen wie folgt. aufteilen:

  • Komponententests
  • Integrationstests
  • Systemtest
  • Abnahmetest

Die ersten beiden Stufen sind eigentlich definiert: Die Komponente an sich (ohne andere) und dann die Interaktion untereinander. Allerdings kann eine Komponente auch geschlossen(!) aus mehreren Komponenten bestehen, also eine Mischung aus beiden. Unittests sind genau dort einzusetzen.

Die beiden letzten können in GUI-Tests abgehandelt werden, und automatisiert werden kann, was eben automatisiert werden kann.

Bezgl. Fehler: Viele (die meisten?) schweren Fehler sind bereits in der Spezifikation.. die findet man nur durch Testen. Oder gar nicht.

gepostet vor 15 Jahre, 2 Monate von Lubi

@Knalli

Wer verfährt in einem Browsergame bitte nach dem V-Modell? (Derart viel Overhead für die Einhaltung des Prozesses kann sich doch kein BG-Entwickler leisten - oder täusche ich mich...)

Bzgl. der Fehler stimme ich dir zu, dass Fehler häufig auch in der Spezifikation enthalten sind. Diese findet man aber meist nur in manuellen Tests (und nicht in automatischen), weil die automatischen gegen die Spezifikation designed werden sollten...

gepostet vor 15 Jahre, 2 Monate von knalli

Original von Lubi

@Knalli

Wer verfährt in einem Browsergame bitte nach dem V-Modell? (Derart viel Overhead für die Einhaltung des Prozesses kann sich doch kein BG-Entwickler leisten - oder täusche ich mich...)

Bzgl. der Fehler stimme ich dir zu, dass Fehler häufig auch in der Spezifikation enthalten sind. Diese findet man aber meist nur in manuellen Tests (und nicht in automatischen), weil die automatischen gegen die Spezifikation designed werden sollten...

Das war nur als Anregung bzw. als Quelle gemeint, falls jemand meint, ich habe mir diese Testsache spontan ausgedacht und nur hier ungeprüft hinterlegt. 

Man muss nicht wirklich jede Software tot dokumentieren, aber die Teststufen sind logisch und auch ohne V anwendbar. Es geht ja darum, Komponenten (Unit-Tests) von der GUI zu trennen.. Komponente 1 als Unit und Komponente 2 als GUI-Test ist pauschal keine gute Testqualität.

gepostet vor 15 Jahre, 2 Monate von ChrisM

Ich kann Dir nur sagen, dass man nicht genug testen kann. Man sollte aber immer den Nutzen/Aufwand im Auge behalten.

Ich erstelle immer verschiedene Layer für meine Programme. Jeder Layer hat dann seine eigenen Testroutinen. Wo Du anfängst hängt davon ab, ob Du zuerst unten - also die Datenbankebene oder oben im Browser anfängst.
Ich selbst baue mir zuerst immer mein Datenmodel auf. Also die Objekte, die im Spiel zu verwalten sind. Dannach ist die Datenbank und der Aufbau der einzelnen Tabellen dran. Anders als im klassischen TDD fange ich erst dann an meine ersten Tests zu schreiben.

Meinst teste ich sowohl die DAL als auch die Datenbank selbst parallel. Wenn Dir VS Team Suite zur Verfügung steht hast Du dafür schon die best mögliche Ausgangslage. Mit VS TS kann man schliesslich auch die Datenbank selbst testen. Es gibt dafür z.B. den SQL Server 2008 Wizard. Mit diesem Projekt liest Du zerst das Datenbankschema einer vorher von Hand erstellten Datenbank aus und erzeugst ein Datenbank-Schema. Das ist doppelt hilfreich: beim Testen und beim späteren Deployment.

Mit VS kannst Du dann auch verschiedene Data Generation Pläne erstellen. Dabei werden beliebig viele Beispieldaten erstellt. Da das DB-Schema bekannt ist können hier auch Relationen zwischen verschiedenen Tabellen berücksichtigt werden.

Für einen Test gegen die Datenbank selbst kannst Du dann T-SQL verwenden, um bspw. Stored Procedures zu testen. Oder aber bei Unit Tests diese Werte aus der Datenbank auslesen und überprüfen, ob Werte gelesen und/oder verändert werden.

Für die DAL kann ich nur das Repository Pattern empfehlen. Hier teste ich dann zwei Dinge.
1. Schreibe ich mit einen TestRepository das ich später auch für die Tests der eigentlichen Funktionen (z.B. BaueGebäude) verwenden kann. Dies hat den Vorteil, dass ich unabhängig von der Datenbank bin, die Test wesentlich schneller ausgeführt werden können und die Datenbasis vorhersagbar ist. Allerdings empfiehlt es sich auch zu testen, ob die TestRepositories sich überhaupt so verhalten wie gedacht.
2. Anschliessend folgenden Integration Tests. Anders als die Units Tests, die sehr schnell ablaufen und die ich bspw. während der Entwicklung immer wieder durchführe, sind die Integration Test sehr langsam, da sie mit einer echten Datenbank arbeiten. Hier verwende ich die selben Testmethoden wie bei den Unit Tests und tausche nur die Repositories gegen echte Repositories aus, die entweder gegen die oben erzeugte Testdatenbank und deren Dummy-Daten laufen oder gegen eine Datenbank mit "echten" Daten.
Nachteil hierbei ist aber auch, dass die Datenbank dann immer wieder neu aufgebaut werden muss. Also eigentlich vor jeder Testmethode die Datenbank leeren und mit neuen Testdaten füllen. Hier bietet VS wirklich gute Dienste, nichtsdestotrotz dauert dies eben sein Zeit, weshalb die Tests dann auch länger laufen.
Durch die Aufteilung entweder in verschiedene Test-Projekte oder in verschiedene Testfälle (aka Test Lists) kann ich dann während der Entwicklung bspw. nur meine schnellen Unit Tests aufrufen und dann in unregelmässigeren Abständen oder beim Check-In meine langsamen Integration Tests.

Anschliessend baue ich Service Klassen, die dann wahlweise meine verschiedenen Repositories verwenden. Durch diese trennung kann ich dann die Logik aber sehr schön unabhängig von der Datenbasis testen.

Für einfache Tests, wie bspw. die Überprüfung der Funktion "IstEmailSyntaktischKorrekt" bietet VS zusätzlich nach die Möglichkeit einen Test mit verschiedenen Testdaten durchzuführen. Also anstelle mehrere Test zu schreiben, die verschiedene mögliche Emailadressen überprüft, kann man der Testmethode hier bspw eine CSV Datei mit verschiedenen Emailadressen übergeben (Dies geht jedoch nur mit VS Team Suite oder VS Test Edition)

Vor VS 2008 hatte ich immer nUnit verwendet, mit den neuen Funktionen und der wirklich guten Integration von VS 2008 habe ich allerdings viel mehr Möglichkeiten. So kann ich Datenbank, Web Oberfläche und Performance testen, was ich sonst nur mit verschiedenen anderen Test bibliotheken erreichen würde. 

Es gibt aber keine vernünftige Aussage dafür wieviele Tests man schreiben sollte. Auch die Aussage, "Je mehr desto besser", stimmt nur zur Hälfte. Ich hatte auch Projekte in denen wir uns dann später nur noch um die Tests gekümmert haben und Angst davor hatten Änderungen an bestehenden Funktionen durchzuführen, da dies eine ganze Reihe von Tests brechen würde. (Ein sicherer Hinweis dafür, dass die Tests selbst nicht sehr sinnvoll sind)

Ich selbst fange immer nur mit einen einfachen Satz an Tests an. Bspw. ein Test pro öffentliche Funktion oder Methode in den Layern. Nur an kritischen Stellen, also sicherheitsrelevante oder komplexe Funktionen, schreibe ich mehr als einen Tests, eventuell sogar ein paar negativ Tests, um bspw.  sicherzustellen, dass ein Login mit falschen Passwort nicht funktioniert.

Die meisten Tests kommen aber erst später im Verlauf des Projekts. Dann nämlich, sobald ich irgendwo ein Problem oder einen Bug. Dann schreibe ich einen Test, der genau diesen Bug nachstellt. Dieser Test sollte dann Rot sein (also der Test scheitern), da das Ergebnis ja nicht das ist, was ich eigentlich erwartet hatte. Anschliessend korregiere ich den Bug und überprüfe, ob mein Test nun endlich auf Grün steht.

Wichtig: Durch das Schreiben von Tests verhindert man nicht automatisch Fehler im Code. Der wirkliche Nutzen besteht meiner Meinung nach darin, dass man dann später überhaupt noch in der Lage ist, Änderungen (Fehlerbehebungen, Erweiterungen) vorzunehmen. Ohne Tests passierte mir das immer wieder. Ich schreibe ein Programm, es funktioniert und ich veröffentliche es. Monate (Jahre?) später kommt es dann zu einem Fehler oder es soll eine kleine Änderung vorgenommen werden. Entweder kann ich mich dann noch an alle Zeilen meiner Arbeit erinnern, oder ich habe ziemliches Gottvertrauen in meine Fähigkeiten alle Abhängigkeiten dann noch überschauen zu können. Auch beim Arbeiten im Team sind Tests fast schon überlebenswichtig. Wie oft habe ich mich davor schon darüber gewundert, das mein Code plötzlich nicht mehr funktionierte, nur weil jemand anderes eine Basisfunktion geändert hat oder einen Paramter umbenannt hat. Hier erst einmal die Fehlerquelle zu finden ist oft recht mühselig. Hier könnte ein Test schon beim Einchecken anzeigen, dass der neue Code Probleme verursacht. Existiert für diesen Fehlerfall noch kein Test, dann spätestens nachdem ich das Problem lokalisiert und behoben habe.

Ich hoffe, dass hilft Dir 

gepostet vor 15 Jahre, 2 Monate von altertoby

Ui ist ja schön, dass die Diskussion doch noch so informativ weiter geht (besonderen Dank an ChrisM für die ausfürhliche Beschreibung). 

Ein wenig hab ich jetzt schon UnitTest in der freien Laufbahn ausprobiert und muss sagen, dass nach anfänglichen Startproblemen es immer einfacher wird passende Tests zu schreiben (zumindest finde ich sie jetzt passend, wie das wohl später aussieht ). Die größten Probleme bereitet mir immernoch, dass ich den zu testenden Algorithmus nicht erneut schreibe. Aber das wird :-)

Pex ist dabei eine sehr große Hilfe, ist ja vom Prinzip her die Variante mit der csv-Datei (aus dem E-Mail-Beispiel), nur dass ich mir keine Eingabedaten überlegen muss ;-). Desweiteren finde ich Mock/Stub-Objekte sehr praktisch. Dadurch kann man sehr gut unabhängig von dem darunterleigenden Layer arbeiten (vom Prinzip her ähnlich dem Repository Pattern) und arbeitet hervorrangend mit Pex zusammen...

Auf diese Diskussion antworten