Hlavní navigace

Tvorba BDD testů s využitím jazyka Go a nástroje godog

Pavel Tišnovský

Po delší odmlce se budeme v seriálu o programovacím jazyce Go opět věnovat problematice testování. Popíšeme si framework Godog který umožňuje tvorbu a spouštění BDD testů napsaných v doménově specifickém jazyce Gherkin.

Doba čtení: 41 minut

Sdílet

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ů

16. Formát JUnit

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

20. Odkazy na Internetu

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/smartys­treets/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).

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.

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:

  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/
Poznámka: to, že se dají BDD testy psát nezávisle na vlastním programovacím jazyku (jazycích), v nichž je aplikace implementována, je velmi dobrá vlastnost. Musíme si totiž uvědomit, že jednotlivé části aplikace mohou být vytvořeny v různých programovacích jazycích a dokonce i různými vývojovými týmy. Testy zjišťující chování aplikace jako celku je ovšem možné tvořit nezávisle na použitých jazycích.

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
}
Poznámka: povšimněte si, že testovat budeme metodu, jejíž název začíná malým písmenem, tj. jedná se o metodu viditelnou pouze v rámci aktuálního balíčku. Tomu budeme muset přizpůsobit i testy – budou muset být umístěny ve stejném balíčku.

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)
}
Poznámka: pokud se podíváte na výše uvedený příklad naprogramovaný v Pythonu, je zřejmé, že Python (resp. přesněji řečeno jeho knihovna Behave) automaticky do každé funkce s definicí testů dodává kontext ve formě reference na objekt.

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
}
Poznámka: připomeňme si, že v Go můžeme (většinou) pracovat s hodnotou referencovanou přes ukazatel stejným způsobem, jakoby se jednalo o přímou proměnnou – není tedy zapotřebí používat zápisu s *.

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
                        }
                    }
                ]
            },
            ...
            ...
            ...
        ]
    }
]
Poznámka: výsledek byl naformátován přes:
$ 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:

# Příklad Stručný popis Cesta
1 bdd_iteration0 projekt, který neobsahuje žádné testy https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration0
2 bdd_iteration1 kostra BDD testů, ovšem jednotlivé kroky nejsou plně implementovány https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration1
3 bdd_iteration2 implementace jednotlivých kroků BDD testů https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration2
4 bdd_iteration3 alternativní způsob inicializace akumulátoru https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration3
5 bdd_iteration4 úprava testů tak, aby akceptovaly záporná čísla https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration4
6 bdd_iteration5 tabulky v testovacích scénářích https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration5
7 bdd_iteration_5B vylepšení předchozího příkladu – inicializace akumulátoru jen jedenkrát https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration_5B
8 bdd_iteration6 tabulky v testovacích scénářích https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration6
9 bdd_iteration7 spojení BDD s jednotkovými testy https://github.com/tisnik/go-root/blob/master/article41/bdd_i­teration7

20. Odkazy na Internetu

  1. Cucumber for golang
    https://github.com/DATA-DOG/godog
  2. How to Use Godog for Behavior-driven Development in Go
    https://semaphoreci.com/com­munity/tutorials/how-to-use-godog-for-behavior-driven-development-in-go
  3. Comparative Analysis Of GoLang Testing Frameworks
    https://www.slideshare.net/Dushy­antBhalgami/comparative-analysis-of-golang-testing-frameworks
  4. A Quick Guide to Testing in Golang
    https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/
  5. Tom's Obvious, Minimal Language.
    https://github.com/toml-lang/toml
  6. xml.org
    http://www.xml.org/
  7. Soubory .properties
    https://en.wikipedia.org/wi­ki/.properties
  8. Soubory INI
    https://en.wikipedia.org/wi­ki/INI_file
  9. JSON to YAML
    https://www.json2yaml.com/
  10. Data Format Converter
    https://toolkit.site/format.html
  11. Viper na GitHubu
    https://github.com/spf13/viper
  12. GoDotEnv na GitHubu
    https://github.com/joho/godotenv
  13. The fantastic ORM library for Golang
    http://gorm.io/
  14. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  15. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  16. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  17. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  18. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  19. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  20. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  21. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  22. Sémantické verzování
    https://semver.org/
  23. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  24. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  25. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  26. Modules
    https://github.com/golang/go/wi­ki/Modules
  27. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  28. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  29. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  30. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  31. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  32. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  33. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  34. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  35. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  36. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  37. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  38. gopls
    https://github.com/golang/go/wi­ki/gopls
  39. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  40. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  41. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  42. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  43. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  44. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  45. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  46. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  47. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  48. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  49. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  50. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  51. Package trace
    https://golang.org/pkg/runtime/trace/
  52. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  53. Command trace
    https://golang.org/cmd/trace/
  54. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  55. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  56. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  57. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  58. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  59. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  60. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  61. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  62. 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/
  63. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  64. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  65. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  66. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  67. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  68. The LLDB Debugger
    http://lldb.llvm.org/
  69. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  70. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  71. 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
  72. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  73. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  74. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  75. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  76. 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/
  77. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  78. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  79. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  80. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  81. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  82. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  83. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  84. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  85. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  86. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  87. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  88. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  89. go-cron
    https://github.com/rk/go-cron
  90. gocron
    https://github.com/jasonlvhit/gocron
  91. clockwork
    https://github.com/whiteShtef/cloc­kwork
  92. clockwerk
    https://github.com/onatm/clockwerk
  93. JobRunner
    https://github.com/bamzi/jobrunner
  94. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  95. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  96. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  97. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  98. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  99. go-prompt
    https://github.com/c-bata/go-prompt
  100. readline
    https://github.com/chzyer/readline
  101. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  102. go-readline
    https://github.com/fiorix/go-readline
  103. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  104. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  105. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  106. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  107. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  108. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  109. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  110. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  111. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  112. Editline Library (libedit)
    http://thrysoee.dk/editline/
  113. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  114. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  115. WinEditLine
    http://mingweditline.sourceforge.net/
  116. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  117. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  118. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  119. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  120. history(3) – Linux man page
    https://linux.die.net/man/3/history
  121. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  122. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  123. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  124. Balíček ogletest
    https://github.com/jacobsa/ogletest
  125. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  126. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  127. package testing
    https://golang.org/pkg/testing/
  128. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  129. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  130. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  131. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  132. GoConvey
    http://goconvey.co/
  133. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  134. 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
  135. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  136. package gg
    https://godoc.org/github.com/fo­gleman/gg
  137. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  138. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  139. The Go image package
    https://blog.golang.org/go-image-package
  140. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  141. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  142. YAML
    https://yaml.org/
  143. edn
    https://github.com/edn-format/edn
  144. Smile
    https://github.com/FasterXML/smile-format-specification
  145. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  146. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  147. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  148. Introducing JSON
    http://json.org/
  149. Package json
    https://golang.org/pkg/encoding/json/
  150. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  151. Go by Example: JSON
    https://gobyexample.com/json
  152. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  153. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  154. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  155. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  156. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  157. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  158. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  159. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  160. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  161. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  162. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  163. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  164. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  165. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  166. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  167. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  168. Algorithms to Go
    https://yourbasic.org/
  169. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  170. 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/
  171. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  172. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  173. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  174. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  175. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  176. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  177. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  178. The Go Programming Language (home page)
    https://golang.org/
  179. GoDoc
    https://godoc.org/
  180. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  181. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  182. The Go Programming Language Specification
    https://golang.org/ref/spec
  183. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  184. Package builtin
    https://golang.org/pkg/builtin/
  185. Package fmt
    https://golang.org/pkg/fmt/
  186. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  187. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  188. Learning Go
    https://www.miek.nl/go/
  189. Go Bootcamp
    http://www.golangbootcamp.com/
  190. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  191. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  192. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  193. The Go Blog
    https://blog.golang.org/
  194. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  195. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  196. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  197. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  198. 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
  199. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  200. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  201. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  202. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  203. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  204. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  205. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  206. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  207. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  208. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  209. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  210. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  211. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  212. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  213. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  214. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  215. 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/
  216. 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
  217. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  218. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  219. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  220. Go vs. Python
    https://www.peterbe.com/plog/govspy
  221. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  222. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  223. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  224. Go by Example: Slices
    https://gobyexample.com/slices
  225. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  226. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  227. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  228. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  229. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  230. nils In Go
    https://go101.org/article/nil.html
  231. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  232. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  233. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  234. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  235. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  236. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  237. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  238. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  239. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  240. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  241. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  242. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  243. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  244. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  245. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  246. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  247. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  248. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  249. 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
  250. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  251. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  252. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  253. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  254. Selectors
    https://golang.org/ref/spec#Selectors
  255. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  256. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  257. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  258. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  259. Part 21: Goroutines
    https://golangbot.com/goroutines/
  260. Part 22: Channels
    https://golangbot.com/channels/
  261. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  262. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  263. 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/
  264. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  265. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  266. Control Structures
    https://www.golang-book.com/books/intro/5
  267. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  268. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  269. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  270. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  271. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  272. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  273. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  274. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  275. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  276. Effective Go
    https://golang.org/doc/ef­fective_go.html
  277. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  278. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  279. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  280. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  281. Nils in Go
    https://www.doxsey.net/blog/nils-in-go