Obsah
1. Tvorba BDD testů s využitím nástroje godog
2. BDD testy a programovací jazyk Go
4. Testovaný modul s implementací jednoduchého akumulátoru
5. Vytvoření testovacího scénáře
6. První spuštění nástroje godog
7. Implementace jednotlivých kroků testu a spuštění testovacího scénáře
8. Proměnná s kontextem celého scénáře
9. Alternativní způsob inicializace akumulátoru
10. Úprava testů pro možnost použití záporných hodnot
11. Tabulky v BDD testech, aneb zápis osnovy testovacího scénáře
12. Inicializace akumulátoru jedenkrát pro celý test
13. Úprava aplikace takovým způsobem, aby bylo možné spustit BDD i jednotkové testy jediným příkazem
14. Spuštění BDD testů společně s jednotkovými testy
15. Podporované formáty s výsledky BDD testů
17. Formáty JSON: Cucumber a JSON event stream
18. Obsah následující části seriálu
19. Repositář s demonstračními příklady
1. Tvorba BDD testů s využitím nástroje godog
Již několikrát jsme se v seriálu o programovacím jazyce Go zabývali důležitou oblastí – testování vytvářených aplikací. Popsali jsme si především možnosti standardní knihovny testing, která tvoří základ pro tvorbu jednotkových testů a v případě nouze ji lze využít i pro psaní testů funkcionálních (i když zde poměrně rychle narazíme na limity této velmi minimalisticky pojaté knihovny). Příklad jednotkového testu:
type AddTest struct { x int32 y int32 expected int32 } func TestAdd(t *testing.T) { var addTestInput = []AddTest{ {0, 0, 0}, {1, 0, 1}, {2, 0, 2}, {2, 1, 3}, {2, -2, 0}, {math.MaxInt32, 0, math.MaxInt32}, {math.MaxInt32, 1, math.MinInt32}, {math.MaxInt32, math.MinInt32, -1}, } for _, i := range addTestInput { result := add(i.x, i.y) if result != i.expected { msg := fmt.Sprintf("%d + %d should be %d, got %d instead", i.x, i.y, i.expected, result) t.Error(msg) } } }
Taktéž již víme, že pro jazyk Go vzniklo i poměrně velké množství dalších knihoven, s jejichž využitím lze tvorbu testů zjednodušit a zpřehlednit. Mezi tyto knihovny patří zejména oglematchers, což je sada pomocných funkcí umožňujících explicitnější zápis podmínek pro jednotkové testy. Dobré vlastnosti knihovny oglematchers plně oceníme až ve chvíli, kdy je zkombinujeme s knihovnou ogletest.
Obrázek 1: Webové rozhraní aplikace GoConvey.
Třetí doplňkovou knihovnou určenou pro usnadnění psaní testů, s níž jsme se již setkali, je knihovna nazvaná assertions, jejíž repositář naleznete na adrese https://github.com/smartystreets/assertions. V této knihovně je deklarováno několik funkcí, které se používají podobným způsobem jako klasické aserce (které ve standardním jazyku Go vůbec nenalezneme). Zajímavé je, že ve chvíli, kdy je testovaná podmínka splněna, vrací tyto funkce prázdný řetězec (nikoli nil!), v opačném případě řetězec s popisem podmínky i důvodem, proč nebyla splněna – tyto informace tedy nebudeme muset zapisovat ručně.
Obrázek 2: Změna stylu webového rozhraní aplikace GoConvey.
Zapomenout nesmíme ani na užitečnou knihovnu GoConvey, která může posloužit pro vytváření BDD testů, ovšem jiným způsobem, než bude popsáno v tomto článku. A konečně jsme se již seznámili s projektem go-carpet, který primárně slouží k získání informace o tom, kterými větvemi programu se prošlo při testování a kterými naopak nikoli (což sice dokážeme zjistit i standardní knihovnou testing, ovšem výsledek není příliš přehledný).
Obrázek 3: Historie již spuštěných testů.
2. BDD testy a programovací jazyk Go
Dnes se budeme primárně věnovat frameworku s poněkud zvláštním názvem godog. Tento framework je určen pro psaní BDD (Behavior Driven Development) testů. Tyto testy, které se používají pro zjištění, zda se projekt/aplikace chová podle svého popisu, je možné vytvářet různými způsoby. Již víme, že je možné je zapisovat přímo ve formě zdrojového kódu jazyka Go. Jen pro připomenutí si ukažme, jak mohou tyto testy vypadat. Konkrétně použijeme možnosti nabízené výše zmíněnou knihovnou GoConvey:
package factorial import( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestFactorial(t *testing.T) { Convey("0! should be equal 1", t, func() { So(Factorial(0), ShouldEqual, 1) }) } func TestFactorial2(t *testing.T) { Convey("10! should be greater than 1", t, func() { So(Factorial(10), ShouldBeGreaterThan, 1) }) Convey("10! should be between 1 and 10000000", t, func() { So(Factorial(10), ShouldBeBetween, 1, 10000000) }) }
Obrázek 4: Výsledky v případě, že některé testy neproběhly korektně.
Vidíme, že se sice jedná o zdrojový kód velmi snadno čitelný pro programátora, který se pouze musí naučit význam jednotlivých volaných funkcí a metod (což je snadné), ovšem pro ostatní členy týmu se již může jednat o složitější a relativně nesnadno uchopitelný problém. Musíme si totiž uvědomit, že BDD testy většinou nepíšou pouze programátoři, ale měly by do nich zasahovat například i architekti ve spolupráci se zákazníky atd. Je tedy vhodné, aby byly testy co nejčitelnější a snadno upravitelné. A přesně pro tento účel byl vytvořen specializovaný jazyk nazvaný Gherkin. V případě Gherkinu se jedná o doménově specifický jazyk (DSL – domain specific language) navržený skutečně takovým způsobem, aby bylo možné předpokládané (očekávané) chování aplikace popsat tak jednoduše, že se přípravy popisu bude moci zúčastnit i zákazník-neprogramátor, popř. po krátkém zaučení prakticky jakýkoli člen vývojového týmu.
3. Jazyk Gherkin
Testovací scénář vytvořený v Gherkinu může vypadat následovně:
Obrázek 5: Ukázka scénářů napsaných v jazyce Gherkin.
Zvýrazněna jsou 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. A přesně pro tento účel použijeme výše zmíněný framework godog, který dokáže přečíst skript (přesněji řečeno testovací scénář) napsaný v Gherkinu a navrhnout na jeho základě strukturu implementace testů pro jazyk Go. Následně godog dokáže testy spustit a vyhodnotit jejich výsledky. Alternativně je možné BDD testy zahrnout do testů jednotkových a spouštět je jediným příkazem.
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. 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/
4. Testovaný modul s implementací jednoduchého akumulátoru
Víme již, že jazyk Gherkin je navržen takovým způsobem, aby ho uživatelé (nemusí se totiž nutně jednat pouze o programátory) mohli začít používat prakticky okamžitě, tj. bez nutnosti studia sáhodlouhých manuálů. I z toho důvodu si možnosti tohoto doménově specifického jazyka postupně ukážeme na několika demonstračních příkladech. První příklad bude velmi jednoduchý, protože bude obsahovat jediný balíček, který budeme chtít otestovat. I přesto se však bude jednat o plnohodnotný projekt, jehož struktura odpovídá struktuře projektů složitějších a sofistikovanějších. Adresář s projektem i s testovacím scénářem by měl vypadat následovně:
. ├── accumulator.go ├── accumulator_test.go └── features └── accumulator.feature
Balíček accumulator, který vlastně tvoří celou testovanou aplikaci, je velmi stručný, protože obsahuje jedinou metodu nazvanou acc, jež – jak ostatně její název naznačuje – slouží k připočtení nějaké hodnoty k akumulátoru. Samotný akumulátor je představován uživatelsky definovanou datovou strukturou, jejíž existence umožňuje, aby bylo možné vytvořit výše zmíněnou metodu accumulate:
package accumulator type acc struct { value int } func (a *acc) accumulate(x int) { a.value += x }
5. Vytvoření testovacího scénáře
Nyní se pokusíme napsat testovací scénář, který otestuje chování výše uvedené datové struktury acc i její metody accumulate. Povšimněte si, že skutečně můžeme nejdříve napsat testovací scénář a teprve poté se pokusit o implementaci jednotlivých kroků testovacího scénáře. Tento postup je jednodušší a z hlediska vývoje projektu i korektnější – ostatně BDD testy je možné začít psát již na samotném začátku vývoje, aniž by byla vyvinuta jediná řádka skutečného programového kódu. Tolik teorie, vraťme se nyní k testovacímu scénáři. Jeho první varianta může vypadat následovně:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario: Accumulate positive integer Given I have an accumulator with 0 When I add 2 to accumulator Then the accumulated result should be 2
Aby bylo možné testovací scénář spustit, musíme mít nainstalován nástroj godog, což je spustitelná nativní aplikace nainstalovaná základními prostředky programovacího jazyka Go. Pro instalaci tohoto nástroje použijte příkaz:
$ go get github.com/DATA-DOG/godog/cmd/godog
Po instalaci je godog popř. godog.exe umístěn v adresáři $GOPATH/bin, což je většinou adresář ~/go/bin:
$ $GOPATH/bin/godog --version Godog version is: v0.7.14
Cestu k tomuto adresáři je vhodné přidat do proměnné prostředí PATH aby bylo možné nástroj godog snadno a odkudkoli spouštět bez nutnosti specifikace cesty k němu:
$ export PATH=$PATH:$GOPATH/bin
Základní otestování instalace:
$ godog --version Godog version is: v0.7.14
6. První spuštění nástroje godog
Nástroj godog zjistí, že sice existuje testovací scénář, ovšem jednotlivé kroky popsané ve scénáři nejsou definovány. Z tohoto důvodu vypíše informace o tom, že sice má k dispozici scénář se třemi kroky, ovšem ani jeden z těchto kroků není implementován:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario: Add two positive integers # features/accumulator.feature:4 Given I have an accumulator with 0 When I add 2 to accumulator Then the accumulated result should be 5 1 scenarios (1 undefined) 3 steps (3 undefined) 49.638µs
Navíc se ovšem dozvíme mnohem užitečnější informaci – kostru jednotlivých kroků testu. Každý krok je představován funkcí akceptující určitý počet parametrů (podle proměnných částí testovacího scénáře) a navíc je nakonec nutné explicitně uvést vazbu mezi kroky napsanými v testu a právě deklarovanými funkcemi. Zajímavé je, že godog velmi správně odhadl, které části popisu jednotlivých kroků testu jsou proměnné:
You can implement step definitions for undefined steps with these snippets: func iHaveAnAccumulatorWith(arg1 int) error { return godog.ErrPending } func iAddToAccumulator(arg1 int) error { return godog.ErrPending } func theAccumulatedResultShouldBe(arg1 int) error { return godog.ErrPending } func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (\d+)$`, theAccumulatedResultShouldBe) }
Obrázek 6: Výstup z nástroje Godog s návrhem jednotlivých kroků testu.
Poznámka: v programovacím jazyce Python by se při použití knihovny Behave postupovalo nepatrně odlišným způsobem – v této knihovně (a jazyku) se totiž pro navázání jednotlivých kroků testů na funkce používají dekorátory:
from behave import given, then, when from src.adder import add @given('The function {function_name} is callable') def initial_state(context, function_name): pass @when('I call function {function} with arguments {x:d} and {y:d}') def call_add(context, function, x, y): context.result = add(x, y) @then('I should get {expected:d} as a result') def check_integer_result(context, expected): assert context.result == expected, \ "Wrong result: {r} != {e}".format(r=context.result, e=expected)
7. Implementace jednotlivých kroků testu a spuštění testovacího scénáře
Nyní je nutné jednotlivé kroky testu implementovat a uložit do souboru se jménem accumulator_test.go. Funkce s implementací jednotlivých kroků testu prozatím vrací hodnotu ErrPending, protože se jedná o pouhou kostru testu:
package accumulator import ( "github.com/DATA-DOG/godog" ) func iHaveAnAccumulatorWith(arg1 int) error { return godog.ErrPending } func iAddToAccumulator(arg1 int) error { return godog.ErrPending } func theAccumulatedResultShouldBe(arg1 int) error { return godog.ErrPending }
Nesmíme zapomenout na propojení jednotlivých kroků testů z jejich implementací, což zajistí funkce FeatureContext:
func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (\d+)$`, theAccumulatedResultShouldBe) }
Po spuštění by se měly zobrazit tyto zprávy:
Obrázek 7: Výstup z nástroje Godog po spuštění testů, ovšem ve chvíli, kdy ještě nejsou jednotlivé kroky plně implementovány.
8. Proměnná s kontextem celého scénáře
Pokud se podíváme na celý testovací scénář, uvidíme, že mezi jednotlivými kroky scénáře musí existovat objekt reprezentující akumulátor, který testujeme:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario: Accumulate positive integer Given I have an accumulator with 0 When I add 2 to accumulator Then the accumulated result should be 2
Ten vytvoříme zcela snadno – jako globální proměnnou, kterou ovšem prozatím nebudeme inicializovat:
var testAccumulator *acc
Samotnou inicializaci lze provést například hned v prvním kroku testu, kde dokonce již víme, jakou hodnotu má akumulátor mít:
func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator = &acc{value: initialValue} return nil }
Povšimněte si též třetího kroku, kde se porovnává skutečná hodnota akumulátoru s hodnotou očekávanou. V případě chyby se vrátí instance struktury error, v opačném případě hodnota nil:
func theAccumulatedResultShouldBe(expected int) error { if testAccumulator.value == expected { return nil } return fmt.Errorf("Incorrect accumulator value") }
Úplný zdrojový kód tohoto příkladu vypadá následovně:
package accumulator import ( "fmt" "github.com/DATA-DOG/godog" ) var testAccumulator *acc func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator = &acc{value: initialValue} return nil } func iAddToAccumulator(value int) error { testAccumulator.accumulate(value) return nil } func theAccumulatedResultShouldBe(expected int) error { if testAccumulator.value == expected { return nil } return fmt.Errorf("Incorrect accumulator value") } func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (\d+)$`, theAccumulatedResultShouldBe) }
Výsledek spuštění BDD testů:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario: Accumulate positive integer # features/accumulator.feature:4 Given I have an accumulator with 0 # accumulator_test.go:10 -> iHaveAnAccumulatorWith When I add 2 to accumulator # accumulator_test.go:16 -> iAddToAccumulator Then the accumulated result should be 2 # accumulator_test.go:20 -> theAccumulatedResultShouldBe 1 scenarios (1 passed) 3 steps (3 passed) 743.228µs
9. Alternativní způsob inicializace akumulátoru
Ukažme si ještě jeden způsob inicializace akumulátoru. Tentokrát použijeme inicializaci v bloku BeforeScenario, který se zavolá před každým testovacím scénářem (prozatím máme jen jediný scénář):
s.BeforeScenario(func(interface{}) { testAccumulator = &acc{} })
První krok testu se změní – bude již pracovat s existující strukturou testAccumulator:
func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator.value = initialValue return nil }
Upravený testovací scénář vypadá následovně:
package accumulator import ( "fmt" "github.com/DATA-DOG/godog" ) var testAccumulator *acc = nil func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator.value = initialValue return nil } func iAddToAccumulator(value int) error { testAccumulator.accumulate(value) return nil } func theAccumulatedResultShouldBe(expected int) error { if testAccumulator.value == expected { return nil } return fmt.Errorf("Incorrect accumulator value") } func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (\d+)$`, theAccumulatedResultShouldBe) s.BeforeScenario(func(interface{}) { testAccumulator = &acc{} }) }
10. Úprava testů pro možnost použití záporných hodnot
Pokud se dobře podíváte na řádky s.Step() z předchozího příkladu, zjistíte, že se v nich používají regulární výrazy na „odchycení“ celočíselné hodnoty. Problém je, že jsme použili (na základě vytvořené šablony) výraz pouze pro kladná čísla \d+ (tedy pro sekvenci číslic) a nikoli pro čísla záporná. Ostatně si to můžeme snadno vyzkoušet po nepatrné úpravě testovacího scénáře:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario: Accumulate positive integer Given I have an accumulator with 0 When I add 2 to accumulator Then the accumulated result should be 2 Scenario: Accumulate negative integer Given I have an accumulator with 0 When I add -2 to accumulator Then the accumulated result should be -2
V případě, že testy spustíme, vypíše se informace o tom, že některé kroky nejsou implementovány. Ovšem framework prozatím není tak propracovaný, aby nám nabídl úpravu stávajících kroků – pouze nabídne vytvoření kroků nových, v nichž je – (minus) konstantním znakem před číslicemi:
You can implement step definitions for undefined steps with these snippets: func iAddToAccumulator(arg1 int) error { return godog.ErrPending } func theAccumulatedResultShouldBe(arg1 int) error { return godog.ErrPending } func FeatureContext(s *godog.Suite) { s.Step(`^I add -(\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be -(\d+)$`, theAccumulatedResultShouldBe) }
To je pochopitelně nesprávné řešení, ale můžeme se jím inspirovat – znak – (minus) bude nepovinnou částí textu zachycovaného regulárním výrazem:
s.Step(`^I have an accumulator with (-?\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (-?\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (-?\d+)$`, theAccumulatedResultShouldBe)
Úplný zdrojový kód s testy se změní jen nepatrně, ovšem nyní již bude plně funkční:
package accumulator import ( "fmt" "github.com/DATA-DOG/godog" ) var testAccumulator *acc func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator.value = initialValue return nil } func iAddToAccumulator(value int) error { testAccumulator.accumulate(value) return nil } func theAccumulatedResultShouldBe(expected int) error { if testAccumulator.value == expected { return nil } return fmt.Errorf("Incorrect accumulator value") } func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (-?\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (-?\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (-?\d+)$`, theAccumulatedResultShouldBe) s.BeforeScenario(func(interface{}) { testAccumulator = &acc{} }) }
11. Tabulky v BDD testech, aneb zápis osnovy testovacího scénáře
Do testovacího scénáře můžeme přidat i takzvanou osnovu (Scenario Outline):
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario Outline: Accumulate multiple values Given I have an accumulator with 0 When I add <amount> to accumulator Then the accumulated result should be <accumulated> Examples: |amount|accumulated| | 0 | 0 | | 1 | 1 | | 1 | 2 | | 10 | 12 |
Tento scénář se bude pro každý řádek tabulky opakovat, přičemž v každé iteraci se namísto textů <amount> a <result> dosadí hodnoty z příslušného sloupce tabulky. Jedná se přitom o pouhou textovou substituci, takže ve skutečnosti je možné s tabulkami provádět i dosti složité operace.
Samotná implementace testů se zdánlivě nemusí žádným způsobem měnit:
package accumulator import ( "fmt" "github.com/DATA-DOG/godog" ) var testAccumulator *acc func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator.value = initialValue return nil } func iAddToAccumulator(value int) error { testAccumulator.accumulate(value) return nil } func theAccumulatedResultShouldBe(expected int) error { if testAccumulator.value == expected { return nil } return fmt.Errorf("Incorrect accumulator value %d", testAccumulator.value) } func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (-?\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (-?\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (-?\d+)$`, theAccumulatedResultShouldBe) s.BeforeScenario(func(interface{}) { testAccumulator = &acc{} }) }
Ve skutečnosti ovšem testy skončí s chybou:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario Outline: Accumulate multiple values # features/accumulator.feature:4 Given I have an accumulator with 0 # accumulator_test.go:11 -> iHaveAnAccumulatorWith When I add <amount> to accumulator # accumulator_test.go:16 -> iAddToAccumulator Then the accumulated result should be <accumulated> # accumulator_test.go:20 -> theAccumulatedResultShouldBe Examples: | amount | accumulated | | 0 | 0 | | 1 | 1 | | 1 | 2 | Incorrect accumulator value 1 | 10 | 12 | Incorrect accumulator value 10 --- Failed steps: Scenario Outline: Accumulate multiple values # features/accumulator.feature:4 Then the accumulated result should be 2 # features/accumulator.feature:7 Error: Incorrect accumulator value 1 Scenario Outline: Accumulate multiple values # features/accumulator.feature:4 Then the accumulated result should be 12 # features/accumulator.feature:7 Error: Incorrect accumulator value 10 4 scenarios (2 passed, 2 failed) 12 steps (10 passed, 2 failed) 356.714µs
12. Inicializace akumulátoru jedenkrát pro celý test
Důvod pádu předchozí implementace BDD je prostý – osnova testu se ve skutečnosti provede jako čtyři na sobě nezávislé testy, přičemž každý z nich znovu inicializuje akumulátor na nulovou hodnotu. Ovšem testovací scénář můžeme upravit, stejně jako jeho implementaci:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario Outline: Accumulate multiple values When I add <amount> to accumulator Then the accumulated result should be <accumulated> Examples: |amount|accumulated| | 0 | 0 | | 1 | 1 | | 1 | 2 | | 10 | 12 |
V implementaci změníme inicializaci akumulátoru jen jedinkrát pro celý běh testů (BeforeSuite namísto BeforeScenario):
func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (-?\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (-?\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (-?\d+)$`, theAccumulatedResultShouldBe) s.BeforeSuite(func() { testAccumulator = &acc{} }) }
Výsledek po spuštění:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario Outline: Accumulate multiple values # features/accumulator.feature:4 When I add <amount> to accumulator # accumulator_test.go:16 -> iAddToAccumulator Then the accumulated result should be <accumulated> # accumulator_test.go:20 -> theAccumulatedResultShouldBe Examples: | amount | accumulated | | 0 | 0 | | 1 | 1 | | 1 | 2 | | 10 | 12 | 4 scenarios (4 passed) 8 steps (8 passed) 965.258µs
13. Úprava aplikace takovým způsobem, aby bylo možné spustit BDD i jednotkové testy jediným příkazem
V případě, že budete chtít spouštět všechny testy jediným příkazem go test, je nutné celou aplikaci nepatrně upravit. Zejména je nutné zpracovat všechny přepínače začínající na godog., které budeme ukládat do struktury typu godog.Options:
var opt = godog.Options{ Format: "progress", } func init() { godog.BindFlags("godog.", flag.CommandLine, &opt) }
Implementace testovaného balíčku (tedy nikoli testů) se změní takto:
package accumulator import ( "flag" "github.com/DATA-DOG/godog" ) type acc struct { value int } func (a *acc) accumulate(x int) { a.value += x } var opt = godog.Options{ Format: "progress", } func init() { godog.BindFlags("godog.", flag.CommandLine, &opt) }
Do implementace testovacího scénáře přidáme novou funkci nazvanou TestMain (nebo TestCokoli), což vlastně není nic jiného, než běžná funkce volaná jako součást jednotkových testů. V této funkci se provede inicializace knihovny godog, předání případných parametrů zadaných na příkazové řádce a nakonec i samotné spuštění testů:
func TestMain(m *testing.M) { flag.Parse() opt.Paths = flag.Args() status := godog.RunWithOptions("godogs", func(s *godog.Suite) { FeatureContext(s) }, opt) if st := m.Run(); st > status { status = st } os.Exit(status) }
Implementace testovacího scénáře bude po úpravě vypadat následovně:
package accumulator import ( "flag" "fmt" "github.com/DATA-DOG/godog" "os" "testing" ) var testAccumulator *acc func iHaveAnAccumulatorWith(initialValue int) error { testAccumulator.value = initialValue return nil } func iAddToAccumulator(value int) error { testAccumulator.accumulate(value) return nil } func theAccumulatedResultShouldBe(expected int) error { if testAccumulator.value == expected { return nil } return fmt.Errorf("Incorrect accumulator value %d", testAccumulator.value) } func FeatureContext(s *godog.Suite) { s.Step(`^I have an accumulator with (-?\d+)$`, iHaveAnAccumulatorWith) s.Step(`^I add (-?\d+) to accumulator$`, iAddToAccumulator) s.Step(`^the accumulated result should be (-?\d+)$`, theAccumulatedResultShouldBe) s.BeforeScenario(func(interface{}) { testAccumulator = &acc{} }) } func TestMain(m *testing.M) { flag.Parse() opt.Paths = flag.Args() status := godog.RunWithOptions("godogs", func(s *godog.Suite) { FeatureContext(s) }, opt) if st := m.Run(); st > status { status = st } os.Exit(status) }
14. Spuštění BDD testů společně s jednotkovými testy
Nyní se již můžeme pokusit spustit následující (již poměrně komplikovaný) testovací scénář, který je opakován čtyřikrát, pokaždé pro odlišnou sérii vstupních hodnot i očekávaných výsledků:
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario Outline: Accumulate multiple values Given I have an accumulator with 0 When I add <amount> to accumulator Then the accumulated result should be <accumulated> When I add <amount2> to accumulator Then the accumulated result should be <accumulated2> Examples: |amount|accumulated|amount2|accumulated2| | 0 | 0 | 0 | 0 | | 1 | 1 | 1 | 2 | | 2 | 2 | 2 | 4 | | 10 | 10 | 10 | 20 |
Spuštění provedeme tímto příkazem:
$ go test -godog.format=pretty
V zobrazených výsledcích si povšimněte, že se spustí jak BDD testy, tak i případné jednotkové testy (ty neexistují, takže se jen lakonicky vypíše zpráva „testing: warning: no tests to run“):
Feature: simple accumulator checks An accumulator must be able to add a number to its content Scenario Outline: Accumulate multiple values # features/accumulator.feature:4 Given I have an accumulator with 0 # accumulator_test.go:14 -> iHaveAnAccumulatorWith When I add <amount> to accumulator # accumulator_test.go:19 -> iAddToAccumulator Then the accumulated result should be <accumulated> # accumulator_test.go:23 -> theAccumulatedResultShouldBe When I add <amount2> to accumulator # accumulator_test.go:19 -> iAddToAccumulator Then the accumulated result should be <accumulated2> # accumulator_test.go:23 -> theAccumulatedResultShouldBe Examples: | amount | accumulated | amount2 | accumulated2 | | 0 | 0 | 0 | 0 | | 1 | 1 | 1 | 2 | | 2 | 2 | 2 | 4 | | 10 | 10 | 10 | 20 | 4 scenarios (4 passed) 20 steps (20 passed) 468.319µs testing: warning: no tests to run PASS ok _/home/tester/src/go/bank 0.006s
15. Podporované formáty s výsledky BDD testů
Nástroj godog podporuje několik formátů, do nichž může ukládat výsledky BDD testů. Tyto formáty lze specifikovat s využitím volby -godog.f nebo -godog.format, za kterou se zapíše jeden z podporovaných formátů: „events“, „junit“, „pretty“ (ten jsme doposud používali), „progress“ a „cucumber“:
-godog.f string How to format tests output. Built-in formats: - events: Produces JSON event stream, based on spec: 0.1.0. - junit: Prints junit compatible xml to stdout - pretty: Prints every feature with runtime statuses. - progress: Prints a character per step. - cucumber: Produces cucumber JSON format output. (default "progress")
Obrázek 8: Ve výchozím nastavení se pouze pro každý krok scénáře zobrazí zelená nebo červená tečka („progress“).
Obrázek 9: Volba „pretty“ vypíše výsledky testů podobným způsobem, jaký známe například z knihovny Behave pro Python.
16. Formát JUnit
Jedním z formátů, který se používá poměrně často (zejména na CI), je formát knihovny JUnit. Jedná se o formát založený na XML, jenž může být zpracováván různými pluginy pro CI (například pro Jenkins), výsledky testů mohou být převedeny do grafů apod. Tento formát se vytvoří po zadání následujícího přepínače:
-godog.f junit
Konkrétně:
$ go test -godog.f junit
Výsledkem by měl být v našem konkrétním případě tento soubor (časy běhu testů se pochopitelně budou odlišovat, ovšem jak formát, tak i výsledky budou shodné):
<?xml version="1.0" encoding="UTF-8"?> <testsuites name="godogs" tests="4" skipped="0" failures="0" errors="0" time="165.39µs"> <testsuite name="simple accumulator checks" tests="4" skipped="0" failures="0" errors="0" time="73.038µs"> <testcase name="Accumulate multiple values #1" status="passed" time="23.303µs"></testcase> <testcase name="Accumulate multiple values #2" status="passed" time="9.687µs"></testcase> <testcase name="Accumulate multiple values #3" status="passed" time="11.763µs"></testcase> <testcase name="Accumulate multiple values #4" status="passed" time="9.692µs"></testcase> </testsuite> </testsuites>
17. Formáty JSON: Cucumber a JSON event stream
Další dva podporované formáty jsou založeny na JSONu. První z formátů se používá ve světě jazyka Cucumber (uvádím jen zkrácenou podobu, protože se jedná o dosti ukecaný formát):
[ { "uri": "features/accumulator.feature", "id": "simple-accumulator-checks", "keyword": "Feature", "name": "simple accumulator checks", "description": " An accumulator must be able to add a number to its content", "line": 1, "elements": [ { "id": "simple-accumulator-checks;accumulate-multiple-values;;2", "keyword": "Scenario Outline", "name": "Accumulate multiple values", "description": "", "line": 13, "type": "scenario", "steps": [ { "keyword": "Given ", "name": "I have an accumulator with 0", "line": 13, "match": { "location": "accumulator_test.go:14" }, "result": { "status": "passed", "duration": 22618 } }, { "keyword": "When ", "name": "I add 0 to accumulator", "line": 13, "match": { "location": "accumulator_test.go:19" }, "result": { "status": "passed", "duration": 5715 } }, { "keyword": "Then ", "name": "the accumulated result should be 0", "line": 13, "match": { "location": "accumulator_test.go:23" }, "result": { "status": "passed", "duration": 3841 } }, { "keyword": "When ", "name": "I add 0 to accumulator", "line": 13, "match": { "location": "accumulator_test.go:19" }, "result": { "status": "passed", "duration": 3180 } }, { "keyword": "Then ", "name": "the accumulated result should be 0", "line": 13, "match": { "location": "accumulator_test.go:23" }, "result": { "status": "passed", "duration": 3639 } } ] }, ... ... ... ] } ]
$ python -m json.tool
Druhý formát je vlastně sekvence jednotlivých JSONů, které obsahují informace o událostech, které při běhu testů vznikly. Jednou z událostí je i načtení testovacího scénáře s tabulkou, další událostí spuštění testů, nalezení definice testů atd.:
{"event":"TestRunStarted","version":"0.1.0","timestamp":1572963740811,"suite":"godogs"} {"event":"TestSource","location":"features/accumulator.feature:1","source":"Feature: simple accumulator checks\n An accumulator must be able to add a number to its content\n\n Scenario Outline: Accumulate multiple values\n Given I have an accumulator with 0\n When I add \u003camount\u003e to accumulator\n Then the accumulated result should be \u003caccumulated\u003e\n When I add \u003camount2\u003e to accumulator\n Then the accumulated result should be \u003caccumulated2\u003e\n\n Examples:\n |amount|accumulated|amount2|accumulated2|\n | 0 | 0 | 0 | 0 |\n | 1 | 1 | 1 | 2 |\n | 2 | 2 | 2 | 4 |\n | 10 | 10 | 10 | 20 |\n\n"} {"event":"TestCaseStarted","location":"features/accumulator.feature:13","timestamp":1572963740811} {"event":"StepDefinitionFound","location":"features/accumulator.feature:5","definition_id":"accumulator_test.go:14 -\u003e iHaveAnAccumulatorWith","arguments":[[27,28]]} {"event":"TestStepStarted","location":"features/accumulator.feature:5","timestamp":1572963740811} {"event":"TestStepFinished","location":"features/accumulator.feature:5","timestamp":1572963740811,"status":"passed"} {"event":"StepDefinitionFound","location":"features/accumulator.feature:6","definition_id":"accumulator_test.go:19 -\u003e iAddToAccumulator","arguments":[[6,7]]} {"event":"TestStepStarted","location":"features/accumulator.feature:6","timestamp":1572963740811} {"event":"TestStepFinished","location":"features/accumulator.feature:6","timestamp":1572963740811,"status":"passed"} ... ... ... {"event":"TestStepStarted","location":"features/accumulator.feature:9","timestamp":1572963740811} {"event":"TestStepFinished","location":"features/accumulator.feature:9","timestamp":1572963740811,"status":"passed"} {"event":"TestCaseFinished","location":"features/accumulator.feature:16","timestamp":1572963740811,"status":"passed"} {"event":"TestRunFinished","status":"passed","timestamp":1572963740811,"snippets":"","memory":""}
18. Obsah následující části seriálu
V navazující části seriálu o programovacím jazyce Go si popíšeme knihovny a frameworky určené pro testování REST API, což je přesně oblast, ve které se Go velmi často používá.
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/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně pět megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Cucumber for golang
https://github.com/DATA-DOG/godog - How to Use Godog for Behavior-driven Development in Go
https://semaphoreci.com/community/tutorials/how-to-use-godog-for-behavior-driven-development-in-go - Comparative Analysis Of GoLang Testing Frameworks
https://www.slideshare.net/DushyantBhalgami/comparative-analysis-of-golang-testing-frameworks - A Quick Guide to Testing in Golang
https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/ - Tom's Obvious, Minimal Language.
https://github.com/toml-lang/toml - xml.org
http://www.xml.org/ - Soubory .properties
https://en.wikipedia.org/wiki/.properties - Soubory INI
https://en.wikipedia.org/wiki/INI_file - JSON to YAML
https://www.json2yaml.com/ - Data Format Converter
https://toolkit.site/format.html - Viper na GitHubu
https://github.com/spf13/viper - GoDotEnv na GitHubu
https://github.com/joho/godotenv - The fantastic ORM library for Golang
http://gorm.io/ - Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go