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 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.