In die­sem Bei­trag wird eine umfang­rei­che Test­stra­te­gie im Rah­men der Soft­ware­ent­wick­lung vor­ge­stellt, die funk­tio­nelle und nicht funk­tio­nelle Abde­ckung durch auto­ma­ti­sierte Test­rou­ti­nen anstrebt. Auf die Bedeu­tung einer umfas­sen­den Test­ab­de­ckung einer Anwen­dung wird an die­ser Stelle nicht expli­zit ein­ge­gan­gen, son­dern nur dar­auf ver­wie­sen, dass funk­tio­nale und nicht-funk­tio­nale Tests unab­ding­bar wer­den, wenn eine Anwen­dung einen gewis­sen Kom­ple­xi­täts­grad erreicht hat. Tests bil­den einen bedeu­ten­den Grund­pfei­ler in Ent­wick­lung und Wei­ter­ent­wick­lung von moder­ner Soft­ware und in der Daten­ver­ar­bei­tung.[1,2]

Um von einem Anwen­dungs­sze­na­rio aus­zu­ge­hen, das als Ori­en­tie­rungs­hilfe für das zu tes­tende „Objekt“ dient, neh­men wir einen Anwen­dungs­auf­bau wie er in Abb. 1. sche­ma­tisch gezeigt ist.

Teststrategie einer (Big-Data) Anwendung Bild1
Abbil­dung 1 Auf­bau einer Anwen­dung: Die ein­zel­nen Zoom­stu­fen sol­len die Gra­nu­la­ri­täts­stu­fen der Anwen­dung reprä­sen­tie­ren. Auf der Stufe Module setzt sich eine ein­zelne Trans­for­ma­tion aus einer oder meh­rere Funk­tio­nen zusammen.

Die Anwen­dung hat eine oder meh­rere Daten­quel­len und die Ver­ar­bei­tung erfolgt nach dem gän­gi­gen Sta­ging-Mus­ter: Extra­hie­ren, (Vor­be­rei­ten), Ver­ar­bei­tung (pro­ces­sing), Nach­be­ar­bei­tung (post­procs­sing) und Export. Hier ist es ohne wei­te­res denk­bar Schritte aus­zu­las­sen oder wei­tere hinzuzunehmen.

Wir neh­men an, die Daten­ver­ar­bei­tung erfolgt auf einem ver­teil­ten Datei­sys­tem (HDFS) mit­tels des Apa­che Spark-Frame­works. Der beschrie­bene Auf­bau ist für das vor­ge­stellte Test­vor­ge­hen nicht erfor­der­lich, er soll ledig­lich als ein mög­li­cher Anwen­dungs­zweck her­an­ge­zo­gen wer­den. Das vor­ge­stellte Vor­ge­hen kann je nach Ein­satz­zweck adap­tiert wer­den. Um eine mög­lichst breite Abde­ckung von Feh­ler­quel­len zu gewähr­leis­ten, soll­ten Tests auf einer brei­ten Gra­nu­la­ri­tät der Anwen­dung anset­zen und dadurch unter­schied­li­che Aspekte und Feh­ler­quel­len prü­fen (vgl. Abb. 1). Dies kann mit Unit‑, Inte­gra­tion- und End-To-End-Tests erreicht wer­den. Im Fol­gen­den wer­den der Ein­satz­zweck und Mög­lich­kei­ten ein­zel­ner Testar­ten ausgeführt.

Unit-Tests

Im Fol­gen­den soll die bei Wiki­pe­dia ange­ge­bene Defi­ni­tion eines Unit-Test als Basis für ein gemein­sa­mes Ver­ständ­nis her­an­ge­zo­gen werden:

Unit tes­ting is a soft­ware tes­ting method by which indi­vi­dual units of source code—sets of one or more com­pu­ter pro­gram modu­les tog­e­ther with asso­cia­ted con­trol data, usage pro­ce­du­res, and ope­ra­ting procedures—are tes­ted to deter­mine whe­ther they are fit for use.”[3]

Mit die­ser Art von Tests wer­den ein­zelne Funk­ti­ons- und Logik­schritte inner­halb einer Trans­for­ma­tion (siehe Abb. 1) abge­deckt, wobei die gesamte Trans­for­ma­tion an sich gege­be­nen­falls durch einen Unit-Test geprüft wer­den kann. Für die gän­gi­gen Pro­gram­mier­spra­chen ste­hen Test-Frame­works zur Ver­fü­gung, wel­che die Kon­struk­tion von Tests erheb­lich ver­ein­fa­chen. Unit-Tests las­sen sich dar­über hin­aus sehr gut für die Ent­wick­lung ein­set­zen. Nach der Fest­le­gung der Funk­tio­na­li­tät kann zunächst ein Unit-Test erstellt und anschlie­ßend der dazu­ge­hö­rende Code geschrie­ben, bezie­hungs­weise fer­tig gestellt wer­den  Ein gro­ßer Vor­teil der Unit-Tests besteht darin, dass mit den Tests stets der Ent­wick­lungs­stand über­prüft wer­den kann. Wird hin­ge­gen der Test zuerst ver­fasst, muss sich der Ent­wick­ler mit dem Wesen der Funk­tio­na­li­tät befas­sen, was häu­fig zu einem bes­se­ren Ver­ständ­nis des Pro­blems führt und somit in einem qua­li­ta­tiv hoch­wer­ti­ge­ren Code resul­tiert. Neben Black-Box Tests soll­ten White-Box Tests spe­zi­fi­sche Eigen­ar­ten der Imple­men­ta­tion berück­sich­ti­gen und auf Grenz­fälle ein­ge­hen. Dies lässt sich gut mit ran­do­mi­sier­ten Tests ver­knüp­fen, die zwar mehr Imple­men­tie­rungs­auf­wand bedeu­ten, jedoch nicht bedachte Fälle abde­cken kön­nen.[1] Unit ‑Tests las­sen sich in der Regel ein­fach umset­zen, soll­ten schnell und ein­fach aus­zu­füh­ren sein und bei jedem Build der Anwen­dung erfol­gen, – näm­lich als Prüf­kri­te­rium für einen erfolg­rei­chen Build.

Wird mit dem Spark-Frame­work gear­bei­tet, müs­sen für die Tests Spark-Con­text und Spark-Ses­sion auf­ge­baut wer­den, dazu sollte in der Regel eine lokale Instanz genutzt wer­den. Der Auf­bau von Spark-Con­text und Spark-Ses­sion nimmt mehr Zeit in Anspruch und ver­lang­samt des­halb die Test­aus­füh­rung, vor allem wenn dies in zahl­rei­chen Tests der Fall ist. Abhilfe kann es sein Tests mit nur ein Spark-Con­text auf­zu­bauen und dar­aus alle benö­tig­ten Spark-Ses­si­ons abzu­lei­ten. Wei­ter­hin kann es für kom­plexe Logik prak­ti­ka­bel sein auf die von Spark bereit gestellte map oder map­Par­ti­ti­ons Methode zurück­zu­grei­fen. Inner­halb der Map-Funk­tion kann die Funk­tio­na­li­tät der nati­ven Pro­gram­mier­spra­che genutzt wer­den und somit auch die zur Ver­fü­gung ste­hen­den Test­mög­lich­kei­ten. So kann eine kom­plexe Ver­ar­bei­tung gut mit Unit-Tests abge­deckt wer­den, ohne dabei mit Data­sets und Data­Frames han­tie­ren zu müssen.

Nichts­des­to­trotz geben Unit-Tests so gut wie keine Aus­kunft zur Funk­tio­na­li­tät der Ver­ket­tung von Code­ab­schnit­ten, die ein­zeln mit Unit-Tests abge­deckt sind. Das bedeu­tet ein erfolg­rei­chen iso­lier­ter Test von Trans­for­mer 1 und Trans­for­mer 2 lässt nicht dar­auf schlie­ßen, dass Trans­for­mer 1 und 2 gemein­sam erfolg­reich aus­ge­führt wer­den. Um dies sicher­zu­stel­len, sollte mit Hilfe von Inte­gra­ti­ons­tests auf eine grö­bere Gra­nu­la­ri­tät hin geprüft werden.

Inte­gra­ti­ons-Test

Für ein gemein­sa­men Ver­ständ­nis eines Inte­gra­tion­tests wird im Fol­gen­den auf die in Wiki­pe­dia gege­bene Defi­ni­tion zurückgegriffen:

“Inte­gra­tion tes­ting (some­ti­mes cal­led inte­gra­tion and tes­ting, abbre­via­ted I&T) is the phase in soft­ware tes­ting in which indi­vi­dual soft­ware modu­les are com­bi­ned and tes­ted as a group. Inte­gra­tion tes­ting is con­duc­ted to eva­luate the com­pli­ance of a sys­tem or com­po­nent with spe­ci­fied func­tio­nal requi­re­ments.” [4]

An die­ser Stelle wird ein gan­zes Modul mit defi­nier­tem Ein­gangs- und Aus­gangs­da­ten­strom getes­tet (vgl. Abb. 1), damit wird das Zusam­men­spiel der Trans­for­ma­tio­nen, die mit Unit-Tests geprüft wer­den, abge­deckt.   Außer­dem kön­nen Inte­gra­ti­ons­tests vom Ent­wick­ler zur Veri­fi­zie­rung der Ver­ar­bei­tungs­lo­gik genutzt wer­den, wenn die Inte­gra­ti­ons­tests ohne ein Deploy­ment-Schritt lokal aus­ge­führt wer­den kön­nen. Dies erspart zudem unnö­tige “Kor­rek­tur-Deploy­ments”. Für die Erstel­lung eines Inte­gra­ti­ons­tests müs­sen Ein- und Aus­gabe nachgestellt/imitiert (Mock) wer­den, wofür gege­be­nen­falls ein von der Anwen­dung abwei­chen­des For­mat gewählt wer­den kann. Somit lässt sich bei­spiels­weise ein manu­el­len Abgleich ein­fa­cher aus­füh­ren. Der ein­fa­che Abgleich und manu­elle Kon­trolle bie­ten durch­aus Vor­teile bei der Ent­wick­lung. Dabei ist anzu­stre­ben, die Tests nicht zu umfang­reich zu gestal­ten, um eine Lauf­zeit im Minu­ten­be­reich zu ermöglichen.

Um das Zusam­men­spiel von Modu­len zu prü­fen, sollte ein wei­te­res Test­sze­na­rio erfol­gen, das die gesamte Anwen­dung testet.

End-To-End-Test

Unter einem End-to-End-Test ver­steht man den Durch­lauf der gesam­ten Anwen­dung mit Berück­sich­ti­gung aller betei­lig­ten tech­ni­schen Kom­po­nen­ten inklu­sive des Imports und des Exports. Ein End-To-End Test erfor­dert einen höhe­ren Auf­wand als die ande­ren vor­ge­stell­ten Testar­ten, stellt aber die tech­ni­sche Funk­tio­na­li­tät der gesam­ten Anwen­dung sicher. Zusam­men mit einer vor­de­fi­nier­ten Ein­gabe und Prü­fung der erwar­te­ten Aus­gabe (End-to-End Test als Regres­si­ons-Test) [5]  kann zudem die fach­li­che Veri­fi­ka­tion erfol­gen. Dabei kön­nen meh­rere Durch­läufe vor­ge­nom­men wer­den, um unter­schied­li­che fach­li­chen Sze­na­rien durch­zu­spie­len. Die Ver­ar­bei­tung gro­ßer Daten­men­gen  kann ggf. meh­rere Stun­den in Anspruch neh­men. Je nach Zeit­auf­wand bie­tet es sich an hier­für nächt­li­che oder Wochen­end­läufe zu nutzen.

Zusam­men­fas­sung

Erst die Prü­fun­gen und Tests auf unter­schied­li­chen Gra­nu­la­ri­täts­stu­fen der Anwen­dung kön­nen eine zuver­läs­sige Aus­sage über den Zustand einer Anwen­dung tref­fen. Eine auto­ma­ti­sierte Prü­fung unter­schied­li­cher Gra­nu­la­ri­täts­stu­fen, bei der fach­li­che und tech­ni­sche Aspekte berück­sich­tigt wer­den, kön­nen die Ent­wick­lung und Deploy­ment beschleu­ni­gen, indem rasches Feed­back durch die Test­rou­ti­nen gege­ben wird. Im All­ge­mei­nen ist die Ent­wick­lungs­ar­beit mit Tests ein ite­ra­ti­ver Pro­zess und mit einem ent­spre­chen­den Auf­wand ver­knüpft, der sich jedoch bei kom­ple­xen Anwen­dun­gen rasch bezahlt macht.

Ver­weise

[1] https://software.rajivprab.com/2019/04/28/rethinking-software-testing-perspectives-from-the-world-of-hardware/ (Stand 24.01.2021)

[2] https://en.wikipedia.org/wiki/Test-driven_development (Stand 24.01.2021)

[3] https://en.wikipedia.org/wiki/Unit_test (Stand 24.01.2021)

[4] https://en.wikipedia.org/wiki/Integration_testing (Stand 24.01.2021)

[5] https://en.wikipedia.org/wiki/Regression_testing (Stand 24.01.2021)