Obsah
1. Univerzální testovací nástroj Robot Framework a BDD testy
3. Atributy objektů použitých pro implementaci kroků testů
4. Platnost objektů vytvářených nástrojem Robot Framework
5. Uchování hodnoty akumulátoru mezi jednotlivými testy
6. Použití deklarace ROBOT_LIBRARY_SCOPE
7. Globální oblast platnosti objektu
8. Podpora operací typu setup a teardown
9. Explicitní zákaz operace typu setup nebo teardown
11. Celý testovací scénář založený na tabulce
12. Alternativní zápis testovacího scénáře
14. Využití Robot Frameworku ve funkci nadmnožiny jazyka Gherkin
15. Praktický příklad: test třídy Accumulator6
18. Vylepšení předchozího příkladu
19. Repositář s demonstračními příklady
1. Univerzální testovací nástroj Robot Framework a BDD testy
Na úvodní článek, v němž jsme se seznámili s některými základními koncepty, na nichž je postaven nástroj Robot Framework, dnes navážeme. Představíme si některé další možnosti, které je možné využít při psaní testovacích scénářů, zejména tvorbu testů založených na tabulkách se vstupními daty a očekávanými výsledky. Zmíníme se mj. i o použití Robot Frameworku při psaní BDD testů, pro něž se běžně používá spíše doménově specifický jazyk (DSL – Domain Specific Language) nazvaný Gherkin. Implementaci jednotlivých kroků testů postavíme opět na programovacím jazyku Python, protože se jedná o primární jazyk, pro nějž je Robot Framework určen (druhým podporovaným jazykem je Java, ovšem Python se v této oblasti s velkou pravděpodobností používá mnohem častěji).

Obrázek 1: Příklad vygenerované HTML stránky s výsledky testů vytvořených a spuštěných v Robot Frameworku.
2. Zápis testovacích scénářů
Připomeňme si ve stručnosti, jak vypadal poslední (plně funkční) test, s nímž jsme se seznámili v samotném závěru předchozího článku. Jednalo se o velmi jednoduchý test, který zjišťoval, zda se provádí korektně aritmetická (celočíselná) operace součtu v programovacím jazyku Python. Jedná se tedy o pouhý demonstrační příklad zvolený z toho důvodu, že je dostatečně jednoduchý a přehledný (v praxi by se spíše testovala minimálně celá třída). Jednotlivé kroky testů, z nichž se skládá testovací scénář, jsou zapsány s využitím takzvaných klíčových slov (keywords) a jejich parametrů, přičemž klíčová slova i jednotlivé parametry od sebe musí být odděleny minimálně dvěma mezerami nebo znakem Tab (protože samotné klíčové slovo je obecně tvořeno několika slovy oddělenými od sebe jedinou mezerou, takže je nutné rozlišit hranici slova od běžných mezer).
Samotný testovací scénář prezentovaný minule byl zapsán následujícím způsobem:
*** Settings *** Library Test16.py *** Test Cases *** Adder #1 Add 1 2 Result should be 3 Adder #2 Add 0 0 Result should be 0 Adder #3 Add 1 -1 Result should be 0
S výsledkem po spuštění celého scénáře příkazem robot:
$ robot test16.robot ============================================================================== Test16 ============================================================================== Adder #1 | PASS | ------------------------------------------------------------------------------ Adder #2 | PASS | ------------------------------------------------------------------------------ Adder #3 | PASS | ------------------------------------------------------------------------------ Test16 | PASS | 3 critical tests, 3 passed, 0 failed 3 tests total, 3 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_01/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_01/log.html Report: /home/tester/src/Python/robot-framework-examples/article_01/report.html
Alternativně je možné v případě potřeby použít odlišný způsob zápisu, v němž se explicitně oddělují jednotlivé sloupce tabulky pomocí znaku „|“. Právě na tomto zápisu je velmi dobře patrné oddělení klíčových slov od jejich parametrů i rozdělení jednotlivých parametrů do samostatných sloupců:
| *** Settings *** | | | | Library | Test16.py | | | | | | | *** Test Cases *** | | | | Adder #1 | | | | | Add | 1 | 2 | | Result should be | 3 | | | | | | Adder #2 | | | | | Add | 0 | 0 | | Result should be | 0 | | | | | | Adder #3 | | | | | Add | 1 | -1 | | Result should be | 0 |
Ve skutečnosti je však možné tabulku dále rozdělit na menší části oddělené jedním či větším množstvím prázdných řádků, což může zvýšit čitelnost:
| *** Settings *** | | | | Library | Test16.py | | | *** Test Cases *** | | | | Adder #1 | | | | | Add | 1 | 2 | | Result should be | 3 | | | Add | 2 | 3 | | Result should be | 5 | | | Add | 4 | 5 | | Result should be | 9 | | Adder #2 | | | | | Add | 0 | 0 | | Result should be | 0 | | Adder #3 | | | | | Add | 1 | -1 | | Result should be | 0 |
3. Atributy objektů použitých pro implementaci kroků testů
Jednotlivé kroky testů, které jsou v testovacím scénáři reprezentovány klíčovými slovy „Add“ a „Result should be“, byly implementovány ve formě běžných metod třídy Test16. Jméno této třídy přitom musí odpovídat jménu souboru, v němž je zdrojový kód třídy uložen (což do značné míry odpovídá konvencím programovacího jazyka Java v případě veřejných tříd). Pokud by tomu tak nebylo, je nutné změnit parametr Library takovým způsobem, aby obsahoval jak jméno souboru, tak i jméno třídy. Samotná implementace této třídy může vypadat následovně:
class Test16: def __init__(self): print("INIT") def add(self, x, y): self.result = int(x) + int(y) def result_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Tento test si připomínáme z jednoho důvodu – můžeme si na jeho příkladu vysvětlit, jakým způsobem se pracuje s atributy objektů použitých pro implementaci jednotlivých kroků testů. Náš test pracuje s jediným atributem nazvaným result, který reprezentuje stav objektu typu Test16. Tento atribut je nastavován v metodě add a tedy (z pohledu testera) v klíčovém slově „Add“; přistupujeme k němu v metodě result_should_be, tedy na straně testů v klíčovém slově „Result should be“. Bude nám tedy dostačovat, aby byl atribut platný mezi zavoláním těchto dvou metod v rámci jednoho test case.
4. Platnost objektů vytvářených nástrojem Robot Framework
Ve výchozím nastavení jsou objekty (v předchozím příkladu instance třídy Test16) vytvořeny pro každý test case. Mezi jednotlivými test casy tedy není možné sdílet stav, pokud se samozřejmě neuchýlíme k použití globálních proměnných atd. Toto chování je většinou plně vyhovující a umožňuje nám psát například následující testovací scénáře, které počítají s tím, že výchozí hodnota akumulátoru bude na začátku každého test case vynulována:
| *** Settings *** | | | | Library | Accumulator1.py | | | | | | | *** Test Cases *** | | | | Initial value | | | | | Accumulator value should be | 0 | | | | | | Adder #1 | Add value | 1 | | | Accumulator value should be | 1 | | | Add value | 2 | | | Accumulator value should be | 3 |
Implementace třídy Accumulator využívá toho, že pro každý test case je vytvořen nový objekt a je tudíž možné hodnotu akumulátoru inicializovat přímo v konstruktoru. Deklarace obou metod realizujících klíčová slova je již triviální:
class Accumulator1: def __init__(self): print("INIT") self.result = 0 def add_value(self, value): self.result += int(value) def accumulator_value_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Tento testovací scénář se dvěma testy je plně funkční, o čemž se můžeme snadno přesvědčit:
$ robot test01.robot ============================================================================== Test01 ============================================================================== Initial value | PASS | ------------------------------------------------------------------------------ Adder #1 | PASS | ------------------------------------------------------------------------------ Test01 | PASS | 2 critical tests, 2 passed, 0 failed 2 tests total, 2 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
5. Uchování hodnoty akumulátoru mezi jednotlivými testy
V některých případech – i když jich nebude v praxi velmi mnoho – by bylo vhodné, aby se hodnota akumulátoru zapamatovala a zůstala zachována i mezi jednotlivými testy. Umožnilo by nám to zapsat například následující testovací scénář, který k akumulátoru postupně přičítá další hodnoty a přitom sleduje mezivýsledky:
| *** Settings *** | | | | Library | Accumulator2.py | | | | | | | *** Test Cases *** | | | | Initial value | | | | | Accumulator value should be | 0 | | | | | | Adder #1 | Add value | 1 | | | Accumulator value should be | 1 | | | | | | Adder #2 | Add value | 2 | | | Accumulator value should be | 3 |
V případě, že nezměníme implementaci třídy Accumulator2, nebude takto zapsaný scénář proveden korektně:
class Accumulator2: def __init__(self): print("INIT") self.result = 0 def add_value(self, value): self.result += int(value) def accumulator_value_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Samozřejmě si opět můžeme odzkoušet, jak se bude testovací scénář chovat po jeho spuštění v Robot Frameworku:
$ robot test02.robot ============================================================================== Test02 ============================================================================== Initial value | PASS | ------------------------------------------------------------------------------ Adder #1 | PASS | ------------------------------------------------------------------------------ Adder #2 | FAIL | 2 != 3 ------------------------------------------------------------------------------ Test02 | FAIL | 3 critical tests, 2 passed, 1 failed 3 tests total, 2 passed, 1 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
Toto chování je očekávatelné, protože pro každý test case byla vytvořena nová instance třídy Accumulator2 a tím pádem došlo i k inicializaci výchozí hodnoty akumulátoru.
6. Použití deklarace ROBOT_LIBRARY_SCOPE
Existuje však možnost, jak zajistit, aby se hodnota akumulátoru zachovala i mezi jednotlivými test casy. Můžeme totiž určit, že nějaký zvolený objekt (v našem případě instance třídy Accumulator3 bude zkonstruován jen jedenkrát a bude platný pro celý testovací scénář. Stačí k tomu maličkost – deklarovat třídní atribut nazvaný ROBOT_LIBRARY_SCOPE a nastavit ho na řetězec „TEST SUITE“. Povšimněte si, že není nutné provádět žádné importy dalších knihoven, což by bylo zapotřebí například v případě, že by Robot Framework používal anotace atd.:
class Accumulator3: ROBOT_LIBRARY_SCOPE = 'TEST SUITE' def __init__(self): print("INIT") self.result = 0 def add_value(self, value): self.result += int(value) def accumulator_value_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Samotný zápis testovacího scénáře se prakticky nezmění, pochopitelně až na nutnost specifikace jiné knihovny s implementací klíčových slov:
| *** Settings *** | | | | Library | Accumulator3.py | | | | | | | *** Test Cases *** | | | | Initial value | | | | | Accumulator value should be | 0 | | | | | | Adder #1 | Add value | 1 | | | Accumulator value should be | 1 | | | | | | Adder #2 | Add value | 2 | | | Accumulator value should be | 3 |
Nyní ovšem celý testovací scénář proběhne v pořádku, což si ověříme na dalším výpisu:
$ robot test03.robot ============================================================================== Test03 ============================================================================== Initial value | PASS | ------------------------------------------------------------------------------ Adder #1 | PASS | ------------------------------------------------------------------------------ Adder #2 | PASS | ------------------------------------------------------------------------------ Test03 | PASS | 3 critical tests, 3 passed, 0 failed 3 tests total, 3 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
7. Globální oblast platnosti objektu
Existuje ještě jedna možnost specifikace platnosti objektu (tedy instance nějaké třídy vytvořené samotným Robot Frameworkem). Můžeme totiž nastavit, že objekt bude globálně platný a tedy bude existovat napříč všemi testovacími scénáři, které budou spuštěny. Postačuje definovat třídní atribut ROBOT_LIBRARY_SCOPE a nastavit ho na řetězec „GLOBAL“ tak, jak je to ukázáno v dnešním čtvrtém demonstračním příkladu:
class Accumulator4: ROBOT_LIBRARY_SCOPE = 'GLOBAL' def __init__(self): print("INIT") self.result = 0 def add_value(self, value): self.result += int(value) def accumulator_value_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Chování si můžeme ověřit na tomto testovacím scénáři:
| *** Settings *** | | | | Library | Accumulator4.py | | | | | | | *** Test Cases *** | | | | Initial value | | | | | Accumulator value should be | 0 | | | | | | Adder #1 | Add value | 1 | | | Accumulator value should be | 1 | | | | | | Adder #2 | Add value | 2 | | | Accumulator value should be | 3 |
S očekávaným výsledkem:
$ robot test04.robot ============================================================================== Test04 ============================================================================== Initial value | PASS | ------------------------------------------------------------------------------ Adder #1 | PASS | ------------------------------------------------------------------------------ Adder #2 | PASS | ------------------------------------------------------------------------------ Test04 | PASS | 3 critical tests, 3 passed, 0 failed 3 tests total, 3 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
8. Podpora operací typu setup a teardown
V prakticky všech knihovnách a frameworcích určených pro tvorbu a spouštění různých typů testů se setkáme s možností specifikace operací, které se mají provést před každým testem, popř. naopak po doběhnutí testu. Většinou jsou tyto operace definovány ve funkcích nebo metodách nazvaných setup a teardown. V Robot Frameworku tuto možnost máme taky, přičemž se operace typu setup a teardown definují v sekci Settings, tedy na začátku testovacího scénáře. Tyto operace lze implementovat libovolným slovem (keyword), přičemž tato slova mohou mít i parametry, pokud je to vyžadováno.
V dalším demonstračním příkladu pro operaci setup použijeme klíčové slovo Setup method s parametrem 0 a pro operaci teardown použijeme klíčové slovo Teardown method, tentokrát bez parametrů:
| *** Settings *** | | | | Library | Accumulator5.py | | | Test setup | Setup method | 0 | | Test teardown | Teardown method | | | | | | | *** Test Cases *** | | | | Initial value | | | | | Accumulator value should be | 0 | | | | | | Adder #1 | Add value | 1 | | | Accumulator value should be | 1 | | | | | | Adder #2 | Add value | 2 | | | Accumulator value should be | 3 |
Implementace obou výše zmíněných klíčových slov se ve skutečnosti žádným způsobem neliší od implementací slov jiných – bude se jednat o běžné metody třídy Accumulator5:
class Accumulator5: ROBOT_LIBRARY_SCOPE = 'GLOBAL' def __init__(self): print("INIT") self.result = None def setup_method(self, value): self.result = int(value) def teardown_method(self): pass def add_value(self, value): self.result += int(value) def accumulator_value_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Na začátku každého testu (přesněji test casu) se inicializuje atribut result na nulovou hodnotu, takže je zřejmé, že tentokrát budou později spuštěné testy havarovat, a to i přesto, že je hodnota tohoto atributu zachována i mezi jednotlivými testy (ve skutečnosti je sice hodnota zachována, ale na začátku testu je atribut explicitně vynulován):
$ robot test05.robot ============================================================================== Test05 ============================================================================== Initial value | PASS | ------------------------------------------------------------------------------ Adder #1 | PASS | ------------------------------------------------------------------------------ Adder #2 | FAIL | 2 != 3 ------------------------------------------------------------------------------ Test05 | FAIL | 3 critical tests, 2 passed, 1 failed 3 tests total, 2 passed, 1 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
9. Explicitní zákaz operace typu setup nebo teardown
V některých situacích budeme potřebovat, aby se operace typu setup nebo teardown vůbec neprovedly, nebo aby se sice provedly, ale s jinými parametry. I tohoto chování je možné v Robot Frameworku dosáhnout, a to tímto zápisem:
| Adder #2 | [Setup] | NONE | | | Add value | 2 | | | Accumulator value should be | 3 |
Výše uvedený zvýrazněný řádek zakazoval pro jeden konkrétní test (Adder #2) provedení operace typu setup. Tímto způsobem můžeme mnohdy i dosti zásadním způsobem ovlivnit spouštění jednotlivých testů, popř. modifikovat jejich kontext (tedy prostředí, v jakém se testy spouští):
| *** Settings *** | | | | Library | Accumulator6.py | | | Test setup | Setup method | 0 | | Test teardown | Teardown method | | | | | | | *** Test Cases *** | | | | Initial value | | | | | Accumulator value should be | 0 | | | | | | Adder #1 | Add value | 1 | | | Accumulator value should be | 1 | | | | | | Adder #2 | [Setup] | NONE | | | Add value | 2 | | | Accumulator value should be | 3 | | | | | | Adder #3 | | | | | Add value | 2 | | | Accumulator value should be | 5 |
Implementace knihovny Accumulator6 může zůstat stejná, jako tomu bylo v knihovně Accumulator5, tedy:
class Accumulator6: ROBOT_LIBRARY_SCOPE = 'GLOBAL' def __init__(self): print("INIT") self.result = None def setup_method(self, value): self.result = int(value) def teardown_method(self): pass def add_value(self, value): self.result += int(value) def accumulator_value_should_be(self, expected): assert self.result == int(expected), "{} != {}".format(self.result, expected)
Nyní by první tři testy měly proběhnout korektně; zhavarovat by měl až test čtvrtý:
$ robot test06.robot ============================================================================== Test06 ============================================================================== Initial value | PASS | ------------------------------------------------------------------------------ Adder #1 | PASS | ------------------------------------------------------------------------------ Adder #2 | PASS | ------------------------------------------------------------------------------ Adder #3 | FAIL | 2 != 5 ------------------------------------------------------------------------------ Test06 | FAIL | 4 critical tests, 3 passed, 1 failed 4 tests total, 3 passed, 1 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
10. Testy řízené tabulkami
Jedna z předností nástroje Robot Framework spočívá v tom, že umožňuje tvořit testy řízené tabulkami. Jedná se o možnost zapsat nějaké vstupní hodnoty (parametry) a očekávané výsledky pro tyto vstupní hodnoty formou nějaké tabulky. V našem konkrétním případě, pokud budeme chtít testovat implementaci třídy s akumulátorem, je možné použít například tento zápis:
| *** Test Cases *** | Value | Expected | | Test1 | 0 | 0 | | | 1 | 1 | | | 10 | 11 | | | -10 | 1 | | | 1 | 2 | | | 1 | 3 |
Ve sloupci Value je uložena hodnota přičítaná do akumulátoru, ve sloupci Expected pak očekávaná (výsledná) hodnota uložená v akumulátoru.
To ovšem pochopitelně není vše, protože je nutné Robot Frameworku oznámit, jakým způsobem se mají hodnoty zapsané do tabulky namapovat na konkrétní klíčová slova neboli kroky testu. To můžeme provést v sekci Keywords (i když existují i další možnosti), a to následujícím způsobem, který zaručí, že se každý řádek předchozí tabulky s testem přepíše na dvojici klíčových slov Add value a Accumulator value should be:
| *** Keywords *** | | | | | Accumulate | | | | | | [Arguments] | ${value} | ${expected} | | | Add value | ${value} | | | | Accumulator value should be | ${expected} | |
11. Celý testovací scénář založený na tabulce
Ukažme si nyní, jak by mohl vypadat test třídy Accumulator (přesněji řečeno její šesté iterace Accumulator6) přepsaný do formátu, v němž se bude využívat tabulka se vstupními parametry a očekávanými výsledky testů. Tabulka bude mít dva sloupce nazvané Value a Expected, přičemž každý řádek této tabulky bude expandován do dvou kroků testu způsobem, který jsme si popsali v předchozí kapitole:
| *** Settings *** | | | | | Library | Accumulator6.py | | | | Test template | Accumulate | | | | Test setup | Setup method | 0 | | | Test teardown | Teardown method | | | | | | | | | *** Test Cases *** | Value | Expected | | | Test1 | 0 | 0 | | | | 1 | 1 | | | | 10 | 11 | | | | -10 | 1 | | | | 1 | 2 | | | | 1 | 3 | | | | | | | | *** Keywords *** | | | | | Accumulate | | | | | | [Arguments] | ${value} | ${expected} | | | Add value | ${value} | | | | Accumulator value should be | ${expected} | |
Po spuštění tohoto testu by měly všechny kroky testovacího scénáře proběhnout korektně (bez pádu):
$ robot test07.robot ============================================================================== Test07 ============================================================================== Test1 | PASS | ------------------------------------------------------------------------------ Test07 | PASS | 1 critical test, 1 passed, 0 failed 1 test total, 1 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
| *** Settings *** | | | | Library | Accumulator6.py | | | Test template | Accumulate | | | Test setup | Setup method | 0 | | Test teardown | Teardown method | | | *** Test Cases *** | Value | Expected | | Test1 | 0 | 0 | | | 1 | 1 | | | 10 | 11 | | | -10 | 1 | | | 1 | 2 | | | 1 | 3 | | *** Keywords *** | | | | | Accumulate | | | | | | [Arguments] | ${value} | ${expected} | | | Add value | ${value} | | | | Accumulator value should be | ${expected} | |
12. Alternativní zápis testovacího scénáře
Předchozí testovací scénář můžeme zapsat i nepatrně odlišným způsobem. Nemusí se totiž jednat o tabulku se sloupci oddělenými znakem „|“, ale o použití mezer (či sady mezer) pro oddělení jednotlivých sloupců dvou tabulek (Test Cases a Keywords):
*** Settings *** Library Accumulator6.py Test template Accumulate Test setup Setup method 0 Test teardown Teardown method *** Test Cases *** Value Expected Test1 0 0 1 1 10 11 -10 1 1 2 1 3 *** Keywords *** Accumulate [Arguments] ${value} ${expected} Add value ${value} Accumulator value should be ${expected}
I přes odlišný způsob zápisu testovacího scénáře bude výsledek totožný s předchozím testem:
============================================================================== Test08 ============================================================================== Test1 | PASS | ------------------------------------------------------------------------------ Test08 | PASS | 1 critical test, 1 passed, 0 failed 1 test total, 1 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
13. Jazyk Gkerkin
Dalším tématem, kterým se dnes budeme zabývat, je tvorba BDD (behavior) testů, neboli testů zjišťujících, zda se testovaný systém chová podle popsaných předpokladů. Tyto testy lze vytvářet různým způsobem, ovšem v této oblasti se velmi často setkáme s použitím doménově specifického jazyka nazvaného Gherkin. Tento jazyk odstiňuje autora testů od vlastní implementace systému i od programovacího jazyka (či jazyků), v nichž je systém vytvořen. Ostatně v Gherkinu lze popsat očekávané chování prakticky jakéhokoli systému, který dokonce nemusí mít nic společného s IT. Testovací scénář vytvořený v Gherkinu může vypadat následovně:

Obrázek 2: Ukázka scénářů napsaných v jazyce Gherkin.
Na předchozím screenshotu jsou zvýrazněna klíčová slova uvozující jednotlivé kroky testu. Ostatní slova a číslice ve větách jsou buď pevně daná (svázaná s konkrétním krokem), nebo se jedná o proměnné. Ve scénáři je i tabulka, jejíž obsah se řádek po řádku postupně stává obsahem jednotlivých kroků testu (obsahem tabulky se nahrazují slova umístěná do ostrých závorek).
Jednotlivé kroky testu napsané v jazyce Gherkin je samozřejmě nutné nějakým způsobem implementovat. Způsob, jakým lze implementaci zařídit v nástroji Robot Framework, bude popsán v navazujících kapitolách.
S jazykem Gherkin a se způsobem jeho použití jsme se již na stránkách Rootu několikrát setkali, protože jsme si ukázali implementaci Gherkinu jak pro programovací jazyk Clojure, tak i pro Python a dokonce i pro jazyk Go. Podrobnější informace o těchto implementacích naleznete v následujících článcích:
- Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/ - Behavior-driven development v Pythonu s využitím knihovny Behave
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave/ - Behavior-driven development v Pythonu s využitím knihovny Behave (druhá část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-druha-cast/ - Behavior-driven development v Pythonu s využitím knihovny Behave (závěrečná část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-zaverecna-cast/ - Tvorba BDD testů s využitím jazyka Go a nástroje godog
https://www.root.cz/clanky/tvorba-bdd-testu-s-vyuzitim-jazyka-go-a-nastroje-godog/
14. Využití Robot Frameworku ve funkci nadmnožiny jazyka Gherkin
Testovací scénář pro třídu Accumulator6 lze přepsat do podoby BDD testů, a to jazykem, který připomíná Gherkin. Ústřední část bude vypadat následovně:
*** Test Cases *** Accumulate Given accumulator has been zeroed When I add "1" to accumulator Then the accumulated value should be "1" When I add "1" to accumulator Then the accumulated value should be "2" When I add "-10" to accumulator Then the accumulated value should be "-8"
Ovšem podobně jako u předchozích testů řízených tabulkami je i zde nutné specifikovat klíčová slova za „Given“, „When“ a „Then“, a to konkrétně v sekci Keywords:
*** Keywords *** Accumulator has been zeroed log accumulator init I add "${value}" to accumulator Add value ${value} Then the accumulated value should be "${expected}" Accumulator value should be ${expected}
Tímto způsobem vytvořenou kostru testů již můžeme použít v reálném testovacím scénáři.
15. Praktický příklad: test třídy Accumulator6
Předchozí testy třídy s implementací jednoduchého akumulátoru je možné přepsat do formy BDD připomínající (ve vlastní části s popisem testů) jazyk Gherkin. Výsledek, a to plně funkční, může vypadat například takto:
*** Settings *** Library Accumulator6.py Test setup Setup method 0 Test teardown Teardown method *** Test Cases *** Accumulate Given accumulator has been zeroed When I add "1" to accumulator Then the accumulated value should be "1" When I add "1" to accumulator Then the accumulated value should be "2" When I add "-10" to accumulator Then the accumulated value should be "-8" *** Keywords *** Accumulator has been zeroed log accumulator init I add "${value}" to accumulator Add value ${value} Then the accumulated value should be "${expected}" Accumulator value should be ${expected}
Tento testovací scénář je plně funkční:
============================================================================== Test09 ============================================================================== Accumulate | PASS | ------------------------------------------------------------------------------ Test09 | PASS | 1 critical test, 1 passed, 0 failed 1 test total, 1 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html

Obrázek 3: Výsledky běhu BDD testů.

Obrázek 4: Ve výpisu jednotlivých kroků vidíme, jak se věty z BDD testů namapovaly na konkrétní klíčová slova.
16. Použití spojky and
V BDD testech je dobrým zvykem nahrazovat sekvenci vět začínajících stejným slovem „When“ či „Then“:
When I add "1" to accumulator When I add "1" to accumulator When I add "-10" to accumulator Then the accumulated value should be "-8"
spojkou And, která je čitelnější:
When I add "1" to accumulator And I add "1" to accumulator And I add "-10" to accumulator Then the accumulated value should be "-8"
I tato vlastnost je v Robot Frameworku podporována, takže můžeme psát:
*** Settings *** Library Accumulator6.py Test setup Setup method 0 Test teardown Teardown method *** Test Cases *** Accumulate Given accumulator has been zeroed When I add "1" to accumulator And I add "1" to accumulator And I add "-10" to accumulator Then the accumulated value should be "-8" *** Keywords *** Accumulator has been zeroed log accumulator init I add "${value}" to accumulator Add value ${value} Then the accumulated value should be "${expected}" Accumulator value should be ${expected}
Výsledek takto upraveného testovacího scénáře:
============================================================================== Test10 ============================================================================== Accumulate | PASS | ------------------------------------------------------------------------------ Test10 | PASS | 1 critical test, 1 passed, 0 failed 1 test total, 1 passed, 0 failed ============================================================================== Output: /home/tester/src/Python/robot-framework-examples/article_02/output.xml Log: /home/tester/src/Python/robot-framework-examples/article_02/log.html Report: /home/tester/src/Python/robot-framework-examples/article_02/report.html
17. BDD testy řízené tabulkou
I BDD testy je možné řídit tabulkami. Zde se ovšem setkáme s určitým zkomplikováním testů, protože je nutné použít dvojího mapování klíčových slov. Nejdříve se z tabulky vytvoří jednotlivé kroky testu, které jsou ovšem popsány v jazyku Gherkin. A následně se věty z Gherkinu mapují na nová uživatelsky definovaná klíčová slova. Výsledek může vypadat následovně:
*** Settings *** Library Accumulator6.py Test setup Setup method 0 Test teardown Teardown method Test template Accumulator operation *** Test Cases *** Value Expected Accumulator operation 1 1 Accumulator operation 2 2 *** Keywords *** Accumulator operation [Arguments] ${value} ${expected} Given accumulator has been zeroed When I add ${value} to accumulator Then the accumulated value should be ${expected} Accumulator has been zeroed log accumulator init I add ${value} to accumulator Add value ${value} Then the accumulated value should be ${expected} Accumulator value should be ${expected}
18. Vylepšení předchozího příkladu
Předchozí příklad lze vylepšit a učinit více čitelným. Nejdříve zapíšeme vlastní testovací scénář, tj. BDD část s tabulkou, a posléze tu méně důležitou část, tedy mapování vět v BDD/Gherkinu na konkrétní klíčová slova:
*** Settings *** Library Accumulator6.py Test setup Setup method 0 Test teardown Teardown method Test template Accumulator operation *** Keywords *** Accumulator operation [Arguments] ${value} ${expected} Given accumulator has been zeroed When I add ${value} to accumulator Then the accumulated value Should Be ${expected} *** Test Cases *** Value Expected Accumulator operation 1 1 Accumulator operation 10 10 *** Keywords *** Accumulator has been zeroed log accumulator init I add ${value} to accumulator Add value ${value} Then the accumulated value should be ${expected} Accumulator value should be ${expected}
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/robot-framework-examples (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Stránka projektu Robot Framework
https://robotframework.org/ - GitHub repositář Robot Frameworku
https://github.com/robotframework/robotframework - Robot Framework (Wikipedia)
https://en.wikipedia.org/wiki/Robot_Framework - Tutoriál Robot Frameworku
http://www.robotframeworktutorial.com/ - Robot Framework Documentation
https://robotframework.org/robotframework/ - Robot Framework Introduction
https://blog.testproject.io/2016/11/22/robot-framework-introduction/ - robotframework 3.1.2 na PyPi
https://pypi.org/project/robotframework/ - Robot Framework demo (GitHub)
https://github.com/robotframework/RobotDemo - Robot Framework web testing demo using SeleniumLibrary
https://github.com/robotframework/WebDemo - Robot Framework for Mobile Test Automation Demo
https://www.youtube.com/watch?v=06LsU08slP8 - Gherkin
https://cucumber.io/docs/gherkin/ - Selenium
https://selenium.dev/ - SeleniumLibrary
https://robotframework.org/ - The Practical Test Pyramid
https://martinfowler.com/articles/practical-test-pyramid.html - Acceptance Tests and the Testing Pyramid
http://www.blog.acceptancetestdrivendevelopment.com/acceptance-tests-and-the-testing-pyramid/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
https://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
https://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/ - Tab-separated values
https://en.wikipedia.org/wiki/Tab-separated_values - A quick guide about Python implementations
https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321 - Python 2.7 will retire in…
https://pythonclock.org/