Immer wieder begegnet mir der Mythos vom bösen statischen Code, der schwer zu testen und nicht zu mocken ist. Architekten und erfahrene Entwickler erzählen dieses Märchen, und die Berufsanfänger greifen es auf und wiederholen es: „Statischer Code ist böse. Er ist schwer zu testen. Statische Methoden lassen sich nicht mocken.“
DAS.IST.BLÖDSINN.
Und ich werde zeigen warum.
Testen statischer Methoden
Warum sollten statische Methoden schwerer zu testen sein als nicht-statischer Code in Instanzmethoden? Werfen wir einen Blick auf das folgende Beispiel:
1public final class StringUtil {
2
3 public static String nullSafeTrim(String s) {
4 return s == null ? "" : s.trim();
5 }
6}
Um dieses String Utility zu testen, schreibt man Unit-Tests so wie für jeden anderen Code. Man schreibt die Tests vor (Glückwunsch, Sie sind ein echter Agilist), während oder nach der Implementierung, immer eine hohe Testabdeckung im Hinterkopf. Wenden Sie alle Ihnen bekannte TDD- und BDD-Methoden an (sofern die Arbeit dadurch leichter wird):
1public class StringUtilTest {
2
3 @Test public void shouldHandleNull() {
4 assertEquals("", StringUtil.nullSafeTrim(null));
5 }
6
7 @Test public void shouldHandleEmptyString() {
8 assertEquals("", StringUtil.nullSafeTrim(""));
9 }
10
11 @Test public void shouldTrimWhiteSpace() {
12 assertEquals("foo bar", StringUtil.nullSafeTrim("\t\n\r foo bar \r\n\t"));
13 }
14
15}
Man mag nun einwenden, dass eine statische Methode vielleicht Abhängigkeit zu anderen Klassen hat und man daher verloren hat. Naja, entweder rechnen wir diese abhängigen Klassen der zu testenden Unit zu (was allerdings die Komplexität erhöht) oder man mockt diese Abhängigkeiten. „Meine statische Methode verwendet den bösen new-Operator. Ich kann die abhängige Klasse nicht mocken.“
Das stimmt schon, gilt aber genauso für nicht-statischen Code! Falls sich die Abhängigkeit über ein Interface abstrahieren lässt, können wir auf Setter-Injektion zurückgreifen. Nehmen wir an, dass StringUtil
von einem Interface Dependency
abhängt und wir dafür eine Mock-Implementation bereitstellen wollen:
1public interface Dependency {
2 void doSomething();
3}
4
5public final class StringUtil {
6
7 private static Dependency dependency;
8
9 public static void setDependency(Dependency d) {
10 dependency = d;
11 }
12 ...
13}
In unserem Test können wir die Mock-Implementierung wie folgt injizieren:
1public class DependencyMock implements Dependency {
2
3 public void doSomething() {
4 }
5}
6
7public class StringUtilTest {
8
9 @Before public void setUp() {
10 StringUtil.setDependency( new DependencyMock() );
11 }
12 ...
13}
Mocken statischer Methoden
„Ich verwende EasyMock oder Mockito. Diese Frameworks können meine statischen Methoden nicht mocken. Statischer Code ist böse.“ Dann haben Sie keine Ahnung von der Dunklen Seite der Macht … eh … von der Macht von PowerMock!
PowerMock ist eine JUnit-Erweiterung, die die Möglichkeiten von EasyMock und Mockito um das Mocken statischer Methoden (und einigem mehr) erweitert. Gehen wir von folgendem Szenario aus:
Unsere zu testende Unit ist die Klasse Calculator
, die die Berechnung zweier Integer-Werte an die Klasse MathUtil
delegiert, die nur statische Methoden anbietet:
1public class Calculator {
2
3 public int add(int a, int b) {
4 return MathUtil.addInteger(a, b);
5 }
6}
7
8public abstract class MathUtil {
9
10 public static final int addInteger(int a, int b) {
11 return a + b;
12 }
13
14 private MathUtil() {}
15}
Aus einem obskuren fachlichen Grund möchten wir das Verhalten von MathUtil
mocken, weil in unserem Testszenario die Addition andere Ergebnisse als sonst liefern soll (im echten Leben müssten wir ggfs. ein Webservice-Aufruf oder einen Datenbankzugriff mocken). Wie kriegen wir das hin? Werfen wir einen Blick auf den folgenden Test:
1@RunWith(PowerMockRunner.class)
2@PrepareForTest( MathUtil.class )
3public class CalculatorTest {
4
5 /** Unit under test. */
6 private Calculator calc;
7
8 @Before public void setUp() {
9 calc = new Calculator();
10
11 PowerMockito.mockStatic(MathUtil.class);
12 PowerMockito.when(MathUtil.addInteger(1, 1)).thenReturn(0);
13 PowerMockito.when(MathUtil.addInteger(2, 2)).thenReturn(1);
14 }
15
16 @Test public void shouldCalculateInAStrangeWay() {
17 assertEquals(0, calc.add(1, 1) );
18 assertEquals(1, calc.add(2, 2) );
19 }
20}
Zunächst verwenden wir einen speziellen Test-Runner, der vom PowerMock-Framework zur Verfügung gestellt wird. Mit der Annotation @PrepareForTest( MathUtil.class )
wird die zu mockenden Klasse vorbereitet. Diese Annotation kann auch eine ganze List von zu mockenden Klassen verarbeiten. In unserem Beispiel besteht die Liste aus einem Element MathUtil.class
.
In der setup-Methode rufen wir PowerMockito.mockStatic(...)
auf. (Wir hätten für die Verwendung der Methode mockStatic
einen statischen Import schreiben können, ohne wird aber besser sichtbar, woher die Methode stammt.)
Nun definieren wir das Mock-Verhalten unserer statischen Methode, indem wir PowerMockito.when(...)
aufrufen. Danach kommen in den eigentlichen Tests die typischen Assertions.
Wie Sie sehen konnten, haben wir gerade eine statische Methode gemockt!
Um das Beispiel laufen zu lassen, fügen Sie die folgenden Abhängigkeiten in Ihre pom.xml
ein (vorausgesetzt Sie verwenden Maven):
1<properties> 2 <powermock.version>1.4.9</powermock.version> 3</properties> 4 5<dependencies> 6 <dependency> 7 <groupId>org.powermock</groupId> 8 <artifactId>powermock-module-junit4</artifactId> 9 <version>${powermock.version}</version> 10 <scope>test</scope> 11 </dependency> 12 <dependency> 13 <groupId>org.powermock</groupId> 14 <artifactId>powermock-api-mockito</artifactId> 15 <version>${powermock.version}</version> 16 <scope>test</scope> 17 </dependency> 18 <dependency> 19 <groupId>org.mockito</groupId> 20 <artifactId>mockito-core</artifactId> 21 <version>1.8.5</version> 22 <scope>test</scope> 23 </dependency> 24 <dependency> 25 <groupId>junit</groupId> 26 <artifactId>junit</artifactId> 27 <version>4.8.2</version> 28 <scope>test</scope> 29 </dependency> 30</dependencies>
Zusammenfasssung
Ich habe gezeigt, wie Unit-Tests für statische Methoden geschrieben werden. Das war einfach. Mit Hilfe des PowerMock Frameworks waren wir sogar in der Lage, das Verhalten von statischen Methden zu mocken.
Werfen Sie einen Blick in die Dokumentation von PowerMock, um zu sehen, was PowerMock sonst noch alles kann.
Weitere Beiträge
von Tobias Trelle
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Tobias Trelle
Software Architect
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.