Laden...

Testgetriebene Entwicklung

Letzter Beitrag vor 19 Jahren 13 Posts 6.593 Views
Testgetriebene Entwicklung

Hallo Leute,

ich habe mir vor kurzem mal NUnit angeschaut und halt auch Testgetriebene Entwicklung. Diese Seite [1] erklärt dass ganze glaub ganz gut.
Aber mich hat das eher abgeschreckt.
Hier ein Beispiel:


public class CustomerTest...
  public void testCustomerName() {
    Customer customer = new Customer("Bent Keck");
    assertEquals("Bent Keck", customer.getName());
  }
}

public class Customer...
  public String getName() {
    return "Bent Keck";
  }
}

Ich meine, wenn ich ein math. Problem habe, dann fange ich auch nicht nochmal mit dem 1x1 ein, sondern ein paar Sachen sind einfach schon klar. zB wenn ich einen Taschenrechner entwicklen will, dann gehe ich auch nicht jede mögliche Kombination 2x2 = 4 3x3=9 durch?? Sondern löse es gleich dynamisch. Aber in dieser Anleitung steht eben, dass man Schritt für Schritt alles durchmachen soll, und bewusst Fehler erzeugen soll, etc.

Falls sich jemand mit dem Gebiet auskennt, und/oder es selber anwendet, wäre ich sehr erfreut über eine Erklärung wieso er/sie diese Art der Entwicklung verwendet. Und vorallem, ob man solche Sachen wie oben gezeigt, auch wirklich anwendet.

[1] http://www.frankwestphal.de/TestgetriebeneEntwicklung.html

Bakunin

Hallo Bakunin,

ist schon lustig, in dem Code, den du als Beispiel angegeben hast, ist ein Fehler. Leider wird dieser Fehler durch den Testcode nicht gefunden. Das spricht dafür, dass man noch mehr Testcode braucht, auch und gerade bei so einfachen Sachen!

Mit


    Customer customer = new Customer("Bent Keck");
    assertEquals("Bent Keck", customer.getName());
    Customer customer = new Customer("Peter Pan");
    assertEquals("Peter Pan", customer.getName());

würdest du den Fehler finden. Ist doch ein ziemlich schlagender Beweis für die Notwendigkeit von viel Testcode. Überzeugt?

herbivore

.

Hallo herbivore,

ich habe den Code herauskopiert und nicht die ganze Klasse abgebildet. Es fehlt der Konstruktor mit einem string Parameter. (ist im fall java code).

Nzgh ich habe die Seite ja komplett vergessen 🙂:
http://www.frankwestphal.de/TestgetriebeneEntwicklung.html

Das Testcode nötig ist, ist mir schon klar. Aber in der Form wie es auf dieser Seite gemacht wird, finde ich es schon ein wenig krass. Darum würde mich eben interessieren ob wirklich viele Leute das ganze so machen.

Und ich habe bis jetzt eignetlich auch noch nie so entwickelt -> Zuerst Testcode, dann der eigentliche Code. Ich mache mir zuerst immer ein UML, designe das ganze durch, solange bis ich denke es passt soweit. Je nach dem, zeige ich es anderen Leuten, damit die Fehler sehen die ich nicht sehe etc.
Und dann entwickle ich nach diesem Design.

Mit der Testgetriebenen Entwicklung, entwickelt man Testcode, und aus den Fehlern die vom Testcode erzeugt werden, erstellt man den eigentlichen Code. (oder habe ich jetzt was falsch verstanden?).

Bakunin

Hallo Bakunin,

dass du nur einen Teil wiedergegeben hast, ist schon klar. Und in diesem Teil ist ein Fehler.

herbivore

.

Ähm ich glaube ich weiss was du meinst. Es war mir schon klar, dass dieser Code kein Fehler zurückgibt, da es ja Bent Keck zurückgibt.

Also damit die Reihenfolge stimmt:

  1. Schritt:

public class CustomerTest...
  public void testCustomerName() {
    Customer customer = new Customer("Bent Keck");
    assertEquals("Bent Keck", customer.getName());
  }
}

  1. Schritt -> soweit verfeinern dass man kompilieren kann, und dass es einen Fehler zurückgibt.

public class Customer...
  public Customer(String name) {
  }

  public String getName() {
    return null;
  }
}

  1. Schritt -> einfachste Lösung anwenden

public class Customer...
  public String getName() {
    return "Bent Keck";
  }
}

So, damit es verständlicher ist.

Was ich jetzt nicht ganz verstehe, wieso diese Schritte durchmachen. Wieso nicht gleich eine Dyn. Lösung machen. Mein Ansatz "fast" ohne zu Denken:


public class CustomerTest{
  private string m_Name;

  public string Name{
   get{ return this.m_Name;  }
  }

  //Oder damit der Methodenaufruf stimmt
  public string getName(){
    return this.m_Name;
  }
}

//frei gecodet, nicht überprüft ;), aber der Inhalt sollte rübergekommen sein

TDD - Ein Gedankenspiel

Hi,

UML und TDD schliessen sich ja nicht aus. Es heisst ja nicht,
dass du nicht das eigentliche Design der Anwendung erst
durch die Test's erreichst. Natürlich musst Du Dir immer
noch Gedanken um die für das Projekt benötigten Klassen
machen. Sonst könntest Du ja auch keine Tests schreiben 😉
Was sich aber auch durch das Testgetriebene Entwickeln
ergibt, ist das Zusammenspiel der Klassen, wer hat eine
Assoziation bzw. Aggregation mit welcher Klasse. Wie
sehen die Methoden aus, welche Properties brauche
und so weiter und so weiter. Neben den ich nenne es mal
Standardtests, wie im Beispiel genannt. Machen viele
Entwickler und ich auch. TestCases für bestehende Use Cases,
welche dann dieses Scenario einmal durchlaufen. Gerade,
wenn die Anwendungen vor längerer Zeit ausgeliefert wurden
und ich lange nicht daran gearbeit habe, habe ich dann
einen Vorteil -> Kunde meldet bei folgenden Schritten
passiert das und das ... dann stelle ich dieses Scenario
als Test nach und kann somit schnell Fehler finden. Es gibt
also viele Gründe für die Testgetriebene Entwicklung.
Am besten alles mal anschauen und das beste für einen
selbst rausziehen. So ist es auch gemeint 😉 Es sind
alles Vorschläge der Personen die sich dieses ausgedacht
haben und keine feste Regel, dass man es genau so machen
muss 😉

Gruss
LarsLovesDotNet

Alles was man sich vorstellen kann,kann man auch programmieren.

AODL- An OpenDocument LibraryAODL
WWW: www.OpenDocument4all.com

Bakunin
Was ich jetzt nicht ganz verstehe, wieso diese Schritte durchmachen.

Die Schritte darf man nicht so eng sehen. Das sind Beispiele, die erläutern sollen, wie testgetriebene Entwicklung (und Extreme Programming) vom Prinzip her ablaufen.

  1. Schritt, die nicht implementierte Eigenschaft sollte im Test fehlschlagen.
  2. Schritt, die extremst einfachste Implementierung funktioniert anfangs. Super, reicht erstmal so.
  3. Schritt, man programmiert weiter und lässt zwischendurch seine bis dahin entwickelten Tests laufen. Einige Tests werden fehl schlagen. So sieht man sofort, welcher alter Code mit den aktuellen Änderungen nicht mehr zusammen funktioniert und angepasst werden muss. Die in Schritt 2 entwickelte, einfachste Implementierung muss wahrscheinlich etwas ausgebaut werden.

Ziel ist es, so einfachen und primitiven Code wie möglich zu schreiben, der gerade das erledigt, was er soll. Vorkehrungen für etwaiige, zukünftige Anforderungen werden rigoros ausser Acht gelassen. Man kennt sie ohnehin nicht. Ansonsten steckt man viel Aufwand in etwas was später in der Form niemals erforderlich sein wird. Da man aber den Quatsch nicht wieder entfernen möchte, war ja viel Arbeit, versucht man alle notwendigen Änderungen ins geschaffene "Framework" zu integrieren. So steigt der Aufwand für Änderungen langsam ins Unermessliche.

Die einfache Lösung zu programmieren, ist manchmal schwieriger als man denkt. Man muss sich dazu zwingen, pragmatisch und zielgerichtet zu programmieren (und auch viel umzustricken). Am Anfang hat man tolle Ideen, die alle umgesetzt werden müssen. Am besten gleich alle auf einmal. Das Programm, das alles kann! Von dieser Vorstellung muss man sich lösen. Die Beispiele sind deshalb so krass gewählt, damit man sich mehr zusammenreisst und zur einfachsten Lösung für ein Problem strebt. Wenn es konkret erforderlich wird, kann immer noch erweitert werden.

Das ging jetzt eher in Richtung XP als in Richtung Testfälle.
Zu den Testfällen. Ich finde es ist nicht unbedingt notwendig, dass jede einzelne Methode oder Eigenschaft getestet wird. Zu viele Tests sind auch nicht gut, da sie sonst zu häufig angepasst werden müssen. Meiner Meinung nach schreibt man Tests pro Testfall/Szenario, so wie larslovesdotnet es beschrieben hat. Auf der anderen Seite kann das natürlich auch bedeuten, dass man gleich mehrere Tests für eine einzelne Methode/Eigenschaft zu schreiben hat. Da man dabei aber gefordertes Verhalten testet, sollten die Tests einigermassen stabil bleiben.

Gruss
Pulpapex

Danke für die Antworten,

mein Problem liegt darin, dass ich bis jetzt überhaupt nicht so entwickelt habe. Dass ist eine große Umstellung glaube ich. Ich habe mir das Buch "Patterns Konkret" gekauft, und für mich ist das Buch schon schwer zu lesen. Dass einzige was ich jetzt halbwegs verstanden habe, ist eben XP und Unit Testing.

Ziel ist es, so einfachen und primitiven Code wie möglich zu schreiben, der gerade das erledigt, was er soll. Vorkehrungen für etwaiige, zukünftige Anforderungen werden rigoros ausser Acht gelassen. Man kennt sie ohnehin nicht. Ansonsten steckt man viel Aufwand in etwas was später in der Form niemals erforderlich sein wird. Da man aber den Quatsch nicht wieder entfernen möchte, war ja viel Arbeit, versucht man alle notwendigen Änderungen ins geschaffene "Framework" zu integrieren. So steigt der Aufwand für Änderungen langsam ins Unermessliche.

So wie du Beschrieben hast, wie man Programmieren soll Pulpapex, so mache ich dass auch eigentlich (oder ich nehme es an g).

Was sich aber auch durch das Testgetriebene Entwickeln
ergibt, ist das Zusammenspiel der Klassen, wer hat eine
Assoziation bzw. Aggregation mit welcher Klasse.

Das ist interessant, denn ich versuche diese Verknüpfungen bereits vor der Programmierung zu lösen. D.h. auf einem Blatt Papier, dann UML.

Kunde meldet bei folgenden Schritten
passiert das und das ... dann stelle ich dieses Scenario
als Test nach und kann somit schnell Fehler finden.

Das wäre mir gar nicht in den Sinn gekommen. Aber stimmt, so ist es viel leichter einen Fehler zu finden.

Danke für die Antworten. Ich finde das Thema sehr interessant und deswegen werde ich mir gleich noch mehr zu dem Thema reinziehen 🙂.

Bakunin

Ich denke, TDD (test first) hat gegenüber klassischer Entwicklung mit Unit-Tests zwei Vorteile:

  1. Es deckte gnadenlos Lücken oder Fehler in den Specs auf. Denn der Weg von der Anforderung zum Test ist meist kürzer als von der Anforderungen zu einer Lösung.

  2. Es zwingt den Entwickler noch früher bereits beim Design auf die Testbarkeit zu achten. Bei komplexeren Szenarien ist gerade das ein Riesenproblem. Da müssen Mock-Objekte gebaut werden, Datenbanken in einen konsistenten Zustand gebracht werden, etc.

An einer deutschen Uni wurde mal (mit Studenten) eine empirische Untersuchung von TDD gemacht. Ergebnis war, dass TDD sowohl produktiver war als auch weniger Fehler produzierte.

Leider kriegen viele Chefs immer eine Krise, wenn man TDD machen will. Die hören dann nur: "Die arbeitet nicht am Ergebnis, sondern an Tests".

Hallo Bakunin,

hat eine Weile gedauert, aber jetzt habe ich den Artikel gelesen. Zu deiner Eingangfrage scheinen mir zwei Kernaussagen aus dem Artikel zu passen:

Indem wir den Test zunächst fehlschlagen sehen, testen wir den Test selbst. Wenn der Test fehlschlägt, war der Test tatsächlich erfolgreich. Ein solcher Test gibt uns Vertrauen, dass wir einen nützlichen Test geschrieben haben.

Wenn Sie mehr Code schreiben, als Ihr Testfall in Wirklichkeit verlangt, schreiben Sie immer zu wenige Tests. Sie schreiben dann Code, der unter Umständen von keinem Test gesichert wird.

Insbesondere die zweite ist der Grund dafür, warum man keine Schritte überspringen soll.

Allerdings würde ich wohl auch wie du geneigt sein, mehr als einen Schritt auf einmal zu machen. Im konkreten Fall würde gleich zwei Tests für den Namen schreiben:


public class CustomerTest...
  public void testCustomerName() {
    Customer customer;
    customer = new Customer("Bent Keck");
    assertEquals("Bent Keck", customer.getName());
    customer = new Customer("Peter Pan");
    assertEquals("Peter Pan", customer.getName());
  }
}

und dann erst mit leeren Rümpfen implementieren (Test schlägt fehl) und dann aber Konstruktor und getName "gleich richtig" implementieren (Test besteht)


private String strName;

public class Customer...
  public Customer(String name) {
    strName = name;
  }

  public String getName() {
    return strName;
  }
}

Wenn ich es mir recht überlege, würde ich vielleicht sogar gleich drei Tests schreiben:


public class CustomerTest...
  public void testCustomerName() {
    Customer customer1 = new Customer("Bent Keck");
    assertEquals("Bent Keck", customer1.getName());
    Customer customer2 = new Customer("Peter Pan");
    assertEquals("Peter Pan", customer2.getName());
    assertEquals("Bent Keck", customer1.getName());
  }
}

Aber ich trotzdem denke ich, dass Tests und Implementierung eng verzahnt sein müssen. Deshalb sollte man schon drauf achten nicht zuviele Schritte zusammenzufassen.

herbivore

Danke herbivore, sehr schön 🙂

Ich habe angefangen TDD umzusetzen und es gefällt mir wirklich sehr gut. Wobei ich aber immer zuerst ein UML erstelle, also ein Klassendiagramm. Dann diese Struktur aufbaue, also die Methoden etc aber ohne implementierung. Dann erst schreibe ich die Tests.... somit baue ich die Struktur zwar nicht durch das Testen auf, aber dass hat mich sowieso nicht wirklich überzeugt. Denn ich finde die Struktur sollte schon zuerst auf einem Blatt Papier entstehen.

Falls ich jetzt komplett daneben liege, dann sagts ruhig 🙂.

BTW: ich glaube jetzt kann man es gar nicht mehr Testgetriebene Entwicklung nennen, denn ich überprüfe die Methoden ja eigentlich nur. Also eher Unit Testing. Aber wenn ich zB eine neue Anforderung habe für eine Klasse, dann schreibe ich immer zuerst einen Test und dann wird implementiert. Aber wie gesagt, am Anfang baue ich die Struktur eigentlich ohne Tests.

Bakunin

Hallo Bakunin,

Falls ich jetzt komplett daneben liege, dann sagts ruhig

wie du schon selber sagst, ist deine Vorgehensweise nicht mehr testdriven. Aber das heißt ja nicht, dass sie schlecht ist. Ich habe mir mal das Interview auf http://www.frankwestphal.de/Tonabnehmer1-JohannesLink-SoftwaretestsmitJUnit.html angehört. Und da sagt Johannes Link an einer Stelle sinngemäß, dass mache Entwickler nach ein paar Stunden schon ein Ah-Erlebnis mit TDD haben und andere nach Monaten (noch) nicht. Nicht jede Entwicklungsmethode passt für jeden Entwickler und solange du keine Vorgaben bezüglich einer bestimmten Methode einhalten musst, solltet du die wählen, die dir am besten liegt und mit der du die besten Ergebnisse produzierst.

herbivore

Danke für das Feedback.

Bakunin