Wer eigenen Code für Salesforce schreibt, muss für diesen auch Modultests (englisch Unittests) erstellen. Salesforce misst hierbei die Anzahl der durch Tests abgedeckten Codezeilen und erlaubt das Überführen in das Produktivsystem nur dann, wenn mehr als 75 Prozent des gesamten Codes durch Tests abgedeckt werden (Code Coverage).
Während in anderen Bereichen diese Art von automatisierten Tests oft dem Zeit-, Kostendruck oder Faulheit zum Opfer fallen, wird man also von Salesforce gezwungen, diese automatisierten Tests zur Verfügung zu stellen. Natürlich kann man auch in Salesforce Mittel und Wege finden, der Testabdeckung zu genügen, ohne wirklich zu testen. Ich würde jedoch dazu raten, es weniger als Zwang zu sehen denn als Chance und diese auch zu nutzen. Mein Ziel mit diesem Beitrag ist es, nicht eine komplette Einführung zum Thema zu geben. Vielmehr will ich ganz subjektiv auf bestimmte Werkzeuge eingehen und Empfehlungen aussprechen.
Testgetriebene Entwicklung
Unabhängig von der generellen Diskussion über Testgetriebene Entwicklung im Allgemeinen, empfiehlt es sich gerade für Salesforce zuerst den Test und dann den Code zu erstellen.
Im Gegensatz zur Programmierung in herkömmlichen Programmiersprachen bewegt man sich in Salesforce in einem eingeschränkten Umfeld. So können zum Beispiel nicht alle Standardobjekte im Rahmen eines Tests erzeugt oder gemockt werden. Das bedeutet in der Praxis, dass der Code stärker in Hinblick auf die Testbarkeit strukturiert werden muss. Es ist daher, nicht nur für Anfänger, oft einfacher, den Test zuerst zu schreiben und danach den Code in Angriff zu nehmen.
Test.isRunningTest()
Diese Methode erlaubt es im Programm, zum Zeitpunkt der Ausführung zu erkennen, ob es im Rahmen eines Tests ausgeführt wird. Abhängig davon kann dann zum Beispiel ein Aufruf an ein externes System erfolgen oder eine vordefinierte Antwort zurückgegeben werden.
Persönlich vermeide ich es, diese Methode zu benutzen, wenn ich kann. Sie führt dazu, dass ein Teil des Codes nicht getestet wird und auch keine Testabdeckung erfährt. Ich halte es für schwierig, wenn sich Code im Produktivbetrieb anders verhält als im Testbetrieb. Zu schnell werden Probleme durch Tests nicht erkannt und führen im Produktivbetrieb dann zu Schwierigkeiten.
In fast allen Fällen kann man stattdessen Mocking einsetzen oder den Code anders gestalten.
Die isTest(SeeAllData=true) Annotation
Während der Ausführung von Tests haben diese normalerweise keinen Zugriff auf die in der Org gespeicherten Daten. Mittels isTest(SeeAllData=true) vor einer Testklasse oder Methode erhalten Sie diesen Zugriff jedoch. Während es verlockend sein kann, einfach bestehende Daten im System zu Testzwecken zu verwenden, anstatt mühsam neue Daten für den Test einfügen zu müssen, würde ich doch davon abraten. Hauptsächlich deshalb weil diese dann zum Beispiel dann auch auf der Sandbox des Entwicklers vorhanden sein müssten oder von außen verändert werden können.
Die TestVisible Annotation
Die TestVisibile Annotation erlaubt es, Tests auf Methoden im zu testenden Code zuzugreifen, welche normalerweise nicht verfügbar sind aufgrund Ihres Sichtbarkeitsbereichs. So können einzelne private Methoden einer Klasse getestet werden.
Eine immer wieder generell geführte Diskussion ist die, ob man private Methoden überhaupt separat testen sollte oder der Test durch Aufruf der öffentlichen Methoden einer Klasse erfolgen sollte. Persönlich folge ich hier einem pragmatischeren Ansatz und entscheide abhängig vom Code und dem Aufwand, der nötig ist, die verschiedenen Testszenarien zu erstellen, um alle Codepfade zu testen.
Hilfsklassen
Es empfiehlt sich sehr, für die Erzeugung häufig benötigter Objekte Helferklassen zu erstellen,um Code-Duplikation zu vermeiden und Änderungen an der Erzeugung dieser Objekte schnell für alle Tests übernehmen zu können.
Benötigt man zum Beispiel im Test ein Account Objekt, könnte die Helferklasse eine Methode namens generateAccount haben. Diese würde ein Account Objekt erzeugen, speichern und dann zurückgeben. Um hierbei flexibler zu sein, kann diese Methode Parameter haben, um bestimmte Werte auf dem neuen Account Objekt zu setzen.
Auch lässt sich das gut erweitern, um Abhängigkeiten handhaben zu können. Benötige ich zum Beispiel ein Kontaktobjekt, das mit einem Account verbunden ist, so kann ich o.g. Helferklasse um eine Methode erweitern, die diesen erzeugt und dabei entweder einen Account hinterlegt, den ich als Parameter mitgebe oder einfach selbst die Methode zum Generieren eines Account aufruft und diesen dann im neuen Kontaktobjekt hinterlegt.
Wenn auch recht einfach, von der Struktur her erleichtert dieser Ansatz die Erstellung von Tests ungemein ohne zu viel zusätzliche Komplexität hinzuzufügen.
Mocking und das eigene Mocking-Framework
Meiner Erfahrung nach beschäftigen sich viele nie oder erst sehr spät mit den Möglichkeiten des Mocking innerhalb Salesforce. Dies mag an der Komplexität oder dem fehlenden Verständnis liegen. Ich kann jedem nur raten, sich damit auseinanderzusetzen.
Während Salesforce selbst kein fertiges Mocking-Framework zur Verfügung stellt, bietet es doch die Möglichkeit, sein eigenes zu erstellen und sie in die sogenannte Stub API zur Verfügung stellen. Ebenso wie die o.g. Hilfsklassen kann ein solches, eigenes Framework, die Arbeit mit Tests sehr erleichtern und beschleunigen.
Weiterführendes
Es gibt im Netz sehr viele Blogeinträge zu diesem Thema sowie Dokumentation von Salesforce selbst. Als Einstieg würde ich den englischsprachigen Artikel „How to Write Good Unit Tests“ empfehlen. Dieser gibt nicht nur eine Einführung, sondern enthält auch viele Hintergrundinformationen und Empfehlungen.