Hlavní navigace

Univerzální testovací nástroj Robot Framework a BDD testy

Ve druhém článku o Robot Framework si ukážeme další možnosti, které lze využít při psaní testovacích scénářů. Zmíníme se mj. i o použití Robot Frameworku při tvorbě BDD testů, pro něž se běžně používá jazyk Gherkin.
Pavel Tišnovský
Doba čtení: 23 minut

Sdílet

Obsah

1. Univerzální testovací nástroj Robot Framework a BDD testy

2. Zápis testovacích scénářů

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

10. Testy řízené tabulkami

11. Celý testovací scénář založený na tabulce

12. Alternativní zápis testovacího scénáře

13. Jazyk Gherkin

14. Využití Robot Frameworku ve funkci nadmnožiny jazyka Gherkin

15. Praktický příklad: test třídy Accumulator6

16. Použití spojky and

17. BDD testy řízené tabulkou

18. Vylepšení předchozího příkladu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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)
Poznámka: povšimněte si, že klíčová slova jsou realizována „běžnými“ metodami, tedy nikoli metodami statickými či třídními. Jejich prvním parametrem je tedy self, jenž je předáván automaticky (nepíše se do testovacího scénáře).

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
Poznámka: chybové hlášení znamená, že po přičtení dvojky k obsahu akumulátoru (ten je nulový) bude jeho novou hodnotou pochopitelně taktéž dvojka a nikoli hodnota 3.

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} |             |
Poznámka: zápisem ${value} a ${expected} se specifikují proměnné spravované přímo Robot Frameworkem.

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
Poznámka: opět platí, že testovací scénář lze rozdělit na několik tabulek a použít odlišný, i když sémanticky ekvivalentní zápis:
| *** 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).

Poznámka: jazyk Gherkin existuje v různých jazykových mutacích, my se však budeme držet jeho originální anglické varianty. Ostatně mnoho knihoven, které Gherkin podporují, pracuje pouze s anglickou variantou.

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:

  1. 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/
  2. 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/
  3. 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/
  4. 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/
  5. 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/
  6. 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.

Poznámka: povšimněte si, že v implementaci klíčových slov se nepoužívají první slova z BDD testů, tedy „Given“, „When“ a „Then“.

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}
Poznámka: na posledních dvou příkladech je patrná velká flexibilita Robot Frameworku.

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:

# Příklad Stručný popis Cesta
1 test01.robot první test akumulátoru implementovaného ve třídě Accumulator1 https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test01.robot
2 Accumulator1.py první implementace akumulátoru https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/Accumulator1.py
3 test02.robot několik na sobě nezávislých testů https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test02.robot
4 Accumulator2.py druhá implementace akumulátoru https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/Accumulator2.py
5 test03.robot několik navzájem závislých testů https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test03.robot
6 Accumulator3.py změna rozsahu platnosti objektu s akumulátorem https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/Accumulator3.py
7 test04.robot několik navzájem závislých testů https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test04.robot
8 Accumulator4.py změna rozsahu platnosti objektu s akumulátorem https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/Accumulator4.py
9 test05.robot použití setup a teardown https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test05.robot
10 Accumulator5.py implementace metod setup a teardown https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/Accumulator5.py
11 test06.robot zákaz setup ve zvoleném testu https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test06.robot
12 Accumulator6.py implementace metod setup a teardown https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/Accumulator6.py
13 test07.robot testy řízené tabulkami https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test07.robot
14 test08.robot testy řízené tabulkami https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test08.robot
15 test09.robot jednoduchý BDD test https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test09.robot
16 test10.robot jednoduchý BDD test se spojkou „and“ https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test10.robot
17 test11.robot BDD testy řízené tabulkami https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test11.robot
18 test12.robot vylepšení předchozího příkladu https://github.com/tisnik/robot-framework-examples/blob/master/arti­cle02/test12.robot

20. Odkazy na Internetu

  1. Stránka projektu Robot Framework
    https://robotframework.org/
  2. GitHub repositář Robot Frameworku
    https://github.com/robotfra­mework/robotframework
  3. Robot Framework (Wikipedia)
    https://en.wikipedia.org/wi­ki/Robot_Framework
  4. Tutoriál Robot Frameworku
    http://www.robotframeworktu­torial.com/
  5. Robot Framework Documentation
    https://robotframework.or­g/robotframework/
  6. Robot Framework Introduction
    https://blog.testproject.i­o/2016/11/22/robot-framework-introduction/
  7. robotframework 3.1.2 na PyPi
    https://pypi.org/project/ro­botframework/
  8. Robot Framework demo (GitHub)
    https://github.com/robotfra­mework/RobotDemo
  9. Robot Framework web testing demo using SeleniumLibrary
    https://github.com/robotfra­mework/WebDemo
  10. Robot Framework for Mobile Test Automation Demo
    https://www.youtube.com/wat­ch?v=06LsU08slP8
  11. Gherkin
    https://cucumber.io/docs/gherkin/
  12. Selenium
    https://selenium.dev/
  13. SeleniumLibrary
    https://robotframework.org/
  14. The Practical Test Pyramid
    https://martinfowler.com/ar­ticles/practical-test-pyramid.html
  15. Acceptance Tests and the Testing Pyramid
    http://www.blog.acceptance­testdrivendevelopment.com/ac­ceptance-tests-and-the-testing-pyramid/
  16. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    https://www.root.cz/clanky/pro­gramovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  17. Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
    https://www.root.cz/clanky/pro­gramovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/
  18. 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/
  19. Tab-separated values
    https://en.wikipedia.org/wiki/Tab-separated_values
  20. A quick guide about Python implementations
    https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321
  21. Python 2.7 will retire in…
    https://pythonclock.org/