Testen mit Feature Flags

Software Tests und die damit verbundenen Teststrategien gewinnen aufgrund der immer komplexer werdenen Welt der IT immer mehr an Bedeutung. Niemand würde heute auf die Idee kommen neue Software ohne ausreichende Tests zu veröffentlichen. Feature Flags eignen sich sehr gut um neue Anwendungsfunktionen vorab mithilfe von Beta Testgruppen zu verproben oder Funktionen auf Basis von anderen Benutzermerkmalen nur bestimmten Personengruppen zur Verfügung zu stellen.

Neue Features können so direkt im Livesystem getestet werden, die Beschaffung umständlicher Testdaten oder das Nachstellen von Szenarien die nur im Produktivsystem verfügbar sind entfällt. Was zum einen viele Vorteile mit sich bringt, sorgt auf der anderen Seite leider für einen Nachteil beim eigentlichen Testing: Durch die Einführung von Flags erhöht sich auch die Komplixität.

Teststrategien bei automatisierten Tests

Um Testing mit Feature Flags näher zu betrachten schauen wir als erstes auf die verschiedenen Arten von Softwaretests und die daraus resultierenden Veränderungen wenn Flags in der Entwicklung eingesetzt werden.

Auf Basis der Test-Pyramide (Grafik: Practical Test Pyramid) können wir die Teststrategie für die verschiedenen Tests näher beleuchten.

TestPyramed

Unit Tests

Bei Unit Tests gibt es durch den Einsatz von Feature Flags keine Besonderheiten. Falls das Verhalten von Feature Flags getestet werden soll, kann dies über Mocks passieren. Dies empfiehlt sich vor allem beim Einsatz von SDKs. Um die Komplixität und den Aufwand zu begrenzen sollte dies aber sparsam getan werden.

Integration Tests

Bei Integration Tests wird oft das System im Ganzen oder Teile der Applikation "integriert" getestet. Hier bietet es sich an nur bestimmte Feature Flag Kombinationen zu testen. Beim Einsatz von SDKs kann z.B. der Client auf die produktive Konfigurationsumgebung (Prod) eingestellt werden. Alternativ kann auch eine eigene Konfigurationsumgebung (Integration/Staging) angelegt werden, die der Prod gleicht. Ein großer Vorteil von Feature Flags besteht hierbei nicht nur im Test von neuen Funktionen sondern auch wenn im Lifecyle bestimmte Konstellationen nachgestellt werden müssen.

UI Tests

Zu den UI Tests zählt man an der Stelle die (automatisierten) End-to-End Tests, Quality-Tests oder sogenannte User Acceptances Tests. Eine mögliche Teststrategie ist hier der Einsatz von eigenen Testusern die über Targeting Rules für das Flag "freigeschaltet" werden. Im Testing werden neue Funktionen dadurch nur für diese Benutzergruppen sichtbar. Dadurch können Tests direkt auf der Produktivumgebung durchgeführt werden ohne diese Funktionen bereits dem Endanwender zur Verfügung zu stellen. Sind alle Funktionen fehlerfrei, werden die Targeting Rules an den Flags entfernt. Dadurch können dann auch alle anderen Benutzer die Funktionen nutzen. Ein erneutes Deployment der Applikation ist dafür nicht nötig.

Testkomplixität bei Feature Flags

Um die Komplixität kurz darzustellen, nehmen wir folgendes Beispiel:

function getRedisCache() {
    // return new redis cache
}

function getMemcached() {
    // return legacy memcached instance
}

useNewCache = swClient.toggleValue('new-redis-cache', context, false)

const cacheInstance = useNewCache ? getRedisCache() : getMemcached()

// set a cache key
cacheInstance.set("cache-key", item)

In unserem fiktiven Beispiel wollen wir von Memcached auf Redis migrieren und verwenden dazu einen Feature Toggle der je nach User die neue Cache Instanz verwendet. Wenn wir alle Zustände testen wollen ergeben sich bei einem einzelnen Boolean Flag mindestens zwei Testfälle (true und false). Bei fünf Flags ergeben sich bereits 2^5 = 32 Testfälle. Spätestens beim Testen aller Flag-Kombinationen explodiert die Komplixität und es stellt sich die Frage, ob der Einsatz von Feature Flags überhaupt sinnvoll ist.

Glücklichweise ist die Situation nicht so drastisch wie sie sich zunächst darstellt. Es gibt eine Reihe von Best Practices um die Komplexität reduzieren:

Generell gilt: Es ist nicht notwendig jede Kombination zu testen, da die Feature Flags unabhängig vom eigentlichen Anwendungscode sein sollten.

Es empfiehlt sich im Testing besonderen Wert auf folgendene Kombinatonen zu legen:

Testen des Systems mit seiner geplanten produktiven Flag Konfiguration

Alle Flag Kombinationen die später auch im Live System verwendet werden sollten vorab im QA System getestet werden. Da Feature Flag Manamgenent Systeme im Regelfall unterschiedliche Umgebung unterstützen kann dies mithilfe einer eigenen Umgebungskonfiguration oder durch den Einsatz der Live Konfiguration im QA System erfolgen.

Testen des Systems mit allen aktivierten Flags

Um ein ungewünschtes Verhalten oder das versehentliche Freischalten von neuen, unfertigen Funktionen zu verhindern sollte im Test auch Augenmerk auf alle im System verwendeten Flags gelegt werden. Der Test in dieser Konstellation dient vor allem dem Finden von Seiteneffekten innerhalb der Applikation.

Testen des Systems mit den Default/Fallback Werten der Flags

Dieser Punkt stellt eine Besonderheit beim Einsatz von Feature Flags dar und sollte im Testing nicht fehlen. Feature Flags haben im Regelfall ein default Verhalten konfiguriert welches eintritt, sollte es zu einem Ausfall des Feature Flag Management Systems kommen. Häufig wird dieses Verhalten jedoch eher unterbewusst "in Kauf" genommen und steht nicht im Hauptfokus der Entwicklung. Gerade deshalb sollte im Test auf dieses Szenario besonders geachtet werden.

Testen von weiteren Kombinationen

Natürlich ist die Liste der hier aufgezählen Kombinationen nur eine grundsätzliche Empfehlung. In jeder Anwendung gilt es zu prüfen, ob für den jeweiligen Zweck nicht noch weitere Testkombinationen sinnvoll sind. Allerdings sollte von zu großer Testeuphorie Abstand genommen werden. Wie im Codebeispiel oben gezeigt empfiehlt es sich nicht alle Flag Kombinationen zu testen. Mal vom zeitlichen Aspket abgesehen übersteigt vor allem in der Automatisierung von Testfällen der Aufwand für die Testfallerstellung bei weitem den Nutzen. Es empfiehlt sich also, die Testfälle auf praktikable Szenarien zu begrenzen und auch nur diese in der Automatisierung abzubilden.