Legacy Code under Control – Part 3, JMockit for Experts

In my first blog post in this litte series I have explained why JMockit plays such a crucial role when you want to test legacy applications. In the next post I have presented the most important JMockit API – the Expectations API. In this final post I want to use to concrete examples from the trenches to show how JMockit makes it possible to write unit tests even under very challenging circumstances.

Hier nochmal der Überblick über die ganze Serie:
• Folge 1: Vorstellung von JMockit
• Folge 2: Die Grundkonzepte von JMockit. Ein erster Test, der ohne JMockit nur schwer zu erstellen wäre.
• Folge 3: Zwei schwierige Fälle aus der Praxis: Ein Schaltjahresproblem und ein Singleton-Factory-Lindwurm.

Recap: JMockit-Mocks

Das Grundprinzip eines JMockit-Mocks ist das Folgende: Deklariert man mittels der Annotation @Mocked eine Klasse als Mock, so wird ab sofort an keiner Stelle im System mehr der Originalcode ausgeführt. Alle Klassenmethoden sind ebenso gemockt wie alle Konstruktoren und alle Instanzmethoden. Dies gilt für neu erzeugte Instanzen ebenso wie für alle bereits im System existierenden Instanzen! Alle non-void-Methoden geben Standard-Werte wie 0, false, null, etc. zurück.
Nun kann man Expectations formulieren, d.h. Erwartungen, dass das System-Under-Test (SUT) bestimmte Methoden auf einem solchen Mock aufruft. Dies ist wichtig für verhaltensbasierte Tests.

Und man kann mittels NonStrictExpectations vorgeben, dass bestimmte Methoden eines Mocks andere als die Standard-Werte zurückgeben, ohne dass damit eine Erwartung verbunden wäre, dass bzw. in welcher Reihenfolge die Aufrufe erfolgen. Dies wird vor allem eingesetzt, um das SUT aus seiner Umgebung herauszulösen und in einen testbaren Zustand zu versetzen.

Mit dieser Funktionalität kann man nun verblüffende Tests schreiben, die ohne JMockit nur möglich wären, indem man zuerst das zu testende System anpasst – eine Änderung, die in einem Legacy-System ohne ein Sicherheitsnetz aus Unit-Tests recht heikel sein kann.

As Time goes by …

Mein erstes Beispiel ist die Hilfsklasse DateUtility, die unter anderem auch die Zahl der Tage im aktuellen Monat zurückgibt. Zu testen ist nun, ob das auch im Februar eines Schaltjahres korrekt funktioniert. Leider verwendet die Routine lastDayOfMonth() einen direkten Aufruf von new Date() (bzw. inzwischen new GregorianCalendar()), um das aktuelle Datum zu ermitteln. Und die Systemzeit lässt sich von Java aus nicht einfach setzen. Vorgefunden habe ich etwa folgenden Test:

@Test
public void lastDayOfMonthUsesLeapYear()
{
if (new Date().getMonth() == 1) {
if (DateUtility.isLeapYear()) {
assertEquals(29, DateUtility.lastDayOfMonth());
}
}
}

Auf meinen Hinweis, dass der Schaltjahrestest aber nur alle 4 Jahre einen Monat lang ausgeführt wird, bekam ich vom Entwickler zur Antwort: „Tja, besser als gar nichts. Anders lässt sich das nicht testen!“

Gemeinsam haben wir dann unter Einsatz von JMockit folgende Tests erarbeitet:

private static Calendar FEB_01_2012 = new GregorianCalendar(2012, FEBRUARY, 1);
private static Calendar FEB_01_2009 = new GregorianCalendar(2009, FEBRUARY, 1);

@Test
public void leapYearFebruaryHas29Days()
{
modifySystemDate(FEB_01_2012);
assertEquals(29, DateUtility.lastDayOfMonth());
}

@Test
public void nonLeapYearFebruaryHas28Days()
{
modifySystemDate(FEB_01_2009);
assertEquals(28, DateUtility.lastDayOfMonth());
}

private void modifySystemDate(final Calendar newDate)
{
new NonStrictExpectations(System.class) {{
System.currentTimeMillis(); result = newDate.getTimeInMillis();
}};
}

Der Test macht sich zunutze, dass Date und GregorianCalendar intern die Methode System.currentTimeMillis() aufrufen, um die aktuelle Zeit zu bestimmen. In der privaten Hilfsmethode modifySystemDate(…) wird daher die Klasse System teilweise gemockt. Siehe dazu auch im JMockit-Tutorial den Abschnitt „4.14.2 Dynamic partial mocking“:
http://jmockit.googlecode.com/svn/trunk/www/tutorial/BehaviorBasedTesting.html#dynamicPartial

About factories and snakes

Ein anderes, in Legacy Code oft zu findendes Konstrukt ist das Factory-Singleton, gerne kombiniert mit einem Getter-Lindwurm. In meinem aktuellen Projekt findet sich z.B. an einer Stelle der Aufruf

File dir = RuntimeFactory.getInstance().getFileSystemUtilities().getUserHomeDirectory();

Hier der relevante Ausschnitt aus dem Code von RuntimeFactory:

public class RuntimeFactory {

private static RuntimeFactory instance;

public static RuntimeFactory getInstance() {
if (instance == null) {
instance = new RuntimeFactory();
}
return instance;
}

public FileSystemUtilities getFileSystemUtilities() {
if (SystemProperties.getRuntimeInfo().isClient()) {
return new ClientFileSystemUtilities();
} else {
return new ServerFileSystemUtilities();
}
}
}

Hier fällt einem als erstes auf, dass man aufgrund des Singleton-Patterns dem SUT nicht einfach eine andere Klasse unterschieben kann. Als zweites erkennt man, dass in getFileSystemUtilities()der Spaß in eine weitere Runde geht. Hier wird ermittelt, ob man sich auf einem Client befindet, und zwar mit Hilfe eines weiteren Factory-Singletons SystemProperties, in das man ebenso wenig eingreifen kann.

JMockit greift uns in einer solchen Situation mit einer besonderen Art von Mocks unter die Arme, sogenannten kaskadierenden Mocks. Sie unterscheiden sich von normalen Mocks nur durch ein leicht verändertes Standardverhalten bei Methoden, die Objekte liefern. Solche Methoden liefern bei einem kaskadierenden Mock nicht null, sondern ebenfalls einen kaskadierenden Mock. Und damit lässt sich nun folgende elegante Lösung stricken:

@Test
public void checkDir()
{
new NonStrictExpectations() {
@Cascading RuntimeFactory rf;
{
RuntimeFactory.getInstance().getFileSystemUtilities().getUserHomeDirectory();
result = new File(“E:\\home”);
}
};
assertEquals(“Homedir: E:\\home”, new SUT().checkDir());
}

Siehe dazu auch im JMockit-Tutorial den Abschnitt „4.15 Cascading mocks“: http://jmockit.googlecode.com/svn/trunk/www/tutorial/BehaviorBasedTesting.html#cascading

The test is green, and what now?

Mit JMockit findet man meiner Erfahrung nach praktisch immer auch in scheinbar untestbarem Code einen Weg, eine Unit aus dem System zu isolieren und einen Test dafür zu schreiben. Das Legacy Code Dilemma „Du musst den Code ändern, um einen Test schreiben zu können. Und du musst einen Test schreiben, um den Code ändern zu können“ lässt sich so durchbrechen.

Was ist also der nächste Schritt? Das Sicherheitsnetz ist gespannt. Nun beginnt die eigentliche Arbeit! Wir ändern in kleinen Schritten den Code, so dass die Klassen und Module auch ohne „JMockit-Magie“ entkoppelt sind. In meinem zweiten Beispiel könnte das Ziel z.B. ein SUT sein, das ein UserHomeDirectory oder wenigstens die FileSystemUtilities per Dependency Injection gesetzt bekommt. Und ja, man kann mit JMockit auch einfach einen Mock für ein Interface erzeugen.

Summary

Um Legacy Systeme wieder testbar und damit wartbar und erweiterbar zu machen, ist nach wie vor äußerst mühsam und langwierig. Aber JMockit kann Wege öffnen, die vorher versperrt waren, und so Alternativen zu der höchst riskanten und ebenfalls sehr teuren Komplett-Neuentwicklung ermöglichen. Das sollte meine kleine Blog-Serie verdeutlichen.

Und natürlich reicht JMockit alleine nicht aus. Das Refactoring von Legacy-Systemen erfordert von Entwicklern und Architekten eine hohe technische Expertise, oft detailliertes Fachwissen, sowie große Disziplin und ein hohes Qualitätsbewusstsein. Und es verlangt ein kluges Management, das einerseits Verständnis für das schwer greifbare technische Thema hat, aber andererseits den Return-on-Invest nicht aus den Augen verliert. Gerade bei Refactoring-Maßnahmen von Legacy-Systemen ist es wichtig, Fokuspunkte und Prioritäten zu setzen, Ziele zu definieren und Fortschritte sichtbar zu machen.

Legacy Code under Control – Part 2, Main Concepts of JMockit

In my last post I have explained why JMockit plays such a crucial role at my current client, where we have to make a legacy application testable and thus changeable.

In part 2 I want to explain how to write unit tests using JMockit and point out the important concepts. I will create a test for a piece of legacy code that would have been very difficult to test without JMockit.

Hier nochmal der Überblick über die ganze Serie:

Folge 1: Vorstellung von JMockit
Folge 2: Die Grundkonzepte von JMockit. Ein erster Test, der ohne JMockit nur sehr schwer zu erstellen wäre.
Folge 3: Zwei schwierige Fälle aus der Praxis: Ein Remote-Service-Call und eine Schaltjahresregelung.
Recap: Die Struktur eines Unit-Tests

Die meisten Unit-Tests folgen einer einfachen Struktur.

Vorbereiten des System-under-Test (SUT).
Aufruf der zu testenden Funktionalität im SUT
Prüfen, ob das erwartete Ergebnis erreicht ist.
Bei jedem der drei Schritte erwarten uns in einem Legacy-System z.T. erhebliche Herausforderungen, die aber oft mit Konzepten von JMockit zu meistern sind.

Schritt 1: Die Vorbereitung des SUT

Beginnen wir mit Schritt 1, der Vorbereitung des SUT. Hier ist als erstes zu klären, woraus das zu testende System aus Sicht des Tests überhaupt besteht. Typischerweise handelt es sich hier um eine einzelne Instanz einer Klasse. Schon die direkten „Collaborators“, die Objekte, auf die zu testende Klasse zugreift, gehören nicht mehr dazu. Sie müssen nur ihre Rolle spielen, damit das SUT wie gewünscht funktionieren kann.

Daraus ergeben sich zwei Herausforderungen:

Erzeugen einer Instanz der zu testenden Klasse
Diese Instanz in eine geeignete Umgebung (Kontext) einbetten, in der die zu testende Funktionalität ausführbar ist.
Nehmen wir als Beispiel wieder unseren hypothetischen InvoiceUpdateRecorder aus der letzten Folge. Er protokolliert neu erstelle Rechnungen und liefert unter anderem einen Report dazu. Diese Report-Erstellung, die in der Methode getInvoiceReport implementiert ist, wollen wir testen. Hier der Code unseres SUT sowie eines Collaborators, der Klasse DbConnection:

public class InvoiceUpdateRecorder {
private DbConnection dbConnection;

public InvoiceUpdateRecorder()
{
dbConnection = new DbConnection();
// … Viele weitere Zeilen unverständliche Initialisierung
}

public String getInvoiceReport() {
String result = “”;
// … Viele Zeilen unverständlicher Code
List invoices = dbConnection.fetchAllInvoices();
if (invoices.isEmpty()) {
result = “no invoices found”;
} else{
// Ihr wollt nicht wissen, was hier alles passiert
}
return result;
}
}
public class DbConnection {
Connection jdbcConnection;

public DbConnection() {
// Holen der JDBC-Connection vom globalen Connection-Manager
jdbcConnection = JDBCConnectionManager.currentJdbcConnection();
// weiterer Initialisierungscode
}

public List fetchAllInvoices() {
List result = null;
// JDBC-Code, um Liste aus DB zu holen
return result;
}

}

Beginnen wir mit einem besonders einfachen Unit-Test, der prüft, ob als Report „no invoices found“ kommt, wenn die DbConnection keine Invoices liefert. Was ist also unser SUT? Offensichtlich nur die Klasse InvoiceUpdateRecorder, ohne die Klasse DbConnection. Ganz offensichtlich gehört auch nicht JDBC-Connection der DbConnection dazu und schon gar nicht Datenbank selbst mit ihren Inhalten. Beginnen wir also mit dem Schreiben unseres Tests, indem wir das SUT erzeugen:

@Test public void newInvoiceUpdateRecorderHasNoInvoices() {
// System-Under-Test vorbereiten.
InvoiceUpdateRecorder recorder = new InvoiceUpdateRecorder();
}

Wenn wir diesen Test ausführen, wird uns sofort die (nicht einmal zu unserem SUT gehörende) Klasse JDBCConnectionManager eine eigenartige Fehlermeldung um die Ohren hauen, die besagt, dass sie nicht in einem Zustand ist, dass sie eine JDBC-Connection liefern kann. Aber was tun? Ein Blick in den Konstruktur unseres InvoiceUpdateRecorder zeigt, dass dort hart kodiert eine neue Instanz der Klasse DbConnectionerzeugt wird. Und diese wiederum enthält im Konstruktor den fatalen Aufruf eines globalen Singletons, das uns entweder eine gültige JDBC-Connection oder eine Exception liefert. Wenn wir den Code des SUT nicht ändern wollen, scheitert bereits hier unser Versuch, einen Unit-Test zu schreiben. Und wir haben die zu testende Funktionalität noch nicht einmal aufgerufen!

Wie kann uns nun JMockit weiterhelfen?

Das meiner Meinung nach wichtigste Konzept von JMockit ist wird über das sogenannte Expectations APIbereitgestellt. Damit kann man bestimmte Typen zu gemockten Typen zu machen. Jeder Referenz-Typ kommt dafür in Frage, also konkrete Klassen (wie DbConnection) und Interfaces (wie z.B. List), aber auch abstrakte oder finale Klassen (wie z.B. java.lang.System), nicht aber primitive Typen wie boolean oder int.

Wenn ein Typ gemockt ist, werden alle Aufrufe aller seiner Methoden abgefangen. Es wird nicht die Originalimplementierung ausgeführt (sofern es eine gibt). Stattdessen tut der Mock einfach nichts, allenfalls liefert er noch einen Wert zurück. Standardmäßig ist das null, 0, false, EMPTY_LIST, … . Und das funktioniert für ALLE Methoden, also auch für als private, static, final oder native markierte Methoden, für geerbte Methoden, und auch für die Konstruktoren (die nun Mocks liefern). Und es funktioniert auch für alle Instanzen des Typs, für zum Zeitpunkt des Mockens bereits bestehende und für später erzeugte.

Die Deklaration eines Typs als „gemockt“ geschieht durch die Angabe der Annotation @Mocked vor einer Variablendefinition. Das ist nicht überall, sondern nur an folgenden Stellen erlaubt, die sich durch den Gültigkeitsbereich des Mocks unterscheiden:

Bei der Definition einer Instanzvariable in der Testklasse.
Bei der Deklaration eines Parameters für eine Testmethode (ja, das geht!).
Innerhalb eines Expectation Blocks in einer Testmethode. Das ist die Variante, die uns in unserem Beispiel weiterhilft.
Das Expectations API deckt zwei wichtige Aufgaben ab: erstens man kann Erwartungen an die Interaktion zwischen dem SUT und seiner Umgebung formulieren, also etwa „Ich erwarte, dass im Test ein Konstruktoraufruf erfolgt und dann genau dieser weitere Methodenaufruf, wobei übrigens folgender Wert zurückgegeben werden soll“. Aufrufe anderer Methoden oder eine falsche Aufrufreihenfolge werden dabei als Fehler angesehen. Man verwendet für die Definition dieser Erwartungen Expectation Blocks des Typs Expectations. Daher stammt auch der Name für das API. Zweitens man kann lediglich Rückgabewerte für Methoden vorgeben. Ob überhaupt, wie oft, und in welcher Reihenfolge die Methoden aufgerufen werden, ist dabei unerheblich. Für die Definition solcher Erwartungen an das Verhalten des Mocks verwendet man Blocks des Typs NonStrictExpectations. Diese zweite Aufgabe tritt nach meiner Erfahrung wesentlich häufiger auf als die erste. Auch in unserem Beispiel hilft uns solch ein NonStrictExpectations Block weiter.

Die Syntax von Expectation Blocks

Was hat es nun mit diesen Blocks auf sich? Schauen wir uns den Block an, den wir in unserem Beispiel benötigen:

@Test public void newInvoiceUpdateRecorderHasNoInvoices() {

new NonStrictExpectations() {
@Mocked DbConnection anyDbConnectionMock;
{
anyDbConnectionMock.fetchAllInvoices(); result = Collections.EMPTY_LIST;
}};

}

Syntaktisch haben wir hier die Erzeugung einer Instanz einer anonymen Subklasse der Klasse Expectations vor uns, die eine Instanzvariable und einen statischen Initializer enthält. Die so erzeugte Instanz wird dann umgehend dem Garbage Collector übergeben. Und das soll sinnvoll sein? Puh!

Für JMockit bedeutet dieser Block (grob gesagt):

Der Typ DbConnection ist für den Rest der Ausführung der Testmethode gemockt. D.h., ALLE Methodenaufrufe auf bereits existierenden Instanzen in der Software greifen nun nicht mehr auf den Original-Code zu, sondern auf den Mock. Ein new DbConnection() Aufruf erzeugt nun einen Mock. Statische Methoden in DbConnection sind ebenfalls gemockt.
Der Mock ist nicht-strikt. Aufrufe aller seiner Methoden sind im Rest des Tests beliebig oft möglich und liefern die Standard-Werte.
Im Initializer befindet sich der Mock im Aufzeichnungsmodus. Aufrufe und zugehörige Rückgabewerte werden aufgezeichnet. Im Beispiel wird festgelegt, dass Aufrufe der Instanz-Methode fetchAllInvoices() immer eine leere Collection liefern sollen. Achtung: anders als z.B. bei Mockito oder EasyMock müssen diese Aufrufe NICHT unbedingt auf der Instanz anyDbConnectionMock erfolgen! Jeder Aufruf, egal auf welcher Instanz, liefert nun den vorgegebenen Rückgabewert. Die Variable anyDbConnectionMock ermöglicht es uns nur
Die eigenartige Syntax hat übrigens meiner Meinung nach einige erwähnenswerte Vorteile:

Innerhalb des Initializers stehen die gemockten Aufrufe nackt ohne jegliche, die Lesbarkeit behindernde „Verzierungen“ da, also kein when(…).thenReturn(…) oder doThrow(…).when(…).… wie z.B. mit Mockito.
Die Syntax ist immer dieselbe, egal ob sich um Aufrufe von Methoden mit Rückgabe-Werten, void-Methoden, finalen Methoden oder Konstruktoren handelt.
In IDEs kann man den ganzen Expectations Block einfach zusammenfalten und sich so beim Lesen des Tests auf das Wesentliche konzentrieren.
Es ist kein zusätzliches Kommando nötig, um aus dem Aufzeichnungs- und den Abspielmodus zu kommen. Trotzdem ist der Aufzeichnunsmodus optisch und logisch deutlich getrennt vom Abspiel-Modus.
Zurück zu unserem Test, nun erweitert um den Expectations Block:

@Test public void newInvoiceUpdateRecorderHasNoInvoices() {
// System-Under-Test vorbereiten.
new NonStrictExpectations() {
@Mocked DbConnection anyDbConnectionMock;
{
anyDbConnectionMock.fetchAllInvoices(); result = Collections.EMPTY_LIST;
}};
InvoiceUpdateRecorder recorder = new InvoiceUpdateRecorder();
}

Nun bekommen wir beim Ausführen den gewünschten grünen Balken, da nicht mehr der Original-Konstruktor von DbConnection aufgerufen wird, der den „bösen“ JDBCConnectionManager aufruft. Stattdessen wird einfach nur ein Mock erzeugt und in der Instanzvariablen dbConnection von InvoiceUpdateRecorder abgelegt. Damit haben wir Schritt 1, die Vorbereitung des SUT erledigt.

Schritt 2: Der Aufruf des SUT

Der zweite Schritt ist recht einfach und kurz. Wir ergänzen den Test um den Aufruf.

String result = recorder.getInvoiceReport();

Manchmal muss man bei diesem Aufruf noch einen oder mehrere Parameter-Objekte in das SUT hineinreichen. Wenn diese gemockt werden müssen, genügt es nicht, den Mock innerhalb des Expectation Blocks zu definieren, sondern man muss den Mock als Parameter in die Testmethode hineinreichen, oder gleich als Instanzvariable der ganzen Testklasse definieren.

Schritt 3: Die Überprüfung des Ergebnisses

In unserem Fall ist dies einfach. Wir haben eine Funktion aufgerufen, die keine Seiteneffekte hat, sondern einfach ein Ergebnis liefert. Dieses können wir im Test prüfen, hier bereits in der neuen, eingängigeren JUnit 4-Syntax:

assertThat(result, is(“no invoices found”));

Unser Test sieht damit so aus:

@Test public void newInvoiceUpdateRecorderHasNoInvoices() {
// Schritt 1: System-Under-Test vorbereiten.
new NonStrictExpectations() {
@Mocked DbConnection anyDbConnectionMock;
{
anyDbConnectionMock.fetchAllInvoices(); result = Collections.EMPTY_LIST;
}};
InvoiceUpdateRecorder recorder = new InvoiceUpdateRecorder();

// Schritt 2: SUT aufrufen
String result = recorder.getInvoiceReport();

// Schritt 3: Ergebnis prüfen
assertThat(result, is(“no invoices found”));
}

Manchmal muss man allerdings auch die Interaktion des SUT mit seiner (typischerweise gemockten) Umgebung prüfen. Dafür setzt man entweder Expectations ein. Das führt allerdings dazu, dass man deutlich mehr Wissen über Implementationsdetails in den Test steckt. Ich habe es schon oft erlebt, dass der Test dann überspezifisch wird und fehlschlägt, wenn sich die Implementierung ändert, obwohl das Ergebnis, das ich eigentlich testen wollte, unverändert geblieben ist.

Das Expectations API bietet eine Fülle weiterer Features für fast alle denkbaren Fälle an. So kann man mit Hilfe von Argument Matchers vorgeben, dass z.B. ein Aufruf einer Methode mit einem beliebigen String, der „foo“ enthält, einen bestimmten Wert zurückgeben soll. Um eine Überspezifizierung eines Tests zu vermeiden, kann man nachträglich in einem Verifications Block prüfen, dass einzelne Aufrufe, die man vorher in einem NonStrictExpectations Block ermöglicht hat, stattgefunden haben.

Spiegelt sich das Ergebnis eines SUT-Aufrufs nur in einem veränderten Zustand (z.B. dem Wert der privaten Instanzvariablen secret) des SUT wider, so kann man z.B. mit folgendem Code die Prüfung dennoch vornehmen:

String result = Deencapsulation.getField(sut, “secret”);
assertEquals(“expected result”, result);

Fazit

Und damit sind wir auch schon am Ende der zweiten Folge meiner Blog-Einträge zum Thema JMockit angekommen. Wir haben einen einfach lesbaren Test für ein Legacy-System vorgenommen, in dem Collaborators vom SUT selbst unkontrollierbar erzeugt und verwendet werden. Dazu haben wir das Expectations API von JMockit eingesetzt, das inzwischen so mächtig geworden ist, dass man das noch flexiblere Mockup API, mit dem man Klassen durch Mock-Klassen teilweise ersetzen kann, nur noch in absoluten Außnahmefällen zu Hilfe nehmen muss.

Leider konnte ich trotz der Länge des Blogs nur an der Oberfläche des Expectation APIs kratzen. Weitere Details dazu liest man am besten nach unter http://jmockit.googlecode.com/svn/trunk/www/tutorial/BehaviorBasedTesting.html.

Wer sich dafür interessiert, wie JMockit sein anscheinend magisches Verhalten implementiert, der kann hier weiterforschen: es basiert auf dem Java SE5 Feature Instrumentation API (siehe http://download.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html) und nutzt intern die ASM Library (siehe http://asm.ow2.org/), um Bytecode zur Laufzeit zu modifizieren.

Im nächsten und letzten Teil werde ich dann beispielhaft einige weitere typische Problemfälle, die in Legacy-Code vorkommen, vorstellen und eine Lösung mit JMockit präsentieren, darunter Factory-Singletons, Remote-Service-Calls und eine datumsabhängige Funktionalität.

Legacy Code under Control – Part 1: Introduction to JMockit

At my current client we are using the tool “JMockit” so successfully that it has earned a proper place in my virtual toolbox. Even though the developers in my team try really hard to create good code with proper design, by now we have to wade through more than 1 million lines of Java code. And we can smell all the bad decisions of the last ten years. In essence – we have to fight with legacy code. In this fight, unit tests are an important ally, and JMockit plays a crucial role during the creation of these tests.

Because JMockit is still quite new and relatively unknown, I would like to introduce you to it in a series of blog posts:

Part 1: Introduction
Part 2: The main concepts of JMockit. A first test that would have been quite difficult to create without JMockit
Part 3: Two difficult cases “from the trenches”: A remote service call, and a leap-year logic

Why unit tests for legacy code?

Michael Feathers has expressed an important concept in his great book “Working Effectively With Legacy Code”: it is not enough to try to avoid the degeneration of code quality. These attempts will always be imperfect. Despite best efforts you will sometimes make mistakes that stack up over time and lead to a continuous decrease of code quality. This means you have to counteract proactively: you have to continuously make steps towards a simpler system that is easier to understand and has a better structure.

An important technique for these steps are automatic regression tests. For every tiny change to the structure you have to make sure that you didn’t accidentally break something. For this it is crucial to have completely automatic, fast tests that verify the I/O behavior of the many, many units of a system in isolation, the so-called unit tests. After every little change we execute these tests and thereby check that we haven’t inadvertently changed the behavior. A failing unit tests points to a local, clearly identifiable problem in one of those units.

The Legacy Code Dilemma

Unfortunately, because of the inadequate structure of legacy software, its units cannot be tested in isolation. And here we start going around in circles. In order to improve the structure of the system, we need unit tests. And in order to create unit tests, we need a better structure of the system: the legacy code dilemma.

Why is it so difficult to write unit tests for legacy code? Don’t we have years of experience and comfortable mocking frameworks like EasyMock or Mockito? With them, unit tests in which part of the system are replaced by place holders can be easily created. Have a look at this hypothetical InvoiceUpdateRecordere of which we want to test the method getInvoiceReport and the needed DbConnection (for readability reasons I omit the Import statements):

public class InvoiceUpdateRecorder {
 private DbConnection dbConnection;

 InvoiceUpdateRecorder(DbConnection aDbConnection)
 {
  dbConnection = aDbConnection;
 // ... many more lines of unreadable initialization
 }

 public String getInvoiceReport() {
  String result = "";
  // ... lots of incomprehensible code
  List invoices = dbConnection.fetchAllInvoices();
  if (invoices.isEmpty()) {
   result = "no invoices found";
  } else {
  // you really don't wanna know what happens here
  }
  return result;
 }
}

public class DbConnection {
 Connection jdbcConnection;

 public DbConnection(Connection ourJdbcConnection) {
  jdbcConnection = ourJdbcConnection;
  // more initialization
 }

 public List fetchAllInvoices() {
  List result = null;
  // JDBC code to fetch list from DB
  return result;
 }
}

And this would be a test method written using Mockito:

@Test public void newInvoiceUpdateRecorderHasNoInvoices() {
 // create mock and define behavior
 DbConnection mockedDbConnection = mock(DbConnection.class);
 when(mockedDbConnection.fetchAllInvoices()).
    thenReturn(Collections.EMPTY_LIST);

 // create system under test
 InvoiceUpdateRecorder recorder = new
    InvoiceUpdateRecorder(mockedDbConnection);

 // SUT has to work
 String result = recorder.getInvoiceReport();

 // check result
 assertTrue(result.contains("no invoices found"));
}

Wir erzeugen einen InvoiceUpdateRecorder, dem wir eine gemockte Datenbank-Verbindung untergeschieben, die immer eine leere Liste von Invoice-Objekten zurückgibt. So können wir leicht den Fall herbeigeführen, der uns interessiert, ohne tatsächlich eine Datenbank-Verbindung aufbauen zu müssen. Nachdem das SUT seine Arbeit getan hat, prüfen wir, ob das Ergebnis wie erwartet aussieht.
Und in gut strukturierten Systemen, in denen sich wie in meinem Beispiel das abhängige Objekt so einfach in das SUT per Dependency Injection hineinreichen lässt, funktioniert dieser Proxy-Ansatz auch wunderbar. Leider hat er einige, im Kontext von Legacy Code besonders nachteilige Einschränkungen:

Es können nur Abhängigkeiten des SUTs gemockt werden, die von außen (er)setzbar sind, denn irgendwie muss ja der im Test erzeugte Mock seinen Weg in das SUT finden. Oft erzeugen in Legacy-Systemen Klassen aber die Objekte, von denen sie abhängen, selbst, oder sie holen sie sich, indem sie statische Methoden konkreter Klassen aufrufen. Das Singleton-Pattern spielt hier oft eine unrühmliche Rolle.
Allgemein können mit dem Proxy-Ansatz statische, finale oder private Methoden, die das SUT aufruft, nicht gemockt werden.
Auch finale Klassen können nicht gemockt werden. Darunter sind auch viele Systemklassen, wie z.B. java.lang.System.

JMockit: Das Schweizer Messer unter den Test-Tools für Legacy Code

JMockit bietet eine erstaunliche Alternative ohne diese Einschränkungen. Gemockt werden hier grundsätzlich Klassen und alle ihre Instanzen, egal wer sie erzeugt. Und auch wenn die Syntax anfangs etwas gewöhnungsbedürftig ist, so ist sie doch verblüffend konsistent und mächtig.
Hier ein einfaches Beispiel, in dem wir einen etwas widerspenstigeren InvoiceUpdateRecorder testen wollen, nämlich einen, der seine DbConnection selbst erzeugt. Hier der relevante Ausschnitt aus unserem SUT:

public class LegacyInvoiceUpdateRecorder {
private DbConnection dbConnection;

public LegacyInvoiceUpdateRecorder()
{
// ACHTUNG: Wir erzeugen uns die DbConnection selbst über einen Konstruktor-Aufruf!
dbConnection = new DbConnection();
// …
}

// getInvoiceReport() wie wie oben
}

Und hier der entsprechende Test mit JMockit, der so mit Mockito nicht möglich wäre:

@Test public void newInvoiceUpdateRecorderHasNoInvoicesWithJMockit() {
new NonStrictExpectations() {
@Mocked DbConnection mockedDbConnection;
{
// Liefere bei Aufrufen dieser Methode eine leere Liste
mockedDbConnection.fetchAllInvoices(); result = Collections.EMPTY_LIST;
}};

// System-Under-Test erzeugen. Dieses erzeugt selbst seine DbConnection über
// new DbConnection(), bekommt aber dank JMockit nur einen Mock.
LegacyInvoiceUpdateRecorder recorder = new LegacyInvoiceUpdateRecorder();

// SUT arbeiten lassen. Dabei wird die Methode fetchAllInvoices aufgerufen
String result = recorder.getInvoiceReport();

// Ergebnis prüfen
assertTrue(result.contains(“no invoices found”));
}

In diesem Test geschieht folgendes: Zunächst wird für die Dauer des Tests die Klasse DbConnectiongemockt. Alle Aufrufe von Konstruktoren von DbConnection liefern nun Mocks. So auch der hier nicht sichtbare Aufruf innerhalb des Konstrukturs von InvoiceUpdateRecorder. Außerdem liefern nun alle Aufrufe von Klassen- oder Instanzmethoden von DbConnection Default-Werte: null, false, 0,… . Nur fetchAllInvoices() liefert das explizit vorgegebene Ergebnis (Bitte nicht stören lassen durch die eigenartige Syntax, in der diese Vorgabe erfolgt. In der nächsten Folge dieser kleinen Serie erkläre ich, was es damit auf sich hat und warum das gar keine so schlechte Idee ist). Der Rest entspricht 1:1 dem vorigen Beispiel: wieder rufen wir das SUT auf und prüfen das Ergebnis.

Für erste eigene Experimente mit JMockit empfehle ich die Lektüre des Getting-Started Artikel auf der JMockit-Homepage unter http://jmockit.googlecode.com/svn/trunk/www/gettingStarted.html.

Mein Fazit

Die Ausrede „Dieser Code ist untestbar!“ gilt mit JMockit nicht mehr. In den allermeisten Fällen lassen sich Abhängigkeiten in Java-basierten Legacy-Systemen damit aufbrechen. So war es uns in vielen Fällen möglich, die für eine verantwortliche Umstrukturierung erforderlichen Unit-Tests zu erstellen, ohne in den eigentlichen Code des SUT eingreifen zu müssen Dank JMockitkonnten wir tatsächlich den langen, mühsamen Weg aus der Legacy-Code-Hölle in Angriff nehmen.

In der nächsten Folge schauen wir uns dann einige zentrale Konzepte von JMockit und seine verblüffende Syntax etwas näher an.