Obsah
1. Testování aplikací naprogramovaných v jazyce Go
2. Vytvoření a spuštění jednotkového testu
3. Otestování funkcí a metod, které nejsou viditelné mimo svůj balíček
4. Výsledky testů v případě nesplnění nějaké testované podmínky
5. Okamžité ukončení jednotkového testu v případě detekce chyby
6. Jednotkové testy řízené tabulkami
7. Výběr testů pro spuštění na základě specifikovaného vzorku
8. Výběr testů na základě štítku (tagu)
9. Zjištění, které části programového kódu jsou pokryty jednotkovými testy
11. Ukázka použití nástroje go-carpet
13. Použití webového uživatelského rozhraní nástroje GoConvey
15. Mockování funkcí a metod pro potřeby jednotkových testů
17. Vytvoření mocku funkce get_exchange_rate_from_url
18. Repositář s demonstračními příklady
1. Testování aplikací naprogramovaných v jazyce Go
Tvorba testů, ať již testů jednotkových, integračních, výkonnostních atd., je v současnosti prakticky nedílnou součástí vývoje nových informačních systémů. I z tohoto důvodu se v nabídce standardních nástrojů jazyka Go nachází i nástroj určený pro spouštění jednotkových testů (unit tests) s vyhodnocením jejich výsledků, zjištěním, která část zdrojových kódů je jednotkovými testy pokryta atd. V souvislosti s jednotkovými testy je mnohdy nutné určité části programu nahradit jejich zjednodušenými (umělými) variantami, které se nazývají mock. Nástroj či knihovnu pro mockování sice přímo v základní sadě nástrojů Go nenalezneme (lze ji doinstalovat), ovšem jak si ukážeme v navazujících kapitolách, je většinou možné si vystačit s možnostmi poskytovanými samotným programovacím jazykem (zejména se to týká využití rozhraní – interface).
Jak jsme si již řekli v úvodním odstavci, obsahuje standardní instalace programovacího jazyka Go i knihovnu určenou pro psaní jednotkových testů. Tato knihovna se jmenuje testing a základní informace o ní získáme stejným způsobem, jako je tomu v případě všech dalších knihoven či balíčků – příkazem go doc. Zde konkrétně následujícím způsobem:
$ go doc testing package testing // import "testing" Package testing provides support for automated testing of Go packages. It is intended to be used in concert with the ``go test'' command, which automates execution of any function of the form func TestXxx(*testing.T) where Xxx does not start with a lowercase letter. The function name serves to identify the test routine. ... ... ...
Samotná implementace jednotkových testů je představována běžnými funkcemi, jejichž jména začínají na Test a akceptují parametr typu *testing.T, tj. ukazatel na strukturu obsahující informace o kontextu, ve kterém jsou jednotlivé testy spouštěny:
type T struct { common // další struktura s informacemi o času spuštění testu atd. atd. isParallel bool context *testContext // For running tests and subtests. }
Důležité je, že existuje množství metod pro strukturu testing.T, které jsou použity právě při tvorbě jednotkových testů. Jedná se především o tyto metody:
# | Metoda | Stručný popis metody |
---|---|---|
1 | Error | provede se zalogování chyby a funkce s testem se označí příznakem „chyba“ |
2 | Fail | funkce s testem se označí příznakem „chyba“ |
3 | FailNow | dtto, ovšem současně se příslušná funkce i ukončí |
4 | Log | zalogování zprávy, typicky s informací o chybě |
5 | Fatal | odpovídá kombinaci volání funkcí Log+FailNow |
2. Vytvoření a spuštění jednotkového testu
Již v dokumentaci je zmíněno, jakým způsobem se mají jednotkové testy tvořit, ovšem jedná se o tak důležité téma, že se mu budeme věnovat podrobněji v první polovině dnešního článku. Nejprve se podívejme na zdrojový kód obsahující funkci nazvanou Add, kterou budeme chtít otestovat. Kód funkce Add i příslušné funkce main je uložen v souboru pojmenovaném „add.go“:
package main func Add(x int, y int) int { return x + y } func main() { println(Add(1, 2)) }
Jakým způsobem se napíše jednotkový test či jednotkové testy pro tuto funkci? Testy budou zapisovány do souboru pojmenovaného „add_test.go“, protože právě na základě řetězce „_test“ ve jménu souboru nástroje jazyka Go rozpoznávají, jestli se jedná o zdrojový kód, který má být součástí výsledné aplikace, či naopak o kód používaný pro testování.
Ukažme si tedy způsob naprogramování velmi jednoduchého jednotkového testu pro otestování funkcionality funkce Add. Použijeme přitom metodu , při jejímž zavolání se metodou Error zaregistruje, že test nebyl dokončen úspěšně:
package main import "testing" func TestAdd(t *testing.T) { result := Add(1, 2) if result != 3 { t.Error("1+2 should be 3, got ", result, "instead") } }
Pro spuštění jednotkových testů se nepoužívá příkaz go run, ale příkaz go test. Ten nalezne všechny soubory *_test.go v daném adresáři či podadresářích a pokusí se v něm spustit všechny funkce s implementací jednotkových testů:
$ go test PASS ok _/home/tester/go-root/article_17/tests01 0.005s
Lepší je však použít přepínač -v, aby se vypsaly podrobnější informace o spuštěných testech:
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/tester/go-root/article_17/tests01 0.004s
3. Otestování funkcí a metod, které nejsou viditelné mimo svůj balíček
V předchozím demonstračním příkladu jsme vytvořili jednotkový test pro funkci viditelnou mimo svůj balíček, protože její jméno začíná velkým písmenem. Můžeme si ovšem ověřit, že otestovat můžeme i interní funkce volatelné pouze v rámci svého balíčku. Podívejme se na zdrojový kód druhého příkladu. Ten se od předchozího příkladu odlišuje pouze změnou názvu funkce pro součet dvou celočíselných hodnot:
package main func add(x int, y int) int { return x + y } func main() { println(add(1, 2)) }
Jednotkový test pro funkci add bude vypadat prakticky stejně jako test pro funkci Add:
package main import "testing" func TestAdd(t *testing.T) { result := add(1, 2) if result != 3 { t.Error("1+2 should be 3, got ", result, "instead") } }
Po spuštění jednotkových testů zjistíme, že je funkce add prakticky bez problémů testovatelná:
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/tester/go-root/article_17/tests02 0.006s
4. Výsledky testů v případě nesplnění nějaké testované podmínky
Samozřejmě si můžeme vyzkoušet, jak se bude systém chovat v případě, že nějaká podmínka zapsaná v testech nebude splněna. Můžeme například naši testovanou funkci add nepatrně upravit takovým způsobem, aby dávala pro některé hodnoty špatné výsledky:
package main func add(x int, y int) int { return x - y } func main() { println(add(1, 2)) }
Jednotkové testy budou vypadat následovně. Povšimněte si, že jsme přidali nový test pro zjištění, jak se funkce add chová ve chvíli, kdy je druhý operand nulový:
package main import "testing" func TestAdd(t *testing.T) { result := add(1, 2) if result != 3 { t.Error("1+2 should be 3, got ", result, "instead") } result = add(10, 20) if result != 30 { t.Error("10+20 should be 30, got ", result, "instead") } } func TestAddZero(t *testing.T) { result := add(1, 0) if result != 1 { t.Error("1+0 should be 1, got ", result, "instead") } }
Jednotkové testy spustíme s přepínačem -v:
$ go test -v === RUN TestAdd --- FAIL: TestAdd (0.00s) add_test.go:8: 1+2 should be 3, got -1 instead add_test.go:13: 10+20 should be 30, got -10 instead === RUN TestAddZero --- PASS: TestAddZero (0.00s) FAIL exit status 1 FAIL _/home/tester/go-root/article_17/tests03 0.005s
5. Okamžité ukončení jednotkového testu v případě detekce chyby
V případě, že se má nějaký test (reprezentovaný jednou funkcí s názvem ve formátu TestXXX) ukončit ihned po detekci první chyby, je nutné namísto volání metody Error:
t.Error("...")
Zavolat buď dvojici metod Log+FailNow:
t.Log("...) t.FailNow()
Popř. (což je kratší) metodu Fatal:
t.Fatal("...)
Ukažme si nyní úpravu jednotkových testů takovým způsobem, aby se ihned po nalezení prvního špatného výsledku test ukončil. Zdrojový kód takto upravených testů najdete na adrese https://github.com/tisnik/go-root/blob/master/article17/tests04/add_test.go:
package main import "testing" func TestAdd(t *testing.T) { result := add(1, 2) if result != 3 { t.Log("1+2 should be 3, got ", result, "instead") t.FailNow() } result = add(10, 20) if result != 30 { t.Log("10+20 should be 30, got ", result, "instead") t.FailNow() } } func TestAddZero(t *testing.T) { result := add(1, 0) if result != 1 { t.Log("1+0 should be 1, got ", result, "instead") t.FailNow() } }
Nové chování testů je následující:
$ go test -v === RUN TestAdd --- FAIL: TestAdd (0.00s) add_test.go:8: 1+2 should be 3, got -1 instead === RUN TestAddZero --- PASS: TestAddZero (0.00s) FAIL exit status 1 FAIL _/home/tester/go-root/article_17/tests04 0.004s
6. Jednotkové testy řízené tabulkami
Velmi často se při psaní jednotkových testů v programovacím jazyku Go setkáme s tím, že se specifikuje sada vstupních dat pro testovanou funkci společně se sadou očekávaných hodnot. Tyto údaje můžeme reprezentovat různým způsobem, například je mít uložené v externích souborech (CSV, textové soubory, JSON atd. atd.), ovšem mnohdy si vystačíme s tím nejjednodušším a nejpřímějším řešením – zápisem dat ve formě pole. Typicky se jedná o pole struktur (záznamů), přičemž každý záznam obsahuje jak zmíněné vstupní hodnoty testované funkce, tak i její očekávaný výsledek či výsledky. V našem konkrétním případě budeme opět testovat funkci add, nyní ovšem již s explicitní specifikací přesného typu parametrů i jejich výsledků (předtím jsme se spokojili s použitím datového typu int, nyní použijeme konkrétní typ int32):
Zdrojový kód testovaného modulu:
package main func add(x int32, y int32) int32 { return x + y } func main() { println(add(1, 2)) }
Jednotkové testy používající tabulku se vstupními hodnotami i hodnotami očekávanými (výsledky) mohou v tom nejjednodušším případě vypadat takto:
package main import ( "fmt" "math" "testing" ) 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) } } }
Výsledky testů:
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/tester/go-root/article_17/tests05 0.005s
Samozřejmě se můžeme podívat na to, jak se změní výsledky jednotkových testů ve chvíli, kdy bude funkce add implementována nekorektně:
$ go test -v === RUN TestAdd --- FAIL: TestAdd (0.00s) add_test.go:32: 2 + 1 should be 3, got 1 instead add_test.go:32: 2 + -2 should be 0, got 4 instead add_test.go:32: 2147483647 + 1 should be -2147483648, got 2147483646 instead FAIL exit status 1 FAIL _/home/tester/go-root/article_17/tests06 0.004s
7. Výběr testů pro spuštění na základě specifikovaného vzorku
V této kapitole se seznámíme s jedním ze způsobů volby, které jednotkové testy mají být spuštěny. V některých situacích totiž potřebujeme, aby se spustila pouze určitá sada jednotkových testů. Příkladem může být rychlý test (někdy se zde dosti nepřesně používá termín smoke test) aplikace přímo z integrovaného vývojového prostředí, jenž může být díky rychlosti překladače jazyka Go spuštěn při každém uložení zdrojového souboru (stačí si v programátorském textovém editoru či v integrovaném vývojovém prostředí nastavit příslušnou událost, což je například v případě textového editoru Vim docela snadné s využitím události BufWritePost popř. dokonce BufLeave).
V takovém případě většinou nepotřebujeme spouštět všechny testy, ale jen jejich podmnožinu. Pokud při pojmenování funkcí s implementací jednotkových testů udržujeme nějakou konzistenci v pojmenování, je volba jen některých testů pro spuštění relativně snadná, protože můžeme použít příkaz:
$ go test -v -run vzorek/pattern
Nyní si ukažme, jak se tento příkaz použije v praxi. Jednotkové testy funkce add rozdělíme do několika samostatných funkcí – základní testy, testy se „zajímavými“ (krajními) hodnotami atd. Výsledek může vypadat takto:
package main import ( "fmt" "math" "testing" ) type AddTest struct { x int32 y int32 expected int32 } func checkAdd(t *testing.T, testInputs []AddTest) { for _, i := range testInputs { 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) } } } func TestAddBasicValues(t *testing.T) { var addTestInput = []AddTest{ {0, 0, 0}, {1, 0, 1}, {2, 0, 2}, {2, 1, 3}, } checkAdd(t, addTestInput) } func TestAddNegativeValues(t *testing.T) { var addTestInput = []AddTest{ {0, 0, 0}, {1, 0, 1}, {2, 0, 2}, {2, 1, 3}, {2, -2, 0}, } checkAdd(t, addTestInput) } func TestAddMinValues(t *testing.T) { var addTestInput = []AddTest{ {math.MinInt32, 0, math.MinInt32}, {math.MinInt32, 1, math.MinInt32 + 1}, } checkAdd(t, addTestInput) } func TestAddMaxValues(t *testing.T) { var addTestInput = []AddTest{ {math.MaxInt32, 0, math.MaxInt32}, {math.MaxInt32, 1, math.MinInt32}, {math.MaxInt32, math.MinInt32, -1}, } checkAdd(t, addTestInput) } func TestAddMinMaxValues(t *testing.T) { var addTestInput = []AddTest{ {math.MinInt32, 0, math.MinInt32}, {math.MinInt32, 1, math.MinInt32 + 1}, {math.MaxInt32, 0, math.MaxInt32}, {math.MaxInt32, 1, math.MinInt32}, {math.MaxInt32, math.MinInt32, -1}, } checkAdd(t, addTestInput) }
Spuštění všech testů se provede nám známým příkazem bez dalších modifikací:
$ go test -v === RUN TestAddBasicValues --- PASS: TestAddBasicValues (0.00s) === RUN TestAddNegativeValues --- PASS: TestAddNegativeValues (0.00s) === RUN TestAddMinValues --- PASS: TestAddMinValues (0.00s) === RUN TestAddMaxValues --- PASS: TestAddMaxValues (0.00s) === RUN TestAddMinMaxValues --- PASS: TestAddMinMaxValues (0.00s) PASS ok _/home/tester/go-root/article_17/tests07 0.005s
Spuštění těch testů, které v názvu používají slovo „Max“:
$ go test -v -run Max === RUN TestAddMaxValues --- PASS: TestAddMaxValues (0.00s) === RUN TestAddMinMaxValues --- PASS: TestAddMinMaxValues (0.00s) PASS ok _/home/tester/go-root/article_17/tests07 0.006s
Spuštění těch testů, které v názvu používají slovo „Min“:
$ go test -v -run Min === RUN TestAddMinValues --- PASS: TestAddMinValues (0.00s) === RUN TestAddMinMaxValues --- PASS: TestAddMinMaxValues (0.00s) PASS ok _/home/tester/go-root/article_17/tests07 0.004s
V případě, že použijeme vzorek neodpovídající žádnému testu, budeme o tom informování varovnou zprávou:
$ go test -v -run FooBar testing: warning: no tests to run PASS ok _/home/tester/go-root/article_17/tests07 0.005s
8. Výběr testů na základě štítku (tagu)
Existuje ovšem ještě další způsob určení, jaké testy se mají spustit. V tomto případě ovšem nedochází k volbě skupiny funkcí s implementacemi jednotkových testů, ale celých souborů, v nichž jsou jednotkové testy uloženy. Předchozí příklad si tedy upravíme následujícím způsobem:
- Zdrojový kód testované funkce bude uložen v souboru pojmenovaném „add.go“, tj. tak, jak jsme zvyklí.
- Základní testy budou uloženy v souboru „add_test.go“, tj. opět se použije standardní pojmenování.
- Ovšem navíc budou existovat i dva soubory „add_slow_test.go“ a „add_fast_test.go“ s dalšími (řekněme pomaleji a rychleji běžícími) testy.
Důležité je, že ve dvou posledně zmíněných souborech použijeme strukturovaný komentář, který je rozeznán a zpracováván překladačem programovacího jazyka Go:
// +build fast // +build slow atd.
Podívejme se nyní na obsahy jednotlivých souborů zmíněných v předchozím seznamu.
Soubor add_test.go:
package main import "testing" func TestAdd(t *testing.T) { result := add(1, 2) if result != 3 { t.Error("1+2 should be 3, got ", result, "instead") } }
Soubor add_fast_test.go:
// +build fast package main import ( "fmt" "testing" ) type AddTest struct { x int32 y int32 expected int32 } func checkAdd(t *testing.T, testInputs []AddTest) { for _, i := range testInputs { 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) } } } func TestAddBasicValues(t *testing.T) { var addTestInput = []AddTest{ {0, 0, 0}, {1, 0, 1}, {2, 0, 2}, {2, 1, 3}, } checkAdd(t, addTestInput) } func TestAddNegativeValues(t *testing.T) { var addTestInput = []AddTest{ {0, 0, 0}, {1, 0, 1}, {2, 0, 2}, {2, 1, 3}, {2, -2, 0}, } checkAdd(t, addTestInput) }
Soubor add_slow_test.go:
// +build slow package main import ( "fmt" "math" "testing" ) type AddTest struct { x int32 y int32 expected int32 } func checkAdd(t *testing.T, testInputs []AddTest) { for _, i := range testInputs { 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) } } } func TestAddMinValues(t *testing.T) { var addTestInput = []AddTest{ {math.MinInt32, 0, math.MinInt32}, {math.MinInt32, 1, math.MinInt32 + 1}, } checkAdd(t, addTestInput) } func TestAddMaxValues(t *testing.T) { var addTestInput = []AddTest{ {math.MaxInt32, 0, math.MaxInt32}, {math.MaxInt32, 1, math.MinInt32}, {math.MaxInt32, math.MinInt32, -1}, } checkAdd(t, addTestInput) } func TestAddMinMaxValues(t *testing.T) { var addTestInput = []AddTest{ {math.MinInt32, 0, math.MinInt32}, {math.MinInt32, 1, math.MinInt32 + 1}, {math.MaxInt32, 0, math.MaxInt32}, {math.MaxInt32, 1, math.MinInt32}, {math.MaxInt32, math.MinInt32, -1}, } checkAdd(t, addTestInput) }
Nyní si můžeme jednotlivé testy spustit. Povšimněte si použití přepínače -tags:
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/tester/go-root/article_17/tests08 0.003s
$ go test -v -tags fast === RUN TestAddBasicValues --- PASS: TestAddBasicValues (0.00s) === RUN TestAddNegativeValues --- PASS: TestAddNegativeValues (0.00s) === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/tester/go-root/article_17/tests08 0.004s
$ go test -v -tags slow === RUN TestAddMinValues --- PASS: TestAddMinValues (0.00s) === RUN TestAddMaxValues --- PASS: TestAddMaxValues (0.00s) === RUN TestAddMinMaxValues --- PASS: TestAddMinMaxValues (0.00s) === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/tester/go-root/article_17/tests08 0.004s
9. Zjištění, které části programového kódu jsou pokryty jednotkovými testy
Další problematikou, s níž se v dnešním článku seznámíme, je zjištění, jaké části zdrojového kódu jsou vůbec pokryty jednotkovými testy. To je velmi užitečná informace, která nám umožní soustředit se na efektivní psaní jednotkových testů, hledání různých „corner cases“ atd. Již v základní sadě nástrojů jazyka Go máme možnost si informace o pokrytí testy zjistit, takže si nejprve vytvořme funkci, kterou musíme otestovat. Bude se jednat o klasickou funkci určenou pro výpočet faktoriálu („školní“ rekurzivní varianta):
package factorial func Factorial(n int64) int64 { switch { case n < 0: return 1 case n == 0: return 1 default: return n * Factorial(n-1) } }
Jednotkové testy pro tuto funkci mohou vypadat například následovně:
package factorial_test import ( "factorial" "testing" ) func TestFactorialForZero(t *testing.T) { result := factorial.Factorial(0) if result != 1 { t.Errorf("Expected 0! == 1, but got %d instead", result) } } func TestFactorialForTen(t *testing.T) { result := factorial.Factorial(10) expected := int64(3628800) if result != expected { t.Errorf("Expected 0! == %d, but got %d instead", expected, result) } }
Samotné spuštění testů by nemělo skončit s chybou:
$ go test -v === RUN TestFactorialForZero --- PASS: TestFactorialForZero (0.00s) === RUN TestFactorialForTen --- PASS: TestFactorialForTen (0.00s) PASS ok _/home/tester/go-root/article_17/factorial 0.005s
Otázkou ovšem je, zda jsme skutečně napsali testy takovým způsobem, že pokryjí všechny větve zdrojového kódu. Proto spustíme jiný příkaz:
$ go test -v --cover === RUN TestFactorialForZero --- PASS: TestFactorialForZero (0.00s) === RUN TestFactorialForTen --- PASS: TestFactorialForTen (0.00s) PASS coverage: 75.0% of statements ok factorial 0.005s
Popř. si můžeme vygenerovat soubor s informacemi o pokrytí testy:
$ go test -v --coverprofile coverage
Výsledkem předchozího příkladu bude soubor obsahující informace o tom, kterými větvemi program prošel při svém testování. Obsah tohoto souboru by měl být přibližně následující:
mode: set factorial/factorial.go:3.31,4.9 1 1 factorial/factorial.go:5.13,6.11 1 0 factorial/factorial.go:7.14,8.11 1 1 factorial/factorial.go:9.10,10.28 1 1
Následně z tohoto souboru vygenerujeme HTML stránku s podrobnějšími informacemi o jednotlivých testovaných řádcích:
$ go tool cover -html=cover.out -o cover.html
10. Nástroj go-carpet
Získání informace o tom, kterými větvemi programu se prošlo při testování, je však možné ještě více polidštit. Zde si již ovšem nevystačíme se standardními nástroji jazyka Go, ale budeme muset použít externí pomocný nástroj. Ten se jmenuje go-carpet a instaluje se naprosto stejným způsobem, jako jakýkoli jiný balíček jazyka Go, tj. příkazem go get:
$ go get github.com/msoap/go-carpet
Po instalaci tohoto nástroje můžeme použít nový příkaz go-carpet, ovšem za předpokladu, že máte na PATH umístěnou i cestu „~/go/bin“ (o tom jsme se zmínili již v předchozím článku). Pokud není proměnná PATH nastavena, můžete výše uvedený příkaz volat s celou cestou z adresáře, v němž se nachází jednotkové testy:
$ ~/go/bin go-carpet
11. Ukázka použití nástroje go-carpet
Výsledek je v tomto případě odlišný, protože bude vypsán zdrojový kód testované aplikace se zvýrazněním těch funkcí větví, které byly pokryty jednotkovými testy. Kvůli tomu, že odlišení je ve výchozím nastavení provedeno odlišnou barvou, ukážeme si screenshot terminálu s výsledky:
Obrázek 1: Screenshot terminálu se zvýrazněním těch částí zdrojového kódu, které byly pokryty jednotkovými testy. Ze screenshotu je patrné, že pro jednu větev je nutné napsat další test.
12. Nástroj GoConvey
V závěru článku se – prozatím pouze v rychlosti – musíme zmínit o velmi užitečné aplikaci nazvané GoConvey. Tato aplikace primárně umožňuje spouštět jednotkové testy, a to jak z příkazové řádky, tak i z webového uživatelského rozhraní.
To ovšem není zdaleka vše, protože může sloužit i pro psaní BDD testů, ovšem nikoli s využitím doménově specifického jazyka Gherkin, ale „pouze“ s využitím samotného programovacího jazyka Go doplněného o několik funkcí. Této zajímavé a užitečné problematice se budeme věnovat v navazujících kapitolách. Jak je ve světě jazyka Go zvykem, je instalace této aplikace snadná, protože se opět využívá příkazu go get:
$ go get github.com/smartystreets/goconvey
13. Použití webového uživatelského rozhraní nástroje GoConvey
Podívejme se nyní na způsob použití webového uživatelského rozhraní tohoto nástroje. Příkaz goconvey je nutné spustit v adresáři s jednotkovými testy:
$ cd ~/go/src/factorial/ $ ~/go/bin/goconvey 2019/03/25 19:29:42 goconvey.go:63: Initial configuration: [host: 127.0.0.1] [port: 8080] [poll: 250ms] [cover: true] 2019/03/25 19:29:42 tester.go:19: Now configured to test 10 packages concurrently. 2019/03/25 19:29:42 goconvey.go:194: Serving HTTP at: http://127.0.0.1:8080 2019/03/25 19:29:42 goconvey.go:107: Launching browser on 127.0.0.1:8080 2019/03/25 19:29:42 integration.go:122: File system state modified, publishing current folders... 0 3106557094 2019/03/25 19:29:42 goconvey.go:120: Received request from watcher to execute tests... 2019/03/25 19:29:42 executor.go:69: Executor status: 'executing' 2019/03/25 19:29:42 coordinator.go:46: Executing concurrent tests: factorial 2019/03/25 19:29:42 goconvey.go:115: 2019/03/25 19:29:44 parser.go:24: [passed]: factorial 2019/03/25 19:29:44 executor.go:69: Executor status: 'idle'
Prakticky okamžitě by se v prohlížeči měla (automaticky) zobrazit webová stránka s rozhraním tohoto nástroje:
Obrázek 2: Webové rozhraní aplikace GoConvey.
Obrázek 3: Změna stylu webového rozhraní aplikace GoConvey.
Obrázek 4: Historie již spuštěných testů.
Obrázek 5: Výsledky v případě, že některé testy neproběhly korektně.
14. GoConvey a BDD testy
Při použití nástroje GoConvey je možné vytvářet i BDD testy, s nimiž jsme se seznámili v článku Behavior-driven development v Pythonu s využitím knihovny Behave. Ovšem v případě GoConvey se nepoužívá doménově specifický jazyk Gherkin – testy se zapisují přímo v jazyku Go, ovšem s použitím funkcí a metod z knihovny convey.
Použití BDD si opět ukážeme na implementaci výpočtu faktoriálu:
package factorial func Factorial(n int64) int64 { switch { case n < 0: return 1 case n == 0: return 1 default: return n * Factorial(n-1) } }
Samotné testy jsou uloženy v souboru behaviour_test.go:
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) }) }
Spuštění BDD testů je snadné, protože se použije nám již známý příkaz:
$ go test -v === RUN TestFactorial 0! should be equal 1 ✔ 1 total assertion --- PASS: TestFactorial (0.00s) === RUN TestFactorial2 10! should be greater than 1 ✔ 2 total assertions 10! should be between 1 and 10000000 ✔ 3 total assertions --- PASS: TestFactorial2 (0.00s) PASS ok _/home/tester/go-root/article_17/factorial_convey 0.006s
15. Mockování funkcí a metod pro potřeby jednotkových testů
Při testování aplikací, zejména při psaní jednotkových testů, se poměrně často dostaneme do situace, kdy potřebujeme nahradit nějakou funkci či metodu používanou v reálné aplikaci za „falešnou“ funkci resp. metodu vytvořenou pouze pro účely testů. V programovacím jazyku Go je možné pro tvorbu a použití takových „falešných“ funkcí použít hned několik různých knihoven, které se od sebe odlišují jak svými možnostmi, tak i způsobem zápisu či deklarace očekávaného chování testované aplikace. Ovšem pro lepší pochopení celé problematiky si dnes ukážeme, jak lze mockování (většinou) provést pouze s využitím základních možností programovacího jazyka Go, zejména s použitím rozhraní.
16. Převody měn
Testovat budeme aplikaci, která bude provádět převod měn na základě kurzovního lístku. Samotný převod měny je triviální (a ten právě budeme testovat). Vstupem do funkce exchange je částka ve výchozí měně (cokoli mimo Kč) a kód výchozí měny („GBP“, „USD“ apod.). Výsledkem je hodnota v cílové měně (což jsou vždy Kč):
func exchange(amount float64, code string) float64 { rate := get_exchange_rate(code) return rate * amount }
Pro získání kurzovního lístku můžeme použít následující funkci, která příslušný kurzovní lístek získá v čitelném a jednoduše parsovatelném textovém formátu (ostatně si to můžete vyzkoušet přímo v prohlížeči):
func get_exchange_rate(code string) float64 { const URL = "https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt" response, err := http.Get(URL) if err != nil { panic("Connection refused") } defer response.Body.Close() fmt.Printf("Status: %s\n", response.Status) fmt.Printf("Content length: %d\n", response.ContentLength) scanner := bufio.NewScanner(response.Body) for scanner.Scan() { s := strings.Split(scanner.Text(), "|") if len(s) == 5 { code_str := s[3] rate_str := strings.Replace(s[4], ",", ".", 1) if code == code_str { rate_f, err := strconv.ParseFloat(rate_str, 64) if err != nil { panic(err) } return rate_f } } } return 0 }
Kde je tedy problém? Ten spočívá v tom, že budeme chtít otestovat funkci exchange, ovšem izolovaně od aktuálního kurzovního lístku. Navíc funkce pro stažení a parsing lístku nemusí pracovat vždy správně (například může být CI schován za firewallem, nebudeme chtít přetěžovat cizí webové služby atd.). Proto musíme funkci get_exchange_rate mockovat funkcí jednodušší.
17. Vytvoření mocku funkce get_exchange_rate_from_url
Celý příklad budeme muset nepatrně upravit. Nejprve nadeklarujeme nový datový typ pro jakýkoli „getter“ kurzovního lístku. Jedná se vždy o funkci akceptující kód měny a vracející hodnotu na lístku (například pro „USD“ by se vrátila hodnota 21.10 popř. podobná okamžitě platná hodnota):
type ExchangeDataGetter func(code string) float64
Jakákoli funkce tohoto typu (funkce je plnohodnotnou hodnotou!) bude uložena v jednoduché struktuře, jejíž prvek bude pojmenován právě get_exchange_rate:
type ExchangeGetter struct { get_exchange_rate ExchangeDataGetter }
A nakonec nám zbývá vytvoření „konstruktoru“ vracejícího referenci na instanci předchozí struktury. To je nutné, protože ve skutečnosti budeme funkci volat jako metodu (viz další zdrojový text):
func NewExchangeGetter(g ExchangeDataGetter) *ExchangeGetter { return &ExchangeGetter{get_exchange_rate: g} }
Samotná funkce exchange se změní na metodu, protože jejím příjemcem bude ukazatel na strukturu ExchangeGetter:
func (g *ExchangeGetter) exchange(amount float64, code string) float64 { rate := g.get_exchange_rate(code) return rate * amount }
g := NewExchangeGetter(get_exchange_rate_from_url) fmt.Printf("%5.2f\n", g.exchange(10, "USD")) g2 := NewExchangeGetter(get_exchange_rate_from_file) fmt.Printf("%5.2f\n", g2.exchange(10, "USD")) g3 := NewExchangeGetter(mocked_get_exchange_rate) fmt.Printf("%5.2f\n", g2.exchange(10, "USD"))
Následně již můžeme nadeklarovat několik variant funkcí, které získají kód měny a vrátí hodnotu z kurzovního lístku (získaného buď z URL, ze souboru nebo se bude jednat o mock). Všechny tyto funkce mají stejný typ – ExchangeDataGetter:
func get_exchange_rate_from_url(code string) float64 { const URL = "https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt" response, err := http.Get(URL) if err != nil { panic("Connection refused") } defer response.Body.Close() fmt.Printf("Status: %s\n", response.Status) fmt.Printf("Content length: %d\n", response.ContentLength) scanner := bufio.NewScanner(response.Body) for scanner.Scan() { s := strings.Split(scanner.Text(), "|") if len(s) == 5 { code_str := s[3] rate_str := strings.Replace(s[4], ",", ".", 1) if code == code_str { rate_f, err := strconv.ParseFloat(rate_str, 64) if err != nil { panic(err) } return rate_f } } } return 0 } func get_exchange_rate_from_file(code string) float64 { const FILENAME = "kurzy.txt" file, err := os.Open(FILENAME) if err != nil { panic(err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { s := strings.Split(scanner.Text(), "|") if len(s) == 5 { code_str := s[3] rate_str := strings.Replace(s[4], ",", ".", 1) if code == code_str { rate_f, err := strconv.ParseFloat(rate_str, 64) if err != nil { panic(err) } return rate_f } } } return 0 } func mocked_get_exchange_rate(code string) float64 { return 21.5 }
Úplný kód tedy může vypadat následovně:
package main import ( "bufio" "fmt" "net/http" "os" "strconv" "strings" ) type ExchangeDataGetter func(code string) float64 type ExchangeGetter struct { get_exchange_rate ExchangeDataGetter } func NewExchangeGetter(g ExchangeDataGetter) *ExchangeGetter { return &ExchangeGetter{get_exchange_rate: g} } func get_exchange_rate_from_url(code string) float64 { const URL = "https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt" response, err := http.Get(URL) if err != nil { panic("Connection refused") } defer response.Body.Close() fmt.Printf("Status: %s\n", response.Status) fmt.Printf("Content length: %d\n", response.ContentLength) scanner := bufio.NewScanner(response.Body) for scanner.Scan() { s := strings.Split(scanner.Text(), "|") if len(s) == 5 { code_str := s[3] rate_str := strings.Replace(s[4], ",", ".", 1) if code == code_str { rate_f, err := strconv.ParseFloat(rate_str, 64) if err != nil { panic(err) } return rate_f } } } return 0 } func get_exchange_rate_from_file(code string) float64 { const FILENAME = "kurzy.txt" file, err := os.Open(FILENAME) if err != nil { panic(err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { s := strings.Split(scanner.Text(), "|") if len(s) == 5 { code_str := s[3] rate_str := strings.Replace(s[4], ",", ".", 1) if code == code_str { rate_f, err := strconv.ParseFloat(rate_str, 64) if err != nil { panic(err) } return rate_f } } } return 0 } func mocked_get_exchange_rate(code string) float64 { return 21.5 } func (g *ExchangeGetter) exchange(amount float64, code string) float64 { rate := g.get_exchange_rate(code) return rate * amount } func main() { g := NewExchangeGetter(get_exchange_rate_from_file) fmt.Printf("%5.2f\n", g.exchange(10, "USD")) g2 := NewExchangeGetter(mocked_get_exchange_rate) fmt.Printf("%5.2f\n", g2.exchange(10, "USD")) }
18. 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:
19. Odkazy na Internetu
- Package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation