Test driven

This post is very old. Please bear in mind that information here might be incorrect or obsolete, and links can be broken. If something seems wrong, please feel free to comment or contact me and I'll update the post.

Since half a year, I’m reading a lot about “modern” software engineering approaches, like agile development practices, XP and test-driven development. Read on for a short story how I work today.

This is how I write my code currently, and I’m pretty productive that way, but your mileage may vary.

Usually, I’m working in short sprints nowadays, I try to keep those sprints around 2 hours. Before I start a sprint, I think about what I want to have at the end, usually some specific functionality. It’s just a rough idea what should work, no concept paper, though I tend to write up a short note before implementing a larger system, covering the main classes and their responsibilities.

At the beginning, I just write a skeleton class header and define the types I think I’m gonna need, and the helper objects definitions, just enough that it compiles. There is no code that does something, so this is usually really quick (like 5 minutes or so). Note that I don’t implement the functions so calling them will result in a link error.

Then, I start to write unit tests for helper classes, one for each function, of course only if there are helper classes that I’m aware of beforehand (often, the helper classes appear during a later refactoring). This takes me around 10 minutes or so per function, including the unit test. If I end up using some other function, I’m getting a linker error so I can see if I called the function properly or whether I thought the syntax is different (this happens most of the time – you have a function called doX in your interface but in your test case you think it’s called getQ, this is your chance to fix this, most of the time you were wrong when defining the interface), and I can go ahead and implement the missing ones right away. I check in the code at each step, meaning once after the interface, then after each test, then after implementing the function, and then after some bug fixing or test expanding.

I’m trying to keep the tests simple, just a few lines, for most helper classes, they are 2-3 lines long at most. I try to test border cases, too, it’s quite natural to have one test like “PopStack” and write a “PopEmptyStackThrows” just along with it to see what happens. Here, a crash costs you maybe a minute to fix it, later, you could end up spending an hour tracking it down. And for some reason or another, it is fun to test such border cases :)

After the functionality is pretty much done, I do a refactoring pass. During the implementation, I often end up with duplicate code, and in this step, I try to clean it up as much as possible. With the unit tests in place, I can be pretty sure that I don’t break stuff in this phase. I add a few comments now for stuff that is not obvious. Basically, the goal is to have solid and polished code that is suitable for further development, but it’s not top-notch as it has been just used by the unit tests mainly and not by “real” code.

As soon as the helper classes are in place, I start with the main class. I try to focus on a single function, just as with the helper classes, and get this working. I try to have at least two tests for each function, one where it works, and one which shows what happens in case of an error. The latter one is actually the more important one, as it is the one which makes the code robust, as you tend to forget what will be treated as an error usually. It’s actually just the same as with the helper classes. I tend to go bottom up by using only functionality that I have tested already. This means usually to start with the simplest functions first and work towards the complex ones. If I need a new helper function, I test it first before using it somewhere else, so I have less trouble if a test fails.

The last step – as soon as the whole class works – is yet another refactoring step to consolidate functionality and extract utility functions. In this step, I usually polish the docs, document invariants, and add further checks for error cases (with tests). After this step, I consider the code production ready and move on – the code in this stage has each function documented, with pre/post conditions, unit tests for each function, has been refactored once or twice, and is of pretty high quality.

In case I discover a bug later (by other code that depends on this class or functionality), I always write a test case for the affected class first (and check it in) before fixing it. I also look through all similar code to see if some other part is affected by the same bug (this is usually a sign that some refactoring is needed).

Related posts:

  1. Test on multiple platforms
  2. Code quality notes
  3. Invest in testing

This entry was posted in Programming and tagged , . Bookmark the permalink.

7 Responses to Test driven

  1. Philipp says:

    Wow, diese Herangehensweise gefällt mir wirklich! Ist echt cool, lohnt sich auf jeden Fall mal zu lesen.
    Aber ich muss sagen, dass ich das auch versuche im Groben so zu tun. Hilfsfunktionen immer vorher Testen, bevor man Sie einbindet. Auch cool find ich das, dass du wenn du nen Bug entdeckt und behoben hast, an anderen Stellen im Code nachprüfst, ob der Bug dort auch aufgetreten ist und warum er nicht aufgefallen ist. Jap, das hört sich ziemlich zuverlässig an.

    PS: Warum gehen wir noch in SoS 3, bzw. warum sollten wir noch gehen???

  2. Anteru says:

    Danke. Und ich hab keine Ahnung warum wir in SoS gehen :)

    ^ Ich schau halt möglichst so Code zu schreiben dass ich nicht ständig dran werkeln muss sondern einigermaßen mich aufs Interface verlassen kann; das spart Zeit und das Programmieren macht einfach mehr Spaß. Gute Interfaces krieg ich eigentlich nur mit TDD zusammen, und daraus ist mittlerweile diese Vorgehensweise herausgekommen. Ich glaub so grob versucht jeder so zu arbeiten, ich hab es halt nur mal zusammengeschrieben damit es schön dasteht.

    Außerdem gibt es ja auch Leute die mit dem Programmieren erst anfangen und für die ist es sicher interessant zu lesen was jemand aus seinen Fehlern gelernt hat; selbst wenn sie es nicht direkt übernehmen können.

  3. hehejo says:

    Eher sollte man sagen: Keine Ahnung, warum ihr noch in SoS3 geht.

    Ich mache eher noch wenig mit Testen. Das mag aber daran liegen, dass ich keine so dicken Projekte am Start habe.

    Bei meinen “Homepages” fange ich jedoch auch langsam damit an. V.A. weil Ruby on Rails da ein nettes Framework mitbringt.

  4. Philipp says:

    Ja, Testen ist an sich so ne Sache… Es hat nur Sinn, wenn man sich überlegt, dass man Testcases öfters laufen lassen kann und die dann immer gleich ablaufen. Wenn man nur “ausprobiert”, dann ist das oft nicht der Fall.

  5. andy says:

    Hmm, also ich scheine da etwas undisziplinierter vorzugehen, meistens formuliere ich erst nach ner Weile prototyping paar Eigenschaften zu denen ich mir Tests generieren lasse.

    Wenn ich dann ganz paranoid sein will wird halt ein bisschen das turing-vollständige Typsystem (das man ja mittlerweile in wirklich vielen Sprachen vorfindet ^^) ausgereizt um wichtige Invarianten zu forcieren. Spätestens hier lohnt sich dann auch mal das man brav seine Papers geschmökert hat und vllt sogar nen Theorembeweiser am Start hat um nen “trusted kernel” aufzubauen in die man die fiesen Semantikkiller (böse Seiteneffekte usw ;D) pressen kann.
    Hier mal das zugrundeliegende (leider selbst noch nicht wirklich praktiziertes) Motto: modular ist erst dann wenn aus der Korrektheit der Komponenten die Korrektheit des Systems folgt.
    Konkret wären hier mal paar Richtlinien: wenig mutual recursion usw, am besten recursion toolkits, folds usw nutzen (um das Halteproblem möglichst einzudämmen und effektiv nur die Strategie, nicht auch den Boilerplatekram testen zu müssen) ; wenig Seiteneffekte um referenzielle Transparenz zu wahren (so bleibt die Sache auch schön einfach zu testen), vorallem nicht verstreuen, wenigstens durch Annotationen bzw Typsystem abgrenzen ; Eigenschaften so allgemein wie möglich formulieren damit keine ekligen Randfälle entstehen an die man sowieso nicht mehr denkt; Garantien des Typsystems ausnutzen wie blöd (zB parametricity theorem: eine verallgemeinerte Funktion kann weniger Annahmen über Verhalten treffen, weniger implizite Fehler durch falsche Annahmen bzw fehlende Vorbedinungen); wo möglich “algebraisch” denken, existierende Theorie hilft oftmals bei der Implementierung und v.a. Fehlervermeidung, teilweise existieren auch schon toolkits für den ganzen Kram (konkret: programming with hylomorphisms etc) ; wenn es Papers zu dem Thema gibt wenigstens mal reindenken, das merzt schonmal viele Denkfehler aus.

    Und vllt noch immer so im Hinterkopf behalten das weniger Code (zumindest für die Standardimplementierung von nebenan, muss ja nicht gleich alles zur performance-tödlichen Zone erklärt werden; erstmal ordentlich profilen bevor man meckert) besser und sicher kein Armutszeugnis ist (wehe dem der nach LOC bezahlt wird). ^^

  6. andy says:

    BTW: ich für meinen Teil gehe wenn überhaupt nur deswegen in SoS3 um zu verstehen was die Leute mir sagen wollen mit ihrer tollen Fake-Fachsprache.
    Ist vllt auch ein spezielles Problem von mir, aber ich habs lieber halbwegs formal, da weiss man wenigstens woran man ist. Deswegen tuhe ich mich halbwegs schwer mit dem Softwareengineeringkauderwelsch, zumal das nicht wirklich zu mir vertrauten Inhalten in Bezug gesetzt wird. ^^

  7. Anteru says:

    Mir kommt das SoS3 im Moment eher so pseudowissenschaftlich rüber (hey, das ist zwar nur ein Apfel, aber wir nennen es jetzt Vitamine Package/Red Edition, with Apple Taste), aber bis jetzt haben wir ja eigentlich nur gelernt wie man bekannte Sachen bei denen nennt. So richtig Methodik hatten wir ja noch keine … vielleicht kommt ja da noch was Spannendes. V.a. würde mich interessieren wie so Sachen wie Scrum & Co. überhaupt entwickelt werden.

    Formal ist natürlich immer gut weil gut nachvollziehbar, allerdings sollte immer die Lösung des Problems im Vordergrund stehen und nicht der Formalismus, ich versuche pragmatisch Probleme zu lösen und nicht mehr.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>