Agiles Testen von JIRA Plugins (Teil 3): Systemtests

Keine Kommentare

Nach Unit- and Wired Tests stellen Systemtests einen weiteren Testtyp der Testpyramide dar, den wir im Kontext der Plugin Entwicklung für JIRA betrachten wollen. In diesem Artikel werden wir – d.h. Raimar Falke und ich – zwei weitere Testarten vorstellen, die die gesamte bzw. nahezu die gesamte Applikation testen: Tests der Weboberfläche und REST-API Tests.

Hinweis: Dies ist eine 4-teilige blog Serie. Agiles Testen von JIRA Plugins (Teil 1), Wired Tests (Teil 2), Systemtests (Teil 3), CI Server Integration und Test Coverage (Teil 4). Darüber hinaus finden Sie auch eine Einführung zum Thema JIRA mit Plugins erweitern in unserem blog.

Testen der Weboberfläche

Atlassian stellt eine Reihe von Tools bereit, die die Erstellung von End-to-End Tests für ihre Produkte vereinfacht. Die wichtigsten darunter sind TestKit, welches die Erstellung eines „Backdoor“ vornimmt, welcher für die Durchführung administrativer Aufgaben oder das Einspielen von Testdaten genutzt werden kann, sowie eine große Zahl an Pageobjects für alle Applikationen. Auch wenn TestKit nicht genutzt werden muss, ist die Verwendung sehr zu empfehlen, da es viele Aufgaben, wie bspw. das Erzeugen eines definierten Zustands der zu testenden Instanz zum Kinderspiel macht. Ein weiteres Tool für die Entwicklung von End-to-End Tests, welches TestKit stark ähnelt, ist FuncTest. Der wichtigste Unterschied ist, dass FuncTest Selenium für die Ausführung administrativer Aufgaben nutzt, während TestKit dies über eine REST-API erledigt.

Setup

Die Definition der benötigten Dependencies ist einfach und sieht typischerweise so aus:

<dependency>
  <groupId>com.atlassian.jira.tests</groupId>
  <artifactId>jira-testkit-client</artifactId>
  <version>${testkit.version}</version>
  <scope>test</scope>
</dependency>
 
<dependency>
  <groupId>com.atlassian.jira</groupId>
  <artifactId>atlassian-jira-pageobjects</artifactId>
  <version>${jira.version}</version>
  <scope>test</scope>
  <exclusions>
    <!-- excluded due to clash with other SLF implementation -->
    <exclusion>
      <artifactId>slf4j-simple</artifactId>
      <groupId>org.slf4j</groupId>
    </exclusion>
  </exclusions>
</dependency>

Beim Versuch, Seleniumtests auf einem System mit einem 64-Bit Linux, z.B. einem CI Server in einer AWS EC2 Instanz, auszuführen, wird man jedoch Probleme bekommen. Der Grund dafür ist, dass das SDK seine eigenen Browser mitbringt, unter anderem einen Firefox 12. Dieser hat jedoch ein bekanntes Problem auf 64-Bit Linux Systemen. Die Lösung besteht darin, explizit eine neuere Version von Selenium inklusive der zugehörigen Atlassian Browser als Dependency zu benutzen:

<!-- the following dependencies are needed only for running on 64bit Linux, 
     since the default Firefox 12 has problems -->
<dependency>
  <groupId>com.atlassian.browsers</groupId>
  <artifactId>atlassian-browsers-auto</artifactId>
  <version>2.3.2</version>
  <scope>test</scope>
</dependency>
 
<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-java</artifactId>
  <version>2.25.0</version>
  <scope>test</scope>
</dependency>

Der letzte Schritt bei der Vorbereitung der Projekts besteht darin, das TestKit Plugin in der JIRA-Testinstanz zu aktivieren. Dies kann man entweder durch eine Property in der POM oder durch Hinzufügen des Plugins in der Konfiguration des JIRA Builds erreichen.

  • Als Property:
<prroperties>
  ...
  <plugins>com.atlassian.jira.tests:jira-testkit-plugin:${testkit.version}</plugins>
  ...
</properties>
  • Als Plugin Artefakt in der AMPS Plugin Konfiguration:
<plugin>
  <groupId>com.atlassian.maven.plugins</groupId>
  <artifactId>maven-jira-plugin</artifactId>
  <version>${amps.version}</version>
  <extensions>true</extensions>
  <configuration>
    ...
    <pluginArtifacts>
      ...
      <pluginArtifact>
        <groupId>com.atlassian.jira.tests</groupId>
        <artifactId>jira-testkit-plugin</artifactId>
        <version>${testkit.version}</version>
      </pluginArtifact>
    </pluginArtifacts>
  </configuration>
</plugin>

Tests schreiben

Das Artefakt für die JIRA Pageobjects stellt verschiedene Mittel bereit, die eine effektive Implementierung von UI Tests unterstützen. Zunächst stellt es eine abstrakte Klasse (AbstractJiraPage) für alle (eigenen) Pageobjects bereit, die ein Grundgerüst für die Implementierung bietet. Wenn man die Klasse erweitert, müssen die Methoden getUrl() und isAt() implementiert werden: getUrl() liefert die URL zurück, zu der navigiert werden muss, um die Seite zu laden, die durch das Pageobject repräsentiert wird; isAt() prüft, ob die Seite auch korrekt geladen ist (bspw. durch Prüfung, dass ein bekanntes Element sichtbar ist). Dabei wird grundsätzlich angenommen, dass ein Element nicht unbedingt sofort verfügbar ist. Zusätzlich stellt die abstrakte Klasse eine PageBinder-Instanz bereit, die es ermöglicht, die aktuell geladene Seite an ein Pageobject zu binden, inklusive der auf der Seite enthaltenen Elemente. Ein Beispiel für ein Pageobject kann also folgendermaßen aussehen:

package pages;
 
import com.atlassian.jira.pageobjects.pages.AbstractJiraPage;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.query.TimedCondition;
 
public class FooBarPage extends AbstractJiraPage {
  @ElementBy(id ="save") private PageElement saveButton;
  @ElementBy(id ="some-input") private PageElement someInput; 
 
  @Override
  public String getUrl() {
    return"/secure/admin/foo-bar.jspa";
  }
 
  @Override
  public TimedCondition isAt() {
    return someInput.timed().isVisible();
  }
 
  public FooBarPage save() {
    saveButton.click();
    return pageBinder.bind(FooBarPage.class);
  }
 
  public void setSomeInput(String input) {
    someInput.type(input);
  }
 
  public String getSomeInput() {
    return someInput.getValue();
  }
}

Die eigentlichen UI Tests müssen in einem Package mit Präfix it.* liegen, da sie als Integrationstests eine laufende JIRA Instanz benötigen. Ein Beispiel für einen Test, der FuncTest benutzt, sieht folgendermaßen aus:

package it.foo.bar;
 
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import pages.FooBarPage;
import com.atlassian.jira.functest.framework.FuncTestCase;
import com.atlassian.jira.pageobjects.JiraTestedProduct;
import com.atlassian.jira.pageobjects.config.EnvironmentBasedProductInstance;
import com.atlassian.jira.testkit.client.Backdoor;
import com.atlassian.jira.testkit.client.util.TestKitLocalEnvironmentData;
 
public class FooBarPageTest extends FuncTestCase {
  // the setupUpTest() method sets this
  private JiraTestedProduct jira;
 
  @Override
  protected void setUpTest() {
    super.setUpTest();
    Backdoor backdoor = new Backdoor(new TestKitLocalEnvironmentData());
    backdoor.restoreBlankInstance();
    jira = new JiraTestedProduct(new EnvironmentBasedProductInstance());
  }
 
  @Test
  public void test_that_save_works() {
    FooBarPage page = jira.gotoLoginPage().loginAsSysAdmin(FooBarPage.class);
 
    page.setSomeInput("my input");
    page.save();
 
    jira.gotoHomePage();
    page = jira.goTo(FooBarPage.class)
    assertThat(page.getSomeInput(),is("my input"));
  }
}

Tests ausführen

Frontend Tests werden ganz normal als Integrationstests betrachtet und ausgeführt. Wenn man jedoch wie im obigen Beispiel FuncTest benutzt, muss man mit einer kleinen Überraschung rechnen: die Tests scheitern, wenn man nicht ein (leeres) Verzeichnis src/test/xml im Projekt angelegt hat. Eine Alternative (die wir auch empfehlen), ist auf die Verwendung von FuncTest zu verzichten. Der Test erweitert dann keine Klasse mehr (sieht man von der impliziten Vererbung von java.lang.Object ab) und die Methode setUpTest aus dem Beispiel ist folgendermaßen umzuschreiben:

@Before
public void setUp() {
  jira = TestedProductFactory.create(JiraTestedProduct.class);
  Backdoor backdoor = new Backdoor(
    new ProductInstanceBasedEnvironmentData(jira.getProductInstance()));
  backdoor.restoreBlankInstance();
}

REST-API Tests

Auch wenn UI Tests sowohl den Java-Code des Servers als auch den Javascript (und HTML) Code auf dem Client testet, sind sie für ihren hohen Implementierungs- und Wartungsaufwand und die lange Ausführungsdauer (im Vergleich zu anderen Testarten) bekannt. Eine Alternative für den Test der Serverseite stellen Tests der vom Server bereitgestellten API dar. Im Fall von JIRA ist das eine REST-API. Wie sich herausstellt, gibt es bei der Implementierung von REST-API Tests für JIRA keine großen Besonderheiten zu beachten. Da es sich ebenfalls um Integrationstests handelt, müssen sie wie UI Tests in einem Package mit Präfix it.* liegen. Das Framework für die Entwicklung der Tests kann frei gewählt werden. Aus unserer Erfahrung können wir den Einsatz von REST Assured empfehlen. Danach gibt es nur zwei Dinge, die zu erwähnen sind:

  1. Wird der Test im Rahmen des Maven-Builds ausgeführt, wird durch das SDK eine System-Property “baseurl” gesetzt, die auf die laufende Testinstanz verweist. Startet man den Test jedoch aus der IDE, ist die Property nicht gesetzt. Als Fallback sollte man sie daher im Test selbst setzen, bspw. auf „http://localhost:2990/jira“.
  2. Die Authentifizierungsmethode sollte auf präemptiv gesetzt werden, da JIRA statt Statuscode 401 (Unauthorized) den Code 200 (Ok) zurücksendet, auch wenn eine Authentifizierung benötigt wird. Aus diesem Grund kann die automatische Authentifizierung im Fehlerfall, wie sie üblich ist, nicht verwendet werden.

Ein Teil eines Testsetups könnte daher folgendermaßen aussehen:

  String urlPrefix = System.getProperty("baseurl", "http://localhost:2990/jira");
  RestAssured.baseURI = urlPrefix + urlSuffix;
 
  PreemptiveBasicAuthScheme authScheme = new PreemptiveBasicAuthScheme();
  authScheme.setUserName(username);
  authScheme.setPassword(password);
  RestAssured.authentication = authScheme;

Zusammenfassung

Die Implementierung von seleniumbasierten Frontendtests benötigt nur wenige spezifische Anpassungen an JIRA und auch die lokale Ausführung der Tests ist einfach. Das Ganze sieht etwas anders aus, wenn man die Tests auf einem CI Server ausführen möchte. Tests der REST-API sind im Gegensatz dazu weniger problematisch, sind einfacher zu implementieren und laufen auch schneller. Wir empfehlen sie daher als zweite Testart nach Unittests. Hat das Projekt einen große Menge an eigenem Javascript oder muss eine breites Spektrum an Browsern oder Betriebssystemen zuverlässig bedient werden, lohnt sich evtl. auch die Investition in die Entwicklung von Oberflächentests.

Im nächsten Artikel dieser Serie werfen wir einen Blick auf die Besonderheiten, die bei der Auführung von Tests in einem CI Server eine Rolle spielen.

Thomas Strecker

Thomas Strecker ist als Senior IT Consultant bei der codecentric AG tätig und leitet mit Marcel Wolf den Standort Berlin. Neben seiner Führungsarbeit beschäftigt er sich in Projekten weiterhin mit Themen wie Java Profiling und Performance-Analyse, Microservices oder auch Testautomatisierung im Kontext agiler Softwareentwicklung.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.