Hlavní navigace

Knihovny určené pro tvorbu testů v programovacím jazyce Go

Pavel Tišnovský

Dnes navážeme na část předchozí, v níž jsme se seznámili s psaním jednotkových testů i s mockováním funkcí a metod. Dnes si popíšeme další knihovny, které jsou určeny pro usnadnění psaní jednotkových i integračních testů.

Doba čtení: 45 minut

Sdílet

11. Kombinace knihoven assert a assertions

12. Zjednodušení zápisu testů použitím „tečkového“ importu

13. Knihovna GoConvey

14. Přepis jednotkových testů s použitím knihovny GoConvey

15. Výsledky testů

16. Alternativní formát výstupu

17. Pořadí spouštění jednotlivých kroků definovaných v BDD testech

18. Výsledky BDD testů

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

20. Odkazy na Internetu

1. Knihovny určené pro tvorbu testů v programovacím jazyce Go

V předchozí části seriálu o programovacím jazyku Go jsme si řekli základní informace o tvorbě jednotkových testů (unit test) v jazyce Go. Připomeňme si jen ve stručnosti, že již v základní instalaci tohoto programovacího jazyka je programátorům k dispozici balíček nazvaný testing, který je možné pro psaní jednotkových testů použít. Současně je možné základnímu nástroji go, který spouští většinu standardních operací v kontextu ekosystému tohoto jazyka, předat příkaz test, jenž provede následující operace:

  1. Nalezne v aktuálním adresáři popř. i v případných podadresářích soubory končící na „_test.go“ (jména souborů by ovšem neměla začínat samotným podtržítkem).
  2. V těchto souborech se nástroj dále pokusí najít ty funkce, jejichž názvy začínají prefixem „Test“, za nímž následuje alespoň jedno velké písmeno (korektní název testu je tedy například „TestAdd“, ovšem nikoli už „Testik“).
  3. Tyto funkce se následně spustí s tím, že jejich prvním (a většinou i jediným) parametrem bude reference na strukturu typutesting.T. Existuje přitom možnost paralelního spouštění testů, což by nám však nemělo v praxi vadit, protože jednotlivé testy by měly být naprogramovány takovým způsobem, aby je bylo možné spouštět izolovaně.

Samotné jednotkové testy jsou psány přímo v programovacím jazyce Go, a to (alespoň při použití základního modulu testing) bez použití dalších specializovaných knihoven a případných nových jazykových konstrukcí. Slovy tvůrců tohoto jazyka:

„A related point is that testing frameworks tend to develop into mini-languages of their own, with conditionals and controls and printing mechanisms, but Go already has all those capabilities; why recreate them? We'd rather write tests in Go; it's one fewer language to learn and the approach keeps the tests straightforward and easy to understand.“

Poznámka: v praxi to znamená mj. i to, že nemůžeme použít (neexistující) klíčové slovo assert, což je mezi moderními programovacími jazyky spíše výjimka.

2. Krátké zopakování – použití standardního balíčku testing pro vytvoření jednotkového testu

Připomeňme si nyní, jak vlastně mohou jednotkové testy vypadat, nejprve v případě, že použijeme standardní balíček testing. Testovat budeme známou funkci pro výpočet faktoriálu, která je implementována rekurzivním výpočtem (což možná není pro jazyk Go příliš idiomatické a vlastně ani nejrychlejší řešení). Tato funkce, kterou budeme v rámci dnešního článku testovat hned několika různými způsoby a knihovnami, je implementována následujícím způsobem:

package factorial
 
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
Poznámka: funkci pro výpočet faktoriálu je pochopitelně možné napsat i jednodušším způsobem (minimálně lze ušetřit jednu programovou větev), ovšem ukázaná implementace má tu výhodu, že ji lze snadno měnit a zkoušet tak vliv provedených změn na jednotlivé testy.

Jednotkové testy budou uloženy v souboru nazvaném factorial_test.go. V tomto souboru samozřejmě může být napsáno libovolné množství jednotkových testů, pouze musíme dodržet výše zmíněnou jmennou konvenci. Povšimněte si, že v jednotlivých testech explicitně zapisujeme podmínky a v nich se volají metody t.Error, t.Errorf, t.Fail, t.FailNow, t.Fatal atd., s nimiž jsme se seznámili minule:

package factorial_test
 
import (
        "factorial"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        result := factorial.Factorial(0)
        if result != 1 {
                t.Errorf("Expected that 0! == 1, but got %d instead", result)
        }
}
 
func TestFactorialForOne(t *testing.T) {
        result := factorial.Factorial(1)
        if result != 1 {
                t.Errorf("Expected that 1! == 1, but got %d instead", result)
        }
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        result := factorial.Factorial(5)
        if result <= 10 || result >= 10000 {
                t.Errorf("Expected that 5! == is between 10..10000")
        }
}
 
func TestFactorialForSmallNumberNegative(t *testing.T) {
        result := factorial.Factorial(20)
        if result <= 10 || result >= 10000 {
                t.Errorf("Expected that 20! == is between 10..10000")
        }
}
 
func TestFactorialForTen(t *testing.T) {
        result := factorial.Factorial(10)
        expected := int64(3628800)
        if result != expected {
                t.Errorf("Expected that 10! == %d, but got %d instead", expected, result)
        }
}
 
func TestFactorialForBigNumber(t *testing.T) {
        result := factorial.Factorial(20)
        if result <= 0 {
                t.Errorf("Expected that 20! > 0, but got negative number %d instead", result)
        }
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        result := factorial.Factorial(30)
        if result <= 0 {
                t.Errorf("Expected that 30! > 0, but got negative number %d instead", result)
        }
}

3. Výsledek běhu standardních jednotkových testů

Zajímavé bude i samotné spuštění jednotkových testů, protože dva testy jsou schválně napsány takovým způsobem, aby jimi testovaná podmínka ve skutečnosti nebyla splněna. Aby bylo možné získat podrobnější informace i o těch testech, které proběhly korektně, použijeme při spouštění příkazu go test i přepínač -v (verbose):

$ go test -v

Výsledky by měly vypadat takto:

=== RUN   TestFactorialForZero
--- PASS: TestFactorialForZero (0.00s)
=== RUN   TestFactorialForOne
--- PASS: TestFactorialForOne (0.00s)
=== RUN   TestFactorialForSmallNumber
--- PASS: TestFactorialForSmallNumber (0.00s)
=== RUN   TestFactorialForSmallNumberNegative
--- FAIL: TestFactorialForSmallNumberNegative (0.00s)
    factorial_test.go:32: Expected that 20! == is between 10..10000
=== RUN   TestFactorialForTen
--- PASS: TestFactorialForTen (0.00s)
=== RUN   TestFactorialForBigNumber
--- PASS: TestFactorialForBigNumber (0.00s)
=== RUN   TestFactorialForEvenBiggerNumber
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
    factorial_test.go:54: Expected that 30! > 0, but got negative number -8764578968847253504 instead
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/01_factorial_testing   0.004s
Poznámka: jeden z testů byl napsán schválně takovým způsobem, aby jeho podmínka nebyla splněna. Další z testů, které nebyly úspěšné, sice teoreticky správně předpokládá, že faktoriál jakékoli celé hodnoty by měl být kladné číslo, ovšem kvůli přetečení výsledků při použití datového typu int64 tato podmínka ve skutečnosti není vždy splněna (ostatně si můžete sami vyzkoušet, pro jaké n je funkce korektní a pro jaké by bylo zapotřebí použít balíček big a jeho datový typ Int s prakticky neomezenou přesností).

Pokud do testu přidáme funkci začínající prefixem „Test“, ovšem se špatným (či neexistujícím) typem parametru, bude tento problém nahlášen při pokusu o spuštění jednotkových testů:

package factorial_test
 
import (
        "factorial"
        "testing"
)
 
func TestX() {
        println("TestX")
}
 
func TestFactorialForZero(t *testing.T) {
        result := factorial.Factorial(0)
        if result != 1 {
                t.Errorf("Expected that 0! == 1, but got %d instead", result)
        }
}

V případě, že se testy pokusíme spustit, bude zpráva o špatné signatuře (hlavičce) funkce s implementací testu nahlášena již ve fázi přípravy testů (nikoli až při jejich běhu):

$ go test -v
 
# factorial
/home/test/go/src/factorial/factorial_test.go:8:1: wrong signature for TestX, must be: func TestX(t *testing.T)
FAIL    factorial [setup failed]

4. Když možnosti balíčku testing nedostačují…

Již při letmém pohledu na jednotkové testy, které jsme si ukázali v předchozích dvou kapitolách, je patrné, že se v nich prakticky neustále opakují ty samé operace: zavolání testované funkce (nebo metody), porovnání výsledků zavolané funkce s očekávanými hodnotami a případné zahlášení chybného výsledku v programové větvi s podmínkou. To sice plně odpovídá záměrům tvůrců programovacího jazyka Go, ovšem je nutné si uvědomit, že (pravděpodobně) velká většina programátorů, kteří jazyk Go používají, vyvíjí SW i v jiných programovacích jazycích, v nichž může být přístup k tvorbě jednotkových testů odlišný. Z tohoto důvodu pravděpodobně nebude příliš velkým překvapením, že vzniklo relativně velké množství různých knihoven i celých nástrojů, jejichž cílem je umožnit psát jednotkové a integrační testy sice přímo v programovacím jazyku Go, ovšem s minimem ručního a explicitního zápisu programových bloků if s podmínkami. Dnes si některé z těchto knihoven alespoň ve stručnosti představíme:

  1. Pomocná knihovna oglematchers usnadňující specifikaci podmínek.
  2. Knihovna ogletest využívající zmíněnou knihovnu oglematchers.
  3. Jednoduše použitelná knihovna assertions, opět usnadňující specifikaci podmínek.
  4. Knihovna nazvaná assert, která doplňuje knihovnu assertions.
  5. A konečně se jedná o knihovnu se jménem GoConvey.

5. Přepis jednotkových testů s využitím balíčku oglematchers

První z knihoven, s níž se dnes seznámíme, se jmenuje oglematchers a její zdrojové kódy nalezneme na GitHubu na adrese https://github.com/jacobsa/o­glematchers. Striktně řečeno se vlastně nejedná o knihovnu určenou pro vytváření plnohodnotných jednotkových testů, ale „pouze“ o sadu 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, s níž se seznámíme až v sedmé kapitole.

Instalace této knihovny probíhá naprosto stejným způsobem, jako instalace prakticky jakéhokoli jiného balíčku programovacího jazyka Go:

$ go get -u github.com/jacobsa/oglematchers

Principem, na němž je knihovna oglematchers založena, jsou takzvané matchery, což jsou objekty testující jednu či více podmínek, které se jednou vytvoří a následně je možné je použít ve více testech či ve více krocích jediného testu. Navíc je možné matchery různým způsobem kombinovat ve stylu „musí platit alespoň jedna z podmínek“ nebo „musí současně platit všechny podmínky“. Nezávisle na tom, jakým způsobem je matcher vytvořen, nabízí programátorům metodu Matches, které se předá testovaná hodnota či testované hodnoty a výsledkem je buď návratová hodnota nil ve chvíli, kdy test proběhl v pořádku, či „nenilová“ hodnota reprezentující chybu:

Poznámka: velkou předností matcherů oproti explicitně zapisovaným podmínkám je právě možnost použít je v libovolném množství jednotkových testů. Toho lze samozřejmě dosáhnout i při použití standardní knihovny testing, protože funkce jsou v jazyku Go plnohodnotným datovým typem (a tedy je lze uložit do proměnné atd. atd.), ovšem výsledný zápis nebude příliš standardní.

Jednotkové testy naprogramované s využitím základní knihovny testing je samozřejmě možné relativně snadno přepsat takovým způsobem, aby se použily některé možnosti nabízené knihovnou oglematchers. Nejprve se podívejme na ty testy, v nichž je použita jednoduchá podmínka porovnávající výsledek výpočtu faktoriálu s očekávanou hodnotou. Povšimněte si, že se skutečně jedná o pomocnou knihovnu, protože používáme všechny konvence, které známe ze standardní knihovny testing; pouze samotný zápis testované podmínky a její vyhodnocení je odlišný. V tom nejjednodušším případě můžeme zkonstruovat matcher, který porovná hodnotu vypočtenou funkcí Factorial s očekávanou hodnotou. Samotný matcher se v takovém případě sestrojí konstruktorem Equals:

func TestFactorialForZero(t *testing.T) {
        result := factorial.Factorial(0)
        m := oglematchers.Equals(1)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 0! == 1, but got %d instead", result)
        }
}

Samozřejmě jsou k dispozici i jiné typy matcherů, například takový matcher, který testuje podmínku „větší než“. Ten se vytvoří konstruktorem GreaterThan; opět je samozřejmě k dispozici metoda Matches pro vlastní testování:

func TestFactorialForBigNumber(t *testing.T) {
        result := factorial.Factorial(20)
        m := oglematchers.GreaterThan(0)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 20! > 0, but got negative number %d instead", result)
        }
}

Zajímavější jsou ty jednotkové testy, ve kterých se používají dvě podmínky, které musí platit současně. Zde je možné vytvořit matcher konstruktorem AllOf, jemuž se předají další matchery, jejichž podmínky musí platit současně. Podobným způsobem je možné v případě potřeby konstruovat i složitější podmínky:

func TestFactorialForSmallNumber(t *testing.T) {
        result := factorial.Factorial(5)
        m := oglematchers.AllOf(
                oglematchers.GreaterThan(10),
                oglematchers.LessThan(10000))
        if m.Matches(result) != nil {
                t.Errorf("Expected that 5! == is between 10..10000")
        }
}

Úplný kód příkladu s přepsanými jednotkovými testy může vypadat následovně:

package factorial_test
 
import (
        "factorial"
        "github.com/jacobsa/oglematchers"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        result := factorial.Factorial(0)
        m := oglematchers.Equals(1)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 0! == 1, but got %d instead", result)
        }
}
 
func TestFactorialForOne(t *testing.T) {
        result := factorial.Factorial(1)
        m := oglematchers.Equals(1)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 1! == 1, but got %d instead", result)
        }
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        result := factorial.Factorial(5)
        m := oglematchers.AllOf(
                oglematchers.GreaterThan(10),
                oglematchers.LessThan(10000))
        if m.Matches(result) != nil {
                t.Errorf("Expected that 5! == is between 10..10000")
        }
}
 
func TestFactorialForSmallNumberNegative(t *testing.T) {
        result := factorial.Factorial(20)
        m := oglematchers.AllOf(
                oglematchers.GreaterThan(10),
                oglematchers.LessThan(10000))
        if m.Matches(result) != nil {
                t.Errorf("Expected that 20! == is between 10..10000")
        }
}
 
func TestFactorialForTen(t *testing.T) {
        result := factorial.Factorial(10)
        expected := int64(3628800)
        m := oglematchers.Equals(expected)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 10! == %d, but got %d instead", expected, result)
        }
}
 
func TestFactorialForBigNumber(t *testing.T) {
        result := factorial.Factorial(20)
        m := oglematchers.GreaterThan(0)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 20! > 0, but got negative number %d instead", result)
        }
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        result := factorial.Factorial(30)
        m := oglematchers.GreaterThan(0)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 30! > 0, but got negative number %d instead", result)
        }
}

Vzhledem k tomu, že jsme použili stejný formát testů, jaký odpovídá standardnímu balíčku testing, budou se i samotné testy spouštět naprosto stejným způsobem, jako běžné jednotkové testy:

$ go test
 
--- FAIL: TestFactorialForSmallNumberNegative (0.00s)
    factorial_test.go:41: Expected that 20! == is between 10..10000
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
    factorial_test.go:66: Expected that 30! > 0, but got negative number -8764578968847253504 instead
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/02_factorial_oglematchers      0.004s

6. Zjednodušení testů použitím „tečkového“ importu

Psaní prefixu „oglematchers“ před všemi funkcemi z balíčku oglematchers je v případě tvorby jednotkových testů většinou nadbytečné, protože (opět většinou) v modulech s jednotkovými testy importujeme pouze omezené množství dalších balíčků – testovaný balíček, balíček oglematchers a pochopitelně i balíček testing. Samotný zápis zdrojového kódu s testy si v takovém případě můžeme zjednodušit použitím „tečkového“ importu, s nímž jsme se již seznámili v předchozích částech tohoto seriálu:

import (
        "factorial"
        . "github.com/jacobsa/oglematchers"
        "testing"
)

Podívejme se nyní na to, jak bude vypadat přepsaný zdrojový kód se všemi jednotkovými testy. Samotná logika testů zůstává zachována, pouze není nutné psát prefix s názvem balíčku:

package factorial_test
 
import (
        "factorial"
        . "github.com/jacobsa/oglematchers"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        result := factorial.Factorial(0)
        m := Equals(1)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 0! == 1, but got %d instead", result)
        }
}
 
func TestFactorialForOne(t *testing.T) {
        result := factorial.Factorial(1)
        m := Equals(1)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 1! == 1, but got %d instead", result)
        }
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        result := factorial.Factorial(5)
        m := AllOf(
                GreaterThan(10),
                LessThan(10000))
        if m.Matches(result) != nil {
                t.Errorf("Expected that 5! == is between 10..10000")
        }
}
 
func TestFactorialForSmallNumberNegative(t *testing.T) {
        result := factorial.Factorial(20)
        m := AllOf(
                GreaterThan(10),
                LessThan(10000))
        if m.Matches(result) != nil {
                t.Errorf("Expected that 20! == is between 10..10000")
        }
}
 
func TestFactorialForTen(t *testing.T) {
        result := factorial.Factorial(10)
        expected := int64(3628800)
        m := Equals(expected)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 10! == %d, but got %d instead", expected, result)
        }
}
 
func TestFactorialForBigNumber(t *testing.T) {
        result := factorial.Factorial(20)
        m := GreaterThan(0)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 20! > 0, but got negative number %d instead", result)
        }
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        result := factorial.Factorial(30)
        m := GreaterThan(0)
        if m.Matches(result) != nil {
                t.Errorf("Expected that 30! > 0, but got negative number %d instead", result)
        }
}

Vzhledem k tomu, že se logika jednotlivých jednotkových testů nezměnila, je pochopitelné, že i jejich výsledky budou naprosto stejné, o čemž se samozřejmě můžeme velmi snadno přesvědčit:

$ go test
 
--- FAIL: TestFactorialForSmallNumberNegative (0.00s)
    factorial_test.go:41: Expected that 20! == is between 10..10000
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
    factorial_test.go:66: Expected that 30! > 0, but got negative number -8764578968847253504 instead
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/03_factorial_oglematchers2     0.004s

7. Kombinace balíčků ogletest a oglematchers

Možnosti poskytované knihovnou oglematchers v celé šíři oceníme až ve chvíli, kdy ji zkombinujeme s knihovnou nazvanou ogletest (asi není nutno dodávat, že obě knihovny pochází ze stejného zdroje). Knihovna ogletest se snaží nahradit některé koncepty základní knihovny testing, což vyžaduje provedení určitých změn ve struktuře testů. Stále se sice využívá příkazu go test, ovšem v celém souboru s jednotkovými testy se bude nacházet jen jediný test, který vlastně bude spouštět všechny testy další. Tento speciální test může vypadat následovně:

func TestOgletest(t *testing.T) {
        ogletest.RunTests(t)
}

Dále vytvoříme datovou strukturu, v níž bude možné udržovat kontext, v rámci něhož jsou jednotkové testy spouštěny:

type FactorialTest struct{}

Tuto strukturu bude nutné zaregistrovat ve funkci init:

func init() {
        ogletest.RegisterTestSuite(&FactorialTest{})
}

V tomto okamžiku je možné spustit všechny jednotkové testy, které jsou implementovány jako metody datového typu FactorialTest, například:

func (t *FactorialTest) FactorialForZero() {
        result := factorial.Factorial(0)
        ogletest.ExpectThat(result, oglematchers.Equals(1))
}

nebo:

func (t *FactorialTest) TestFactorialSmallNumberNegative() {
        result := factorial.Factorial(20)
        ogletest.ExpectThat(result, oglematchers.AllOf(
                oglematchers.GreaterThan(10),
                oglematchers.LessThan(10000)))
}
Poznámka: povšimněte si, že v tomto případě není nutné, aby měly funkce/metody s implementací jednotkových testů nějaká speciální jména.

V knihovně ogletest nalezneme dvě skupiny funkcí určených pro psaní jednotkových testů. V první skupině (Expect*) jsou funkce s podmínkou, při jejímž nesplnění se zaznamená chyba a test pokračuje dále. Ve druhé skupině (Assert*) naopak nalezneme takové funkce, které při nesplnění podmínky aktuálně spuštěný test ukončí. Samotné podmínky jsou v obou skupinách prakticky totožné:

První skupina Druhá skupina
ExpectEq AssertEq
ExpectFalse AssertFalse
ExpectGe AssertGe
ExpectGt AssertGt
ExpectLe AssertLe
ExpectLt AssertLt
ExpectNe AssertNe
ExpectThat AssertThat
ExpectTrue AssertTrue

V příkladu použijeme funkci ExpectThat, která jako testovanou podmínku akceptuje matcher.

8. Třetí varianta jednotkových testů

Podívejme se nyní na způsob reimplementace jednotkových testů, v nichž se použije jak knihovna oglematchers, tak i knihovna ogletest. V každém testu nejprve vytvoříme instanci matcheru a posléze porovnáme očekávaný výsledek s výsledkem vypočteným funkcí faktoriálu, a to s využitím funkce ExpectThat, o níž jsme se zmínili v předchozí kapitole. Novou variantu jednotkových testů najdeme na adrese https://github.com/tisnik/go-root/blob/master/article18/04_fac­torial_ogletest/factorial_tes­t.go:

package factorial_test
 
import (
        "factorial"
        "github.com/jacobsa/oglematchers"
        "github.com/jacobsa/ogletest"
        "testing"
)
 
func TestOgletest(t *testing.T) {
        ogletest.RunTests(t)
}
 
type FactorialTest struct{}
 
func init() {
        ogletest.RegisterTestSuite(&FactorialTest{})
}
 
func (t *FactorialTest) FactorialForZero() {
        result := factorial.Factorial(0)
        ogletest.ExpectThat(result, oglematchers.Equals(1))
}
 
func (t *FactorialTest) FactorialForOne() {
        result := factorial.Factorial(1)
        ogletest.ExpectThat(result, oglematchers.Equals(1))
}
 
func (t *FactorialTest) TestFactorialSmallNumber() {
        result := factorial.Factorial(5)
        ogletest.ExpectThat(result, oglematchers.AllOf(
                oglematchers.GreaterThan(10),
                oglematchers.LessThan(10000)))
}
 
func (t *FactorialTest) TestFactorialSmallNumberNegative() {
        result := factorial.Factorial(20)
        ogletest.ExpectThat(result, oglematchers.AllOf(
                oglematchers.GreaterThan(10),
                oglematchers.LessThan(10000)))
}
 
func (t *FactorialTest) TestFactorialForTen() {
        result := factorial.Factorial(10)
        expected := int64(3628800)
        ogletest.ExpectThat(result, oglematchers.Equals(expected))
}
 
func (t *FactorialTest) TestFactorialBigNumber() {
        result := factorial.Factorial(20)
        ogletest.ExpectThat(result, oglematchers.GreaterThan(0))
}
 
func (t *FactorialTest) TestFactorialEvenBiggerNumber() {
        result := factorial.Factorial(30)
        ogletest.ExpectThat(result, oglematchers.GreaterThan(0))
}

Testy opět spustíme příkazem:

$ go test

Tentokrát je ovšem už z výstupu patrné, že se o samotné spouštění jednotlivých podmínek stará knihovna ogletest a nikoli testing:

[----------] Running tests from FactorialTest
[ RUN      ] FactorialTest.FactorialForZero
[       OK ] FactorialTest.FactorialForZero
[ RUN      ] FactorialTest.FactorialForOne
[       OK ] FactorialTest.FactorialForOne
[ RUN      ] FactorialTest.TestFactorialSmallNumber
[       OK ] FactorialTest.TestFactorialSmallNumber
[ RUN      ] FactorialTest.TestFactorialSmallNumberNegative
factorial_test.go:39:
Expected: greater than 10, and less than 10000
Actual:   2432902008176640000
 
[  FAILED  ] FactorialTest.TestFactorialSmallNumberNegative
[ RUN      ] FactorialTest.TestFactorialForTen
[       OK ] FactorialTest.TestFactorialForTen
[ RUN      ] FactorialTest.TestFactorialBigNumber
[       OK ] FactorialTest.TestFactorialBigNumber
[ RUN      ] FactorialTest.TestFactorialEvenBiggerNumber
factorial_test.go:57:
Expected: greater than 0
Actual:   -8764578968847253504
 
[  FAILED  ] FactorialTest.TestFactorialEvenBiggerNumber
[----------] Finished with tests from FactorialTest
--- FAIL: TestOgletest (0.00s)
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/04_factorial_ogletest  0.005s

Samozřejmě nám nic nebrání použít „tečkový“ import, v tomto případě jak knihovny oglematchers, tak i knihovny ogletest. K žádnému konfliktu ve jménech identifikátorů by nemělo dojít, a samotná implementace testů se zkrátí a vlastně i zpřehlední:

package factorial_test
 
import (
        "factorial"
        . "github.com/jacobsa/oglematchers"
        . "github.com/jacobsa/ogletest"
        "testing"
)
 
func TestOgletest(t *testing.T) {
        RunTests(t)
}
 
type FactorialTest struct{}
 
func init() {
        RegisterTestSuite(&FactorialTest{})
}
 
func (t *FactorialTest) FactorialForZero() {
        result := factorial.Factorial(0)
        ExpectThat(result, Equals(1))
}
 
func (t *FactorialTest) FactorialForOne() {
        result := factorial.Factorial(1)
        ExpectThat(result, Equals(1))
}
 
func (t *FactorialTest) TestFactorialSmallNumber() {
        result := factorial.Factorial(5)
        ExpectThat(result, AllOf(
                GreaterThan(10),
                LessThan(10000)))
}
 
func (t *FactorialTest) TestFactorialSmallNumberNegative() {
        result := factorial.Factorial(20)
        ExpectThat(result, AllOf(
                GreaterThan(10),
                LessThan(10000)))
}
 
func (t *FactorialTest) TestFactorialForTen() {
        result := factorial.Factorial(10)
        expected := int64(3628800)
        ExpectThat(result, Equals(expected))
}
 
func (t *FactorialTest) TestFactorialBigNumber() {
        result := factorial.Factorial(20)
        ExpectThat(result, GreaterThan(0))
}
 
func (t *FactorialTest) TestFactorialEvenBiggerNumber() {
        result := factorial.Factorial(30)
        ExpectThat(result, GreaterThan(0))
}

Se shodnými výsledky:

[----------] Running tests from FactorialTest
[ RUN      ] FactorialTest.FactorialForZero
[       OK ] FactorialTest.FactorialForZero
[ RUN      ] FactorialTest.FactorialForOne
[       OK ] FactorialTest.FactorialForOne
[ RUN      ] FactorialTest.TestFactorialSmallNumber
[       OK ] FactorialTest.TestFactorialSmallNumber
[ RUN      ] FactorialTest.TestFactorialSmallNumberNegative
factorial_test.go:39:
Expected: greater than 10, and less than 10000
Actual:   2432902008176640000

[  FAILED  ] FactorialTest.TestFactorialSmallNumberNegative
[ RUN      ] FactorialTest.TestFactorialForTen
[       OK ] FactorialTest.TestFactorialForTen
[ RUN      ] FactorialTest.TestFactorialBigNumber
[       OK ] FactorialTest.TestFactorialBigNumber
[ RUN      ] FactorialTest.TestFactorialEvenBiggerNumber
factorial_test.go:57:
Expected: greater than 0
Actual:   -8764578968847253504

[  FAILED  ] FactorialTest.TestFactorialEvenBiggerNumber
[----------] Finished with tests from FactorialTest
--- FAIL: TestOgletest (0.00s)
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/05_factorial_ogletest2 0.004s

9. Knihovna assertions

Třetí doplňkovou knihovnou určenou pro usnadnění psaní testů, s níž se v dnešním článku setkáme, 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. 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ě.

Mezi aserce, které lze použít v testech patří:

Funkce/aserce
ShouldAlmostEqual
ShouldBeBetween
ShouldBeBetweenOrEqual
ShouldBeBlank
ShouldBeChronological
ShouldBeEmpty
ShouldBeError
ShouldBeFalse
ShouldBeGreaterThan
ShouldBeGreaterThanOrEqualTo
ShouldBeIn
ShouldBeLessThan
ShouldBeLessThanOrEqualTo
ShouldBeNil
ShouldBeTrue
ShouldBeZeroValue
ShouldContain
ShouldContainKey
ShouldContainSubstring
ShouldEndWith
ShouldEqual
ShouldEqualJSON
ShouldEqualTrimSpace
ShouldEqualWithout
ShouldHappenAfter
ShouldHappenBefore
ShouldHappenBetween
ShouldHappenOnOrAfter
ShouldHappenOnOrBefore
ShouldHappenOnOrBetween
ShouldHappenWithin
ShouldHaveLength
ShouldHaveSameTypeAs
ShouldImplement
ShouldNotAlmostEqual
ShouldNotBeBetween
ShouldNotBeBetweenOrEqual
ShouldNotBeBlank
ShouldNotBeChronological
ShouldNotBeEmpty
ShouldNotBeIn
ShouldNotBeNil
ShouldNotBeZeroValue
ShouldNotContain
ShouldNotContainKey
ShouldNotContainSubstring
ShouldNotEndWith
ShouldNotEqual
ShouldNotHappenOnOrBetween
ShouldNotHappenWithin
ShouldNotHaveSameTypeAs
ShouldNotImplement
ShouldNotPanic
ShouldNotPanicWith
ShouldNotPointTo
ShouldNotResemble
ShouldNotStartWith
ShouldPanic
ShouldPanicWith
ShouldPointTo
ShouldResemble
ShouldStartWith

Význam většiny asercí by měl být zřejmý z jejich pojmenování.

10. Použití základních funkcí pro otestování platnosti předpokladů

V následujícím demonstračním příkladu je ukázáno použití některých asercí zmíněných v deváté kapitole, zejména asercí ShouldEqual, ShouldBeGreaterThan a ShouldBeBetween. Výsledky testů prozatím pouze vytiskneme na standardní výstup. Zdrojový kód s testy naleznete na adrese https://github.com/tisnik/go-root/blob/master/article18/06_fac­torial_assertions/factori­al_test.go:

package factorial_test
 
import (
        "factorial"
        "github.com/smartystreets/assertions"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        result := factorial.Factorial(0)
        println(assertions.ShouldEqual(result, 1))
}
 
func TestFactorialForOne(t *testing.T) {
        result := factorial.Factorial(1)
        println(assertions.ShouldEqual(result, 1))
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        result := factorial.Factorial(5)
        println(assertions.ShouldBeBetween(result, 10, 10000))
}
 
func TestFactorialForSmallNegative(t *testing.T) {
        result := factorial.Factorial(20)
        println(assertions.ShouldBeBetween(result, 10, 10000))
}
 
func TestFactorialForTen(t *testing.T) {
        result := factorial.Factorial(10)
        expected := int64(3628800)
        println(assertions.ShouldEqual(result, expected))
}
 
func TestFactorialForBigNumber(t *testing.T) {
        result := factorial.Factorial(20)
        println(assertions.ShouldBeGreaterThan(result, 0))
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        result := factorial.Factorial(30)
        println(assertions.ShouldBeGreaterThan(result, 0))
}

V tomto případě všechny testy pochopitelně projdou bez chyby, ovšem informace o všech nesplněných podmínkách se vypíšou na terminál:

=== RUN   TestFactorialForZero
 
--- PASS: TestFactorialForZero (0.00s)
=== RUN   TestFactorialForOne
 
--- PASS: TestFactorialForOne (0.00s)
=== RUN   TestFactorialForSmallNumber
 
--- PASS: TestFactorialForSmallNumber (0.00s)
=== RUN   TestFactorialForSmallNegative
Expected '2432902008176640000' to be between '10' and '10000' (but it wasn't)!
--- PASS: TestFactorialForSmallNegative (0.00s)
=== RUN   TestFactorialForTen
 
--- PASS: TestFactorialForTen (0.00s)
=== RUN   TestFactorialForBigNumber
 
--- PASS: TestFactorialForBigNumber (0.00s)
=== RUN   TestFactorialForEvenBiggerNumber
Expected '-8764578968847253504' to be greater than '0' (but it wasn't)!
--- PASS: TestFactorialForEvenBiggerNumber (0.00s)
PASS
ok      _/home/tester/go-root/article_18/06_factorial_assertions        0.005s

11. Kombinace knihoven assert a assertions

Knihovnu assertions, resp. přesněji řečeno funkce/aserce z této knihovny, můžeme snadno zkombinovat s další knihovnou nazvanou přímočaře a jednoduše assert. Tuto knihovnu nainstalujete příkazem:

$ go get github.com/mockupcode/gunit 

Prakticky jedinou veřejně dostupnou funkcí z této knihovny je funkce Equal, která se používá pro porovnání dvou hodnot libovolných typů, což je ostatně patrné i při pohledu na další demonstrační příklad:

package factorial_test
 
import (
        "factorial"
        "github.com/mockupcode/gunit/assert"
        "github.com/smartystreets/assertions"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(0)
        assert.Equal(assertions.ShouldEqual(result, 1), "")
}
 
func TestFactorialForOne(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(1)
        assert.Equal(assertions.ShouldEqual(result, 1), "")
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(5)
        assert.Equal(assertions.ShouldBeBetween(result, 10, 10000), "")
}
 
func TestFactorialForSmallNegative(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(20)
        assert.Equal(assertions.ShouldBeBetween(result, 10, 10000), "")
}
 
func TestFactorialForTen(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(10)
        expected := int64(3628800)
        assert.Equal(assertions.ShouldEqual(result, expected), "")
}
 
func TestFactorialForBigNumber(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(20)
        assert.Equal(assertions.ShouldBeGreaterThan(result, 0), "")
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        assert := assert.GetAssertion(t)
        result := factorial.Factorial(30)
        assert.Equal(assertions.ShouldBeGreaterThan(result, 0), "")
}

Nyní již budou některé testy korektně hlásit všechny nalezené problémy, a to automaticky generovanými a přitom snadno pochopitelnými chybovými zprávami:

=== RUN   TestFactorialForZero
--- PASS: TestFactorialForZero (0.00s)
=== RUN   TestFactorialForOne
--- PASS: TestFactorialForOne (0.00s)
=== RUN   TestFactorialForSmallNumber
--- PASS: TestFactorialForSmallNumber (0.00s)
=== RUN   TestFactorialForSmallNegative
--- FAIL: TestFactorialForSmallNegative
        factorial_test.go:33: Expected Expected '2432902008176640000' to be between '10' and '10000' (but it wasn't)! but found .
--- FAIL: TestFactorialForSmallNegative (0.00s)
=== RUN   TestFactorialForTen
--- PASS: TestFactorialForTen (0.00s)
=== RUN   TestFactorialForBigNumber
--- PASS: TestFactorialForBigNumber (0.00s)
=== RUN   TestFactorialForEvenBiggerNumber
--- FAIL: TestFactorialForEvenBiggerNumber
        factorial_test.go:52: Expected Expected '-8764578968847253504' to be greater than '0' (but it wasn't)! but found .
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/07_factorial_assertions2       0.006s

12. Zjednodušení zápisu testů použitím „tečkového“ importu

Opět si můžeme zápis testů zjednodušit použitím již několikrát zmíněného „tečkového“ importu, jak je to ukázáno v dalším demonstračním příkladu:

package factorial_test
 
import (
        "factorial"
        . "github.com/mockupcode/gunit/assert"
        . "github.com/smartystreets/assertions"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(0)
        assert.Equal(ShouldEqual(result, 1), "")
}
 
func TestFactorialForOne(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(1)
        assert.Equal(ShouldEqual(result, 1), "")
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(5)
        assert.Equal(ShouldBeBetween(result, 10, 10000), "")
}
 
func TestFactorialForSmallNegative(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(20)
        assert.Equal(ShouldBeBetween(result, 10, 10000), "")
}
 
func TestFactorialForTen(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(10)
        expected := int64(3628800)
        assert.Equal(ShouldEqual(result, expected), "")
}
 
func TestFactorialForBigNumber(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(20)
        assert.Equal(ShouldBeGreaterThan(result, 0), "")
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        assert := GetAssertion(t)
        result := factorial.Factorial(30)
        assert.Equal(ShouldBeGreaterThan(result, 0), "")
}

Výsledky běhu testů budou podle očekávání shodné s předchozími výsledky:

=== RUN   TestFactorialForZero
--- PASS: TestFactorialForZero (0.00s)
=== RUN   TestFactorialForOne
--- PASS: TestFactorialForOne (0.00s)
=== RUN   TestFactorialForSmallNumber
--- PASS: TestFactorialForSmallNumber (0.00s)
=== RUN   TestFactorialForSmallNegative
--- FAIL: TestFactorialForSmallNegative
        factorial_test.go:33: Expected Expected '2432902008176640000' to be between '10' and '10000' (but it wasn't)! but found .
--- FAIL: TestFactorialForSmallNegative (0.00s)
=== RUN   TestFactorialForTen
--- PASS: TestFactorialForTen (0.00s)
=== RUN   TestFactorialForBigNumber
--- PASS: TestFactorialForBigNumber (0.00s)
=== RUN   TestFactorialForEvenBiggerNumber
--- FAIL: TestFactorialForEvenBiggerNumber
        factorial_test.go:52: Expected Expected '-8764578968847253504' to be greater than '0' (but it wasn't)! but found .
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/08_factorial_assertions3       0.004s

13. Knihovna GoConvey

Již minule jsme se zmínili o nástroji pojmenovaném GoConvey, ovšem především z toho důvodu, abychom si představili jeho webové uživatelské rozhraní používané jak pro spouštění testů, tak i pro prezentaci jejich výsledků. Ovšem základem, na němž je nástroj GoConvey postaven, je knihovna určená pro psaní testů, které již většinou nemusejí mít tvar klasických jednotkových testů, ale spíše testů s popisem chování daného systému (behavio(u)r tests).

Poznámka: celou knihovnu budeme nazývat plným jménem GoConvery, i když se importovaný balíček bude jmenovat pouze convey.

Testy vytvořené s využitím této knihovny popisují očekávané chování systému (tedy většinou změnu jeho stavu), takže můžeme například psát:

convey.Convey("0! should be equal 1", t, func() {
        convey.So(Factorial(0), convey.ShouldEqual, 1)
})

Povšimněte si, že samotné otestování je v tomto případě většinou realizováno anonymní funkcí, která je předaná do funkce Convey.

14. Přepis jednotkových testů s použitím knihovny GoConvey

Podívejme se, jakým způsobem by bylo možné napsat jednotkové testy (tedy ne přímo BDD testy) s využitím knihovny GoConvey, přesněji řečeno balíčku convey. Povšimněte si způsobu zápisu operandů funkce So:

convey.So(Factorial(0), convey.ShouldEqual, 1)
convey.So(Factorial(5), convey.ShouldBeBetween, 1, 10000)
convey.So(Factorial(30), convey.ShouldBeGreaterThan, 0)

Zdrojový kód celého příkladu vypadá následovně:

package factorial
 
import (
        "github.com/smartystreets/goconvey/convey"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        convey.Convey("0! should be equal 1", t, func() {
                convey.So(Factorial(0), convey.ShouldEqual, 1)
        })
}
 
func TestFactorialForOne(t *testing.T) {
        convey.Convey("1! should be equal 1", t, func() {
                convey.So(Factorial(1), convey.ShouldEqual, 1)
        })
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        convey.Convey("5! should be between 1 and 10000", t, func() {
                convey.So(Factorial(5), convey.ShouldBeBetween, 1, 10000)
        })
}
 
func TestFactorialForSmallNumberNegative(t *testing.T) {
        convey.Convey("20! should be between 1 and 10000", t, func() {
                convey.So(Factorial(20), convey.ShouldBeBetween, 1, 10000)
        })
}
 
func TestFactorialForTen(t *testing.T) {
        convey.Convey("10! should be equal to 3628800", t, func() {
                convey.So(Factorial(10), convey.ShouldEqual, 3628800)
        })
}
 
func TestFactorialForBigNumber(t *testing.T) {
        convey.Convey("20! should be greater than zero", t, func() {
                convey.So(Factorial(20), convey.ShouldBeGreaterThan, 0)
        })
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        convey.Convey("30! should be greater than zero", t, func() {
                convey.So(Factorial(30), convey.ShouldBeGreaterThan, 0)
        })
}

15. Výsledky testů

Testy se v tomto případě spouští klasicky příkazem:

$ go test -v

Pokud máte terminál nakonfigurovaný takovým způsobem, aby zobrazovat většinu znaků Unicode, může výsledek vypadat takto:

=== RUN   TestFactorialForZero
 
  0! should be equal 1 ✔
 
 
1 total assertion
 
--- PASS: TestFactorialForZero (0.00s)
=== RUN   TestFactorialForOne
 
  1! should be equal 1 ✔
 
 
2 total assertions
 
--- PASS: TestFactorialForOne (0.00s)
=== RUN   TestFactorialForSmallNumber
 
  5! should be between 1 and 10000 ✔
 
 
3 total assertions
 
--- PASS: TestFactorialForSmallNumber (0.00s)
=== RUN   TestFactorialForSmallNumberNegative
 
  20! should be between 1 and 10000 ✘
 
 
Failures:
 
  * /home/tester/temp/go-root/article_18/09_factorial_convey/behaviour_test.go
  Line 28:
  Expected '2432902008176640000' to be between '1' and '10000' (but it wasn't)!
  goroutine 21 [running]:
  _/home/tester/temp/go-root/article_18/09_factorial_convey.TestFactorialForSmallNumberNegative.func1()
        /home/tester/temp/go-root/article_18/09_factorial_convey/behaviour_test.go:28 +0xe6
  github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)
        /home/tester/go/src/github.com/jtolds/gls/context.go:97 +0x3f2
  github.com/jtolds/gls.EnsureGoroutineId.func1()
        /home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0x2e
  github.com/jtolds/gls._m(0x0, 0xc00008a880)
        /home/tester/go/src/github.com/jtolds/gls/stack_tags.go:108 +0x31
  github.com/jtolds/gls.github_com_jtolds_gls_markS(0x0, 0xc00008a880)
        /home/tester/go/src/github.com/jtolds/gls/stack_tags.go:56 +0x35
  github.com/jtolds/gls.addStackTag(0x0, 0xc00008a880)
        /home/tester/go/src/github.com/jtolds/gls/stack_tags.go:49 +0x3a
  github.com/jtolds/gls.EnsureGoroutineId(0xc000078c00)
        /home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0xc3
  github.com/jtolds/gls.(*ContextManager).SetValues(0xc000074540, 0xc000078ba0, 0xc00008a840)
        /home/tester/go/src/github.com/jtolds/gls/context.go:63 +0x147
  _/home/tester/temp/go-root/article_18/09_factorial_convey.TestFactorialForSmallNumberNegative(0xc0000bc400)
        /home/tester/temp/go-root/article_18/09_factorial_convey/behaviour_test.go:27 +0x99
  testing.tRunner(0xc0000bc400, 0x5ac5f8)
        /opt/go/src/testing/testing.go:827 +0xbf
  created by testing.(*T).Run
        /opt/go/src/testing/testing.go:878 +0x353

 
 
4 total assertions
 
--- FAIL: TestFactorialForSmallNumberNegative (0.00s)
=== RUN   TestFactorialForTen
 
  10! should be equal to 3628800 ✔
 
 
5 total assertions
 
--- PASS: TestFactorialForTen (0.00s)
=== RUN   TestFactorialForBigNumber
 
  20! should be greater than zero ✔
 
 
6 total assertions
 
--- PASS: TestFactorialForBigNumber (0.00s)
=== RUN   TestFactorialForEvenBiggerNumber
 
  30! should be greater than zero ✘
 
 
Failures:
 
  * /home/tester/temp/go-root/article_18/09_factorial_convey/behaviour_test.go
  Line 46:
  Expected '-8764578968847253504' to be greater than '0' (but it wasn't)!
  goroutine 24 [running]:
  _/home/tester/temp/go-root/article_18/09_factorial_convey.TestFactorialForEvenBiggerNumber.func1()
        /home/tester/temp/go-root/article_18/09_factorial_convey/behaviour_test.go:46 +0xca
  github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)
        /home/tester/go/src/github.com/jtolds/gls/context.go:97 +0x3f2
  github.com/jtolds/gls.EnsureGoroutineId.func1()
        /home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0x2e
  github.com/jtolds/gls._m(0x0, 0xc00008b000)
        /home/tester/go/src/github.com/jtolds/gls/stack_tags.go:108 +0x31
  github.com/jtolds/gls.github_com_jtolds_gls_markS(0x0, 0xc00008b000)
        /home/tester/go/src/github.com/jtolds/gls/stack_tags.go:56 +0x35
  github.com/jtolds/gls.addStackTag(0x0, 0xc00008b000)
        /home/tester/go/src/github.com/jtolds/gls/stack_tags.go:49 +0x3a
  github.com/jtolds/gls.EnsureGoroutineId(0xc000079200)
        /home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0xc3
  github.com/jtolds/gls.(*ContextManager).SetValues(0xc000074540, 0xc0000791a0, 0xc00008afc0)
        /home/tester/go/src/github.com/jtolds/gls/context.go:63 +0x147
  _/home/tester/temp/go-root/article_18/09_factorial_convey.TestFactorialForEvenBiggerNumber(0xc0000bc800)
        /home/tester/temp/go-root/article_18/09_factorial_convey/behaviour_test.go:45 +0x99
  testing.tRunner(0xc0000bc800, 0x5ac5d0)
        /opt/go/src/testing/testing.go:827 +0xbf
  created by testing.(*T).Run
        /opt/go/src/testing/testing.go:878 +0x353

 
 
7 total assertions
 
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
FAIL
exit status 1
FAIL    _/home/tester/temp/go-root/article_18/09_factorial_convey       0.008s

Povšimněte si, že v případě nedodržení nějaké podmínky je vypsán celý stack trace.

Již bez zbytečného podrobnějšího vysvětlení si ukažme, jak vypadá úprava testů s využitím tečkového importu:

package factorial
 
import (
        . "github.com/smartystreets/goconvey/convey"
        "testing"
)
 
func TestFactorialForZero(t *testing.T) {
        Convey("0! should be equal 1", t, func() {
                So(Factorial(0), ShouldEqual, 1)
        })
}
 
func TestFactorialForOne(t *testing.T) {
        Convey("1! should be equal 1", t, func() {
                So(Factorial(1), ShouldEqual, 1)
        })
}
 
func TestFactorialForSmallNumber(t *testing.T) {
        Convey("5! should be between 1 and 10000", t, func() {
                So(Factorial(5), ShouldBeBetween, 1, 10000)
        })
}
 
func TestFactorialForSmallNumberNegative(t *testing.T) {
        Convey("20! should be between 1 and 10000", t, func() {
                So(Factorial(20), ShouldBeBetween, 1, 10000)
        })
}
 
func TestFactorialForTen(t *testing.T) {
        Convey("10! should be equal to 3628800", t, func() {
                So(Factorial(10), ShouldEqual, 3628800)
        })
}
 
func TestFactorialForBigNumber(t *testing.T) {
        Convey("20! should be greater than zero", t, func() {
                So(Factorial(20), ShouldBeGreaterThan, 0)
        })
}
 
func TestFactorialForEvenBiggerNumber(t *testing.T) {
        Convey("30! should be greater than zero", t, func() {
                So(Factorial(30), ShouldBeGreaterThan, 0)
        })
}

16. Alternativní formát výstupu

V případě potřeby dalšího zpracování výsledků testů můžeme nástroj GoConvey přepínačem -convey-json donutit k tomu, aby se výsledky posílané na standardní výstup či do výstupního souboru uložily ve formě JSONu. Pro každou testovací funkci se bude jednat o samostatný záznam, který je možné z výstupu relativně snadno vyříznout:

=== RUN   TestFactorialForZero
>->->OPEN-JSON->->->
{
  "Title": "0! should be equal 1",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 9,
  "Depth": 1,
  "Assertions": [
    {
      "File": "",
      "Line": 0,
      "Expected": "",
      "Actual": "",
      "Failure": "",
      "Error": null,
      "StackTrace": "",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- PASS: TestFactorialForZero (0.00s)
=== RUN   TestFactorialForOne
>->->OPEN-JSON->->->
{
  "Title": "1! should be equal 1",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 15,
  "Depth": 1,
  "Assertions": [
    {
      "File": "",
      "Line": 0,
      "Expected": "",
      "Actual": "",
      "Failure": "",
      "Error": null,
      "StackTrace": "",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- PASS: TestFactorialForOne (0.00s)
=== RUN   TestFactorialForSmallNumber
>->->OPEN-JSON->->->
{
  "Title": "5! should be between 1 and 10000",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 21,
  "Depth": 1,
  "Assertions": [
    {
      "File": "",
      "Line": 0,
      "Expected": "",
      "Actual": "",
      "Failure": "",
      "Error": null,
      "StackTrace": "",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- PASS: TestFactorialForSmallNumber (0.00s)
=== RUN   TestFactorialForSmallNumberNegative
>->->OPEN-JSON->->->
{
  "Title": "20! should be between 1 and 10000",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 27,
  "Depth": 1,
  "Assertions": [
    {
      "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
      "Line": 28,
      "Expected": "",
      "Actual": "",
      "Failure": "Expected '2432902008176640000' to be between '1' and '10000' (but it wasn't)!",
      "Error": null,
      "StackTrace": "goroutine 8 [running]:\n_/home/tester/go-root/article_18/09_factorial_convey.TestFactorialForSmallNumberNegative.func1()\n\t/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go:28 +0xe6\ngithub.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)\n\t/home/tester/go/src/github.com/jtolds/gls/context.go:97 +0x3f2\ngithub.com/jtolds/gls.EnsureGoroutineId.func1()\n\t/home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0x2e\ngithub.com/jtolds/gls._m(0x0, 0xc00000a8e0)\n\t/home/tester/go/src/github.com/jtolds/gls/stack_tags.go:108 +0x31\ngithub.com/jtolds/gls.github_com_jtolds_gls_markS(0x0, 0xc00000a8e0)\n\t/home/tester/go/src/github.com/jtolds/gls/stack_tags.go:56 +0x35\ngithub.com/jtolds/gls.addStackTag(0x0, 0xc00000a8e0)\n\t/home/tester/go/src/github.com/jtolds/gls/stack_tags.go:49 +0x3a\ngithub.com/jtolds/gls.EnsureGoroutineId(0xc00000ede0)\n\t/home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0xc3\ngithub.com/jtolds/gls.(*ContextManager).SetValues(0xc00004a550, 0xc00000ed80, 0xc00000a8a0)\n\t/home/tester/go/src/github.com/jtolds/gls/context.go:63 +0x147\n_/home/tester/go-root/article_18/09_factorial_convey.TestFactorialForSmallNumberNegative(0xc0000a6400)\n\t/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go:27 +0x99\ntesting.tRunner(0xc0000a6400, 0x5ac5f8)\n\t/opt/go/src/testing/testing.go:827 +0xbf\ncreated by testing.(*T).Run\n\t/opt/go/src/testing/testing.go:878 +0x353\n",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- FAIL: TestFactorialForSmallNumberNegative (0.00s)
=== RUN   TestFactorialForTen
>->->OPEN-JSON->->->
{
  "Title": "10! should be equal to 3628800",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 33,
  "Depth": 1,
  "Assertions": [
    {
      "File": "",
      "Line": 0,
      "Expected": "",
      "Actual": "",
      "Failure": "",
      "Error": null,
      "StackTrace": "",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- PASS: TestFactorialForTen (0.00s)
=== RUN   TestFactorialForBigNumber
>->->OPEN-JSON->->->
{
  "Title": "20! should be greater than zero",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 39,
  "Depth": 1,
  "Assertions": [
    {
      "File": "",
      "Line": 0,
      "Expected": "",
      "Actual": "",
      "Failure": "",
      "Error": null,
      "StackTrace": "",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- PASS: TestFactorialForBigNumber (0.00s)
=== RUN   TestFactorialForEvenBiggerNumber
>->->OPEN-JSON->->->
{
  "Title": "30! should be greater than zero",
  "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
  "Line": 45,
  "Depth": 1,
  "Assertions": [
    {
      "File": "/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go",
      "Line": 46,
      "Expected": "",
      "Actual": "",
      "Failure": "Expected '-8764578968847253504' to be greater than '0' (but it wasn't)!",
      "Error": null,
      "StackTrace": "goroutine 11 [running]:\n_/home/tester/go-root/article_18/09_factorial_convey.TestFactorialForEvenBiggerNumber.func1()\n\t/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go:46 +0xca\ngithub.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)\n\t/home/tester/go/src/github.com/jtolds/gls/context.go:97 +0x3f2\ngithub.com/jtolds/gls.EnsureGoroutineId.func1()\n\t/home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0x2e\ngithub.com/jtolds/gls._m(0x0, 0xc00000ae40)\n\t/home/tester/go/src/github.com/jtolds/gls/stack_tags.go:108 +0x31\ngithub.com/jtolds/gls.github_com_jtolds_gls_markS(0x0, 0xc00000ae40)\n\t/home/tester/go/src/github.com/jtolds/gls/stack_tags.go:56 +0x35\ngithub.com/jtolds/gls.addStackTag(0x0, 0xc00000ae40)\n\t/home/tester/go/src/github.com/jtolds/gls/stack_tags.go:49 +0x3a\ngithub.com/jtolds/gls.EnsureGoroutineId(0xc00000f350)\n\t/home/tester/go/src/github.com/jtolds/gls/gid.go:24 +0xc3\ngithub.com/jtolds/gls.(*ContextManager).SetValues(0xc00004a550, 0xc00000f2f0, 0xc00000ae00)\n\t/home/tester/go/src/github.com/jtolds/gls/context.go:63 +0x147\n_/home/tester/go-root/article_18/09_factorial_convey.TestFactorialForEvenBiggerNumber(0xc0000a6800)\n\t/home/tester/go-root/article_18/09_factorial_convey/behaviour_test.go:45 +0x99\ntesting.tRunner(0xc0000a6800, 0x5ac5d0)\n\t/opt/go/src/testing/testing.go:827 +0xbf\ncreated by testing.(*T).Run\n\t/opt/go/src/testing/testing.go:878 +0x353\n",
      "Skipped": false
    }
  ],
  "Output": ""
},
<-<-<-CLOSE-JSON<-<-<
--- FAIL: TestFactorialForEvenBiggerNumber (0.00s)
FAIL
exit status 1
FAIL    _/home/tester/go-root/article_18/09_factorial_convey    0.007s

17. Pořadí spouštění jednotlivých kroků definovaných v BDD testech

Při praktickém používání knihovny Convey je nutné si uvědomit, jakým způsobem se vlastně zajišťuje kontext (context), v němž jsou jednotlivé kroky jednotkových testů spouštěny. Pro vysvětlení této problematiky se podívejme na další (již poslední) příklad testů, v němž se pracuje se standardním řezem (slice). Tyto testy, jejichž zdrojový kód nalezneme na adrese https://github.com/tisnik/go-root/blob/master/article17/sli­ce_test/slice_test.go, tedy provádí testování vlastností základního datového typu programovacího jazyka Go:

package slices
 
import (
        "testing"
        . "github.com/smartystreets/goconvey/convey"
)
 
func TestSliceManipulation(t *testing.T) {
        Convey("Given an empty slice", t, func() {
                s := []int{}
 
                Convey("The slice should be empty initially", func() {
                        So(s, ShouldBeEmpty)
                })
 
                Convey("When an item is added", func() {
                        s = append(s, 1)
 
                        Convey("The slice should not be empty", func() {
                                So(s, ShouldNotBeEmpty)
                        })
                        Convey("The slice length should be one", func() {
                                So(len(s), ShouldEqual, 1)
                        })
                        Convey("And length should NOT be zero, of course", func() {
                                So(len(s), ShouldNotEqual, 0)
                        })
                        Convey("When another item is added", func() {
                                s = append(s, 2)

                                Convey("The slice length should be two", func() {
                                        So(len(s), ShouldEqual, 2)
                                })
                                Convey("And length should NOT be zero, of course", func() {
                                        So(len(s), ShouldNotEqual, 1)
                                })
                        })
 
                        Convey("Now the slice length should be one again", func() {
                                So(len(s), ShouldEqual, 1)
                        })
                })
                Convey("And now the slice should be empty again", func() {
                        So(s, ShouldBeEmpty)
                })
        })
}

Povšimněte si zejména těch řádků, na nichž se mění obsah řezu funkcí append (víme již, že řez je vlastně pouze „pohledem“ na pole, které slouží pro uložení jednotlivých prvků, toto pole je při některých operacích realokováno).

18. Výsledky BDD testů

Výsledky BDD testů nyní vypadají následovně:

=== RUN   TestSliceManipulation
 
  Given an empty slice
    The slice should be empty initially ✔
    When an item is added
      The slice should not be empty ✔
      The slice length should be one ✔
      And length should NOT be zero, of course ✔
      When another item is added
        The slice length should be two ✔
        And length should NOT be zero, of course ✔
      Now the slice length should be one again ✔
    And now the slice should be empty again ✔
 
 
8 total assertions

Testy v tomto případě běží (zdánlivě) paralelně, což znamená, že například poslední test stále vidí prázdný řez a nikoli řez, do něhož byly předchozími testy přidány dva prvky. Toto je velmi důležitá vlastnost celého systému GoConvey, která do značné míry zaručuje nezávislost testů (což samozřejmě neznamená, že například při práci se soubory dosáhneme ideální izolace testů).

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

Zdrojové kódy všech dnes popsaný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ě jeden megabajt), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Soubor Popis Cesta
1 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/01_fac­torial_testing/factorial.go
1 factorial_test.go jednotkové testy naprogramované s využitím balíčku testing https://github.com/tisnik/go-root/blob/master/article18/01_fac­torial_testing/factorial_tes­t.go
       
2 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/02_fac­torial_oglematchers/facto­rial.go
2 factorial_test.go jednotkové testy využívající knihovnu oglematchers https://github.com/tisnik/go-root/blob/master/article18/02_fac­torial_oglematchers/facto­rial_test.go
       
3 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/03_fac­torial_oglematchers2/facto­rial.go
3 factorial_test.go použití „tečkové“ notace při importu knihoven https://github.com/tisnik/go-root/blob/master/article18/03_fac­torial_oglematchers2/facto­rial_test.go
       
4 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/04_fac­torial_ogletest/factorial­.go
4 factorial_test.go jednotkové testy používající knihovny oglematchers a ogletest https://github.com/tisnik/go-root/blob/master/article18/04_fac­torial_ogletest/factorial_tes­t.go
       
5 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/05_fac­torial_ogletest2/factorial­.go
5 factorial_test.go použití „tečkové“ notace při importu knihoven https://github.com/tisnik/go-root/blob/master/article18/05_fac­torial_ogletest2/factorial_tes­t.go
       
6 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/06_fac­torial_assertions/factori­al.go
6 factorial_test.go použití knihovny assertions pro tisk podmínek v jednotkovém testu https://github.com/tisnik/go-root/blob/master/article18/06_fac­torial_assertions/factori­al_test.go
       
7 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/07_fac­torial_assertions2/factori­al.go
7 factorial_test.go použití knihovny assertions pro vytvoření jednotkových testů https://github.com/tisnik/go-root/blob/master/article18/07_fac­torial_assertions2/factori­al_test.go
       
8 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/08_fac­torial_assertions3/factori­al.go
8 factorial_test.go použití „tečkové“ notace při importu knihoven https://github.com/tisnik/go-root/blob/master/article18/08_fac­torial_assertions3/factori­al_test.go
       
9 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/09_fac­torial_convey/factorial.go
9 behaviour_test.go BDD test založený na knihovně GoConvey https://github.com/tisnik/go-root/blob/master/article18/09_fac­torial_convey/behaviour_tes­t.go
       
10 factorial.go implementace funkce pro výpočet faktoriálu https://github.com/tisnik/go-root/blob/master/article18/10_fac­torial_convey2/factorial.go
10 behaviour_test.go BDD test založený na knihovně GoConvey https://github.com/tisnik/go-root/blob/master/article18/10_fac­torial_convey2/behaviour_tes­t.go

20. Odkazy na Internetu

  1. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  2. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  3. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  4. Balíček ogletest
    https://github.com/jacobsa/ogletest
  5. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  6. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  7. Package testing
    https://golang.org/pkg/testing/
  8. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  9. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  10. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  11. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  12. GoConvey
    http://goconvey.co/
  13. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  14. 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
  15. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  16. package gg
    https://godoc.org/github.com/fo­gleman/gg
  17. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  18. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  19. The Go image package
    https://blog.golang.org/go-image-package
  20. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  21. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  22. YAML
    https://yaml.org/
  23. edn
    https://github.com/edn-format/edn
  24. Smile
    https://github.com/FasterXML/smile-format-specification
  25. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  26. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  27. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  28. Introducing JSON
    http://json.org/
  29. Package json
    https://golang.org/pkg/encoding/json/
  30. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  31. Go by Example: JSON
    https://gobyexample.com/json
  32. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  33. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  34. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  35. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  36. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  37. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  38. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  39. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  40. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  41. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  42. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  43. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  44. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  45. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  46. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  47. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  48. Algorithms to Go
    https://yourbasic.org/
  49. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  50. 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/
  51. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  52. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  53. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  54. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  55. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  56. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  57. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  58. The Go Programming Language (home page)
    https://golang.org/
  59. GoDoc
    https://godoc.org/
  60. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  61. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  62. The Go Programming Language Specification
    https://golang.org/ref/spec
  63. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  64. Package builtin
    https://golang.org/pkg/builtin/
  65. Package fmt
    https://golang.org/pkg/fmt/
  66. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  67. 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
  68. Learning Go
    https://www.miek.nl/go/
  69. Go Bootcamp
    http://www.golangbootcamp.com/
  70. 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
  71. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  72. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  73. The Go Blog
    https://blog.golang.org/
  74. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  75. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  76. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  77. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  78. 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
  79. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  80. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  81. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  82. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  83. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  84. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  85. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  86. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  87. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  88. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  89. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  90. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  91. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  92. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  93. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  94. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  95. 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/
  96. 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
  97. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  98. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  99. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  100. Go vs. Python
    https://www.peterbe.com/plog/govspy
  101. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  102. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  103. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  104. Go by Example: Slices
    https://gobyexample.com/slices
  105. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  106. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  107. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  108. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  109. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  110. nils In Go
    https://go101.org/article/nil.html
  111. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  112. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  113. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  114. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  115. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  116. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  117. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  118. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  119. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  120. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  121. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  122. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  123. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  124. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  125. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  126. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  127. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  128. 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
  129. 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
  130. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  131. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  132. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  133. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  134. Selectors
    https://golang.org/ref/spec#Selectors
  135. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  136. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  137. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  138. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  139. Part 21: Goroutines
    https://golangbot.com/goroutines/
  140. Part 22: Channels
    https://golangbot.com/channels/
  141. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  142. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  143. 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/
  144. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  145. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  146. Control Structures
    https://www.golang-book.com/books/intro/5
  147. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  148. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  149. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  150. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  151. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  152. 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/
  153. Effective Go
    https://golang.org/doc/ef­fective_go.html
  154. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  155. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation