Obsah
1. Knihovny určené pro tvorbu testů v programovacím jazyce Go
2. Krátké zopakování – použití standardního balíčku testing pro vytvoření jednotkového testu
3. Výsledek běhu standardních jednotkových testů
4. Když možnosti balíčku testing nedostačují…
5. Přepis jednotkových testů s využitím balíčku oglematchers
6. Zjednodušení testů použitím „tečkového“ importu
7. Kombinace balíčků ogletest a oglematchers
8. Třetí varianta jednotkových testů
10. Použití základních funkcí pro otestování platnosti předpokladů
11. Kombinace knihoven assert a assertions
12. Zjednodušení zápisu testů použitím „tečkového“ importu
14. Přepis jednotkových testů s použitím knihovny GoConvey
16. Alternativní formát výstupu
17. Pořadí spouštění jednotlivých kroků definovaných v BDD testech
19. Repositář s demonstračními příklady
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:
- 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).
- 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“).
- 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.“
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) } }
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
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:
- Pomocná knihovna oglematchers usnadňující specifikaci podmínek.
- Knihovna ogletest využívající zmíněnou knihovnu oglematchers.
- Jednoduše použitelná knihovna assertions, opět usnadňující specifikaci podmínek.
- Knihovna nazvaná assert, která doplňuje knihovnu assertions.
- 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/oglematchers. 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:
Matcher |
---|
AllOf |
AnyOf |
Any |
Contains |
DeepEquals |
ElementsAre |
Equals |
Error |
GreaterOrEqual |
GreaterThan |
HasSameTypeAs |
HasSubstr |
IdenticalTo |
LessOrEqual |
LessThan |
MatchesRegexp |
Not |
Panics |
Pointee |
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))) }
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_factorial_ogletest/factorial_test.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/smartystreets/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_factorial_assertions/factorial_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).
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/slice_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:
20. Odkazy na Internetu
- Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - Package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - 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