Gesetz von Demeter, Unit Tests und der zweite Programmierer
In meinem letzten Post habe ich beschrieben, das Delegation für den Benutzer einer Klasse angenehm ist und das wir dies auch im realen Leben schätzen. Heute möchte ich kurz beschreiben wie sich das Einhalten des Gesetzes von Demeter auf das Schreiben von Unit-Tests und das Benutzen meiner Implementierungen auswirkt. Dazu habe ich das Modell um Implementierungen erweitert:

Am Beispiel von CityBike möchte ich erklären, dass die Implementierung der Unit-Test’s jetzt ganz einfach geht. Wir haben also die leere Implementierung der Klasse:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class CityBike implements WithBasket { private Basket basket; public CityBike(Basket basket) { } public void addElement (Element e) { } public Basket getBasket () { return null; } } |
Alle Implementierungen sind leer, da wir ja im Sinne von Test-Driven Development (TDD) zuerst unsere Tests schreiben und erst danach die Implementierung. Hier also die JUnit4-Testklasse:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class CityBikeTest { private Basket basket; private CityBike cityBike; @Before public void setUp() { basket = mock(Basket.class); cityBike = new CityBike(basket); } @Test(expected=IllegalArgumentException.class) public void shouldNotCreateWithNullAsBasket() { new CityBike(null); } @Test public void shouldDelegateAddElementToBasket() { Element e1 = mock(Element.class); // cityBike.addElement(e1); // verify(basket).add(e1); } @Test public void shouldReturnBasket() { assertEquals(basket, cityBike.getBasket()); } } |
Alle 3 Tests melden einen Fehler. Also ändern wir die Implementierung so, dass die Tests funktionieren:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class CityBike implements WithBasket { private Basket basket; public CityBike(Basket basket) { if (null == basket){ throw new IllegalArgumentException("null as basket not allowed"); } this.basket = basket; } public void addElement (Element e) { this.basket.add(e); } public Basket getBasket () { return this.basket; } } |
Alle Tests funktionieren.
(Die Implementierung und Tests für SimpleBasket und SimpleElement lasse ich hier im Text mal weg, damit es nicht zu unübersichtlich wird.) Damit haben wir als Programmierer von CityBike mal alles gemacht
Jetzt kommt ein zweiter Programmierer und möchte unsere Klassen benutzen. Es wird eine Person implementiert, die mit dem Fahrrad fahren soll. Allerdings muss dieser Fahrer seine Umhängetasche in den Korb (Basket) legen, bevor er losfahren darf. Natürlich sollte auch der zweite Programmierer seine Klassen “Fahrer” und “Tasche” testen. Hier die fertigen Implementierungen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | public class Fahrer { private Tasche tasche; private CityBike cityBike; public Fahrer(Tasche tasche, CityBike cityBike) { if (null == tasche){ throw new IllegalArgumentException("null as tasche not allowed"); } this.tasche = tasche; if (null == cityBike){ throw new IllegalArgumentException("null as cityBike not allowed"); } this.cityBike = cityBike; } void losfahren(){ this.cityBike.addElement(tasche); // aufsteigen // nach rechts und links schauen // anfangen in die Pedalen zu treten } } public class Tasche implements Element { public int getWeight() { return 12; } } import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.*; public class FahrerTest { private Tasche tasche; private CityBike cityBike; private Fahrer fahrer; @Before public void setUp() { this.tasche = mock(Tasche.class); this.cityBike = mock(CityBike.class); this.fahrer = new Fahrer(tasche, cityBike); } @Test public void shouldAddTascheInKorb() { // fahrer.losfahren(); // verify(cityBike).addElement(tasche); } } |
Was passiert, wenn wir die Methode addElement(Element e) aus WithBasket und CityBike rausnehmen. Jeder der dann WithBasket, CityBike oder andere Subklassen von WithBasket benutzt muss dann den Wege über getBasket().add(Element e) gehen. Die Methode losfahren in Fahrer sieht dann so aus:
1 2 3 4 5 6 7 | void losfahren(){ this.cityBike.getBasket().add(tasche); // aufsteigen // nach rechts und links schauen // anfangen in die Pedalen zu treten } |
Aber nicht nur das wir jetzt in X Klassen dieses getBasket().add(tasche) stehen haben. Wir machen den Benutzern unserer Klasse auch noch das Testen schwieriger:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.*; public class FahrerTest { private Tasche tasche; private CityBike cityBike; private Basket basket; private Fahrer fahrer; @Before public void setUp() { this.tasche = mock(Tasche.class); this.cityBike = mock(CityBike.class); this.basket = mock(Basket.class); this.fahrer = new Fahrer(tasche, cityBike); } @Test public void shouldAddTascheInKorb() { when(cityBike.getBasket()).thenReturn(basket); // fahrer.losfahren(); // verify(basket).add(tasche); } } |
Wir haben eine zusätzliche Membervariable basket im Test und der Test muss noch Vorbereiten das auf cityBike auch wirklich ein Basket geliefert wird. Und hier haben wir nur eine Verschachtelungstiefe. Man denke sich mal aus wie es aussieht wenn wir folgende Situation haben: A.getB().getC().getD().add(E e). Und jetzt stelle sich der Leser noch vor, dass das ganze Konsequent in der ganzen Applikation so gemacht wird. Irgendwann sind die Tests so aufwendig und kompliziert, dass kein Entwickler mehr Tests schreibt, weil einfach keine Zeit ist. Wenn dagegen immer das Law of Demeter eingehalten wird, dann sind die Tests kurz und einfach.
Schlusswort:
Ich habe hier Mockito verwendet um die Mock-Objekte zu schreiben. Eine wirkliche Empfehlung von meiner Seite. Diese Bibo ist der Hammer. Wirklich schnell zu lernen und eine wunderschöne API. Gratulation an die Entwickler zu diesem tollen Stück Software.
Schönes Wochenende!
2 Responses to Gesetz von Demeter, Unit Tests und der zweite Programmierer
Leave a Reply Cancel reply
You must be logged in to post a comment.
-
Articles
- January 2012
- December 2011
- November 2011
- October 2011
- September 2011
- August 2011
- May 2011
- April 2011
- February 2011
- January 2011
- December 2010
- November 2010
- May 2010
- April 2010
- March 2010
- January 2010
- December 2009
- November 2009
- October 2009
- September 2009
- August 2009
- July 2009
- June 2009
- May 2009
- March 2009
- January 2009
- December 2008
- November 2008
- October 2008
- September 2008
- August 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- July 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- January 2007
- December 2006
- November 2006
- October 2006
- September 2006
- August 2006
- July 2006
- June 2006
-
Meta






Freut mich, dass dir Mockito gefaellt
Hoi Patric,
ja, ist wirklich gut gemacht. Junit-Tests schreiben ist damit soooooo einfach. Gestern und vorgestern bin ich noch auf eine interessante Sache gestossen. Durch konsequentes TDD (mit Mockito und JUnit) ist bei mir im Domain-Code wie von selber eine DSL-ähnliche Struktur entstanden. Bis zur DSL war es dann nur noch eine Stunde.
Ich war so aufgeregt, dass ich die vorletzte Nacht fast nicht schlafen konnte. Wenn ich mal ein paar Minuten Zeit habe werde ich darüber schreiben.
Gruss Oliver