Testautomatisierung in Zeiten von Continuous Delivery
Seite 3: Testarten
Unit-Tests
JUnit und TestNG sind die zwei bekanntesten Unit-Test-Frameworks in der Java-Entwicklung. Beide bieten umfangreiche Möglichkeiten für den Testaufbau (@Before, @BeforeTest, @BeforeSuite), die
Durchführung und den Testabbau (@After, @AfterTest, @AfterSuite). Sie genießen eine breite Unterstützung in den wichtigsten Java-IDEs (IntelliJ IDEA, Eclipse, NetBeans etc.), Build-Tools (Maven, Gradle) und CI-Systemen (Jenkins, Travis etc.). Wie erläutert, erlauben Unit-Tests eine gewisse Isolierung durch Mock-Frameworks (zum Beispiel Mockito). Das folgende Beispiel zeigt einen exemplarischen Unit-Test mit JUnit und Mockito:
public class EmployeeServiceTest {
private EmployeeService employeeService;
@Mock
private MailService mailService;
@BeforeClass
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testAddNewEmployee {
List<Employee> employees = employeeService.getAll();
Assert.assertEquals(0L, employees.size());
employeeService.add(new Employee(26, "Howard", "howard@example.com"));
employees = employeeService.getAll();
Assert.assertEquals(1L, employees.size());
Assert.assertEquals("Howard", employees.get(0).getName());
verify(mailService).send(any(MailMessage.class));
}
}
Im Test wird die Abhängigkeit zu einem MailService durch einen Mockito Mock ersetzt. Es wird dadurch der EmployeeService in Isolation zu anderen Services getestet. Mockito.verify und JUnit Assertions überprüfen, dass der neue Mitarbeiter angelegt wurde. Der Test betrachtet somit völlig isoliert den EmployeeService.
Integrationstests
Bei den Integrationstests ist Arquillian ein verbreitetes Tool für den Test von Java-EE-Anwendungen. Es bietet ein Container-Lifecycle-Management, das den Start und Stopp sowie die Konfiguration eines Java-Anwendungsservers umfasst. Zusätzlich führt das Werkzeug das Deployment der zu testenden Komponenten in einen Application Server durch und erlaubt den Zugriff auf Container-Managed-Ressourcen, zum Beispiel eine Connection-Factory oder eine EJB-Ressource. Das vereinfacht das Set-up der benötigten Infrastruktur für den Integrationstest deutlich. Arquillian unterstützt alle gängigen Application Server (Glassfish, Wildfly, Weld, Tomcat etc.). Auch Docker wird bedient, sodass die Tests innerhalb eines Anwendungs-Containers laufen können.
Ein weiteres Framework fĂĽr Integrationstests ist Citrus. Es spezialisiert sich auf die Messaging-Schnittstellen einer Anwendung. Mit fertigen Komponenten ist das Framework in der Lage, jegliche Kommunikation (HTTP, JMS, REST, SOAP, TCP/IP, Mail, File, RMI etc.) als Client oder Server zu simulieren. Im Test werden dann reale Nachrichten ĂĽber das Transportprotokoll verschickt und empfangen.
Bei der Kombination beider Frameworks in einem Integrationstest kann ein Test die realen Schnittstellen einer Anwendung im Kontext eines echten Application Server aufrufen und validieren. Das verdeutlicht das folgende Mikrodeployment in Arquillian:
@RunWith(Arquillian.class)
@RunAsClient
public class EmployeeMailTest {
@CitrusFramework
private Citrus citrusFramework;
@ArquillianResource
private URL baseUri;
@CitrusEndpoint
private MailServer mailServer;
@Deployment(testable = false)
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(RegistryApplication.class,
EmployeeResource.class,
MailService.class, Employees.class,
Employee.class, EmployeeRepository.class);
}
@Test
@CitrusTest
public void testPostWithWelcomeEmail(@CitrusResource
TestDesigner citrus) {
String serviceUri = new URL(baseUri,
"registry/employee").toExternalForm();
citrus.http().client(serviceUri)
.post()
.fork(true)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.payload("name=Rajesh&age=20&email=rajesh@example.com");
citrus.receive(mailServer)
.payload(new ClassPathResource("templates/
welcome-mail.xml"))
.header(CitrusMailMessageHeaders.MAIL_SUBJECT,
"Welcome new employee")
.header(CitrusMailMessageHeaders.MAIL_FROM,
"employee-registry@example.com")
.header(CitrusMailMessageHeaders.MAIL_TO,
"rajesh@example.com");
citrus.send(mailServer)
.payload(new ClassPathResource
("templates/welcome-mail-response.xml"));
citrus.http().client(serviceUri)
.response(HttpStatus.NO_CONTENT);
citrus.http().client(serviceUri)
.get()
.accept(MediaType.APPLICATION_JSON);
citrus.http().client(serviceUri)
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.payload("{ \"employees\": [ { \"age\": 20, \"name\":
\"Rajesh\", \"email\": \"rajesh@example.com\" } ] }");
citrusFramework.run(citrus.getTestCase());
}
}
Das Beispiel, zeigt wie das Mikrodeployment in Arquillian mit allen am Test beteiligten Klassen in der Methode createDeployment festgelegt wird. Es erfolgt automatisch beim Start des Tests in einem Application Server. Er nutzt @ArquillianResource-Annotations, um Ressourcen des Anwendungs Containers zu laden. Im Beispiel ist das die URL einer REST-Schnittstelle, die das SUT für Clients bietet. Mit Citrus als Client und Server für die HTTP- und Mail-Kommunikation ist der Test damit in der Lage, die REST-Schnittstelle aufzurufen und das Ergebnis zu validieren. Das Framework fügt als Client einen neuen Mitarbeiter über REST im SUT hinzu und simuliert anschließend einen Mail-Server für die Willkommensnachricht, die das SUT an den neuen Mitarbeiter schickt. Das komplette Beispiel mit lauffähigen Tests findet sich auf GitHub.
UI-Tests
Im Bereich der UI-Tests sind Webframeworks wie Selenium verbreitet. Anhand des von einer HTML-Seite abgeleiteten Document Object Model (DOM) lassen sich alle enthaltenen Elemente auf Basis von Namen, IDs oder CSS-Klassen identifizieren. Über diese sogenannten Identifier wird die Seite innerhalb des Browsers ferngesteuert oder mit Werten befüllt. Asserts validieren darauf folgend den neuen, veränderten Zustand. Zum Beispiel prüfen sie, ob das Textfeld den Produkttext des vorher ausgewählten Artikels anzeigt. Besteht allerdings die Anforderung, Rich Clients oder ein generiertes PDF zu validieren, stoßen reine Web-Testing-Tools an ihre Grenzen. Hier können End-2-End-Frameworks wie Sakuli einen Ausweg bieten, da zusätzlich zu den DOM-basierten Elementen Inhalte außerhalb des Browserfensters zur Verfügung stehen.
Konkret simuliert Sakuli zusätzlich zur Webseiten-Manipulation User-Aktionen per Maus und Tastatur auf der nativen UI des Betriebssystems. Die Navigation darauf erfolgt über Bildmuster, die im Testfall definiert und während der Testausführung mit der aktuellen Anzeige verglichen werden. Zum Beispiel lässt sich ein nativer Mail-Client (etwa MS Outlook) im Test öffnen und fernsteuern. Das folgende Codebeispiel zeigt einen Testfall, der im Chrome-Browser über die "PDF speichern"-Funktion ein PDF speichert, es in einem neuen Browser-Tab öffnet und abschließend validiert, ob das Dokument die gewünschten Elemente enthält. Der Test gewährleistet, dass im PDF alle gewünschten Report-Elemente enthalten sind und nicht aufgrund eines CSS-Fehlers Elemente fehlen.
//open print preview
env.type("p", Key.CTRL);
screen.find("save_button").highlight().click().type(Key.ENTER);
//open pdf in new tab and validate
env.type("tl", Key.CTRL).paste(getPDFpath()).type(Key.ENTER);
screen.waitForImage("pdf_place_order.png", 5).highlight();
[
"pdf_blueberry.png",
"pdf_caramel.png",
"pdf_chocolate.png"
].forEach(function (imgPattern) {
screen.find(imgPattern).highlight();
});
Verzichtet man auf die Webkomponente von Sakuli, lassen sich sogar native Rich Clients wie eine Swing Anwendung oder ein SAP Rich Client testen. Ein weiteres Szenario wäre, das Zusammenspiel zweier Anwendungen wie die Webeingabe eines Anfrageformulars mit Übermittlung an das Backend eines CRM-Systems mit Rich Client zu testen. Um die Tests innerhalb des CI-Builds "headless" auszuführen, bietet Sakuli Docker-Images an, die Chrome und Firefox sowie die Sakuli-Runtime vorinstalliert haben (Sakuli-Docker-Container). Die in JavaScript geschriebenen Tests werden innerhalb des Builds in den Container als Volume gemountet, ausgeführt und das Ergebnis an den Build zurückgegeben.
Will man die Tests unter Windows zum Beispiel im Internet Explorer ausführen, bietet es sich an, Windows-VMs als Worker-Slaves zu benutzen, auf denen der Build-Server die Tests von außen zur Ausführung bringt. Durch die Flexibilität für Web-Clients, die DOM-basierten Identifier zu nutzen und nahtlos zusätzliche UI-basierte Aktionen auszuführen, ist es möglich, mit einem Sakuli-Test nahezu jeden Use Case abzubilden. Das komplette Beispiel mit lauffähigem CI-Build ist auf GitHub zu finden.