Hlavní navigace

Pomůcky při tvorbě jednotkových testů v jazyce Go

Dnes se budeme zabývat dvěma souvisejícími tématy, které se obě týkají testování aplikací. Ukážeme si, jakým způsobem lze zachytávat zápisy do standardního výstupu a následně si popíšeme možnosti standardní knihovny httptest.
Pavel Tišnovský 16. 1. 2020
Doba čtení: 40 minut

Sdílet

11. Testování handlerů implementovaných v HTTP serveru

12. Jednoduchý HTTP server

13. Implementace testu handleru HTTP serveru

14. Pokrytí kódu HTTP serveru testy

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

16. Odkazy na Internetu

1. Testování aplikací zapisujících informace na standardní výstup

Při tvorbě testů pro ty aplikace, které zapisují informace na standardní (popř. na chybový) výstup se můžeme setkat s požadavkem, aby se zkontrolovalo, zda testovaná aplikace skutečně na standardní nebo chybový výstup zapsala očekávané zprávy. K této problematice je možné přistoupit několika způsoby. Jedno z možných (i když mnohdy ne ideálních) řešení spočívá v použití některé ze specializovaných knihoven, s nimiž jsme se již v tomto seriálu setkali. Jedná se především o knihovny s podobnými názvy go-expect, goexpect a gexpect, které byly popsány v následujících dvou článcích:

  1. Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem
    https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem/
  2. Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem (dokončení)
    https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem-dokonceni/

Jen pro úplnost si ukažme jeden příklad použití těchto knihoven (který jsme si již v tomto seriálu taktéž uvedli). Jedná se o krátký program, který spustí interpret programovacího jazyka Python, počká na inicializaci interpretru a nakonec nechá tímto interpretrem vyhodnotit několik výrazů, pochopitelně s testem, zda jsme získali očekávané výsledky. Nakonec je Python ukončen standardním způsobem:

func expectOutput(child *gexpect.ExpectSubprocess, output string) {
        err := child.Expect(output)
        if err != nil {
                log.Fatal(err)
        }
}
 
func expectPrompt(child *gexpect.ExpectSubprocess) {
        expectOutput(child, ">>> ")
}
 
func sendCommand(child *gexpect.ExpectSubprocess, command string) {
        err := child.SendLine(command)
        if err != nil {
                log.Fatal(err)
        }
}
 
func main() {
        child, err := gexpect.Spawn("python")
        if err != nil {
                log.Fatal(err)
        }
 
        expectPrompt(child)
        sendCommand(child, "1+2")
        expectOutput(child, "3")
 
        expectPrompt(child)
        sendCommand(child, "6*7")
        expectOutput(child, "42")
 
        expectPrompt(child)
        sendCommand(child, "quit()")
 
        child.Wait()
}

Výše zmíněné knihovny jsou však primárně určeny pro psaní testů, popř. automatizačních skriptů, které většinou s aplikací pracují jako s black boxem. Ovšem mnohdy potřebujeme zjistit, jaké informace se na výstup zapsaly přímo v jednotkových testech (unit tests). A právě v takových případech lze využít postupu, jenž je popsán a ukázán v navazujících kapitolách.

2. Princip zachycení standardního výstupu

Vzhledem k tomu, že všechny funkce ze standardní knihovny, které jsou určeny pro výpis informací na standardní výstup (jedná se o funkce z balíčku fmt), jsou skutečně implementovány jako běžné funkce a nikoli jako metody, je v tomto případě relativně obtížné při psaní jednotkových testů použít mockování (které se v programovacím jazyce Go provádí poněkud obtížněji, než například v Pythonu). Můžeme však namísto toho změnit obsah proměnné os.Stdout a nahradit ho jiným vhodným objektem, přesněji řečeno takovým objektem, který je odvozen od struktury os.File.

Ve zdrojových kódech balíčku os lze najít, jakého typu (a hodnoty) je proměnná os.Stdout a taktéž příbuzná proměnná os.Stderr:

var (
        Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
        Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
        Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
Poznámka: obsah této proměnné je možné změnit z toho důvodu, že se jedná o globálně viditelnou proměnnou – její název začíná velkým písmenem.

Jakmile se obsah této proměnné změní, například když do ní přiřadíme jiný otevřený soubor, budou všechny standardní funkce pro tisk (tedy funkce z balíčku fmt) používat nový cíl – odlišný soubor či strukturu, která chování souboru napodobuje. Je tomu tak z toho důvodu, že tyto funkce vypadají následovně:

func Print(a ...interface{}) (n int, err error) {
        return Fprint(os.Stdout, a...)
}
 
func Println(a ...interface{}) (n int, err error) {
        return Fprintln(os.Stdout, a...)
}
 
func Printf(format string, a ...interface{}) (n int, err error) {
        return Fprintf(os.Stdout, format, a...)
}

Ovšem řešení založené na tom, že se namísto standardního výstupu použije výstup do souboru, pochopitelně není ideální. Výhodnější by bylo, aby se tisk zprávy či zpráv provedl do k tomu alokované paměťové oblasti, tedy do nějakého bufferu, jehož obsah by bylo možné následně zkontrolovat a zjistit tak, jaké zprávy aplikace v dané (testované) funkci vytiskla. Právě pro tento účel lze použít strukturu, s níž jsme se již v tomto seriálu dobře seznámili – pipe:

$ go doc os.Pipe
 
package os // import "os"
 
func Pipe() (r *File, w *File, err error)
    Pipe returns a connected pair of Files; reads from r return bytes written to
    w. It returns the files and an error, if any.

Díky tomu, že do pipe může aplikace zapisovat (jakoby se jednalo o standardní výstup) a současně je možné data z druhé strany přečíst a zpracovat, může pipe posloužit právě ve funkci bufferu pro zapamatování zpráv, které aplikace zapsala na standardní výstup.

3. Postup při zachycení standardního výstupu

Celý postup při zachycování standardního výstupu může ve zkrácené podobě vypadat následovně:

  1. Zapamatujeme si původní obsah proměnné os.Stdout, protože ho budeme chtít po testování obnovit.
  2. Vytvoříme nový objekt typu Pipe, což mj. znamená, že získáme i implementace readeru a writeru (první dvě návratové hodnoty jsou typu *File).
  3. Do proměnné os.Stdout přiřadíme writer, tj. vstupní část objektu typu Pipe (část, do které se provádí zápis – tisk zpráv).
  4. V gorutině použijeme nějakou funkci, která přečte celý obsah pipe (použije výstupní část, tedy reader, s čekáním na dokončení zápisu) a převede ji na řetězec.
  5. Dále se zavolá testovaná funkce, která může provádět zápisy zpráv (tedy tisk) na standardní výstup.
  6. Na konci již pouze stačí pipe uzavřít (tím dojde k dokončení gorutiny, která svůj výsledek zapíše do kanálu), obnovit obsah proměnné os.Stdout a vrátit řetězec, který byl zachycen.

Vlastní implementace je ve skutečnosti nepatrně složitější, a to zejména kvůli tomu, že gorutina určená pro zpracování zpráv zapisovaných na standardní výstup musí komunikovat s původní gorutinou, ve které (mj.) běží i testovaná funkce. A nakonec je vhodné počkat na to, až se nově vytvořená gorutina spustí. K tomuto účelu lze použít například nějakou formu synchronizačního objektu.

4. Implementace jednotlivých bodů z popsaného postupu

Pomocná funkce, která vrátí řetězec vytisknutý na standardní výstup libovolnou předanou funkcí, může mít tuto hlavičku:

func CaptureStandardOutput(function func()) (string, error) {
}

Funkce tedy akceptuje libovolnou (typicky anonymní) testovanou funkci a na konci vrátí zachycený řetězec a popř. i objekt nesoucí informace o chybě, která v průběhu zachycování nastala.

Poznámka: omezení, že testovaná funkce je bez parametrů, je pouze zdánlivé, protože můžeme snadno vytvořit anonymní funkci bez parametrů, která zavolá testovanou funkci a předá ji všechny potřebné parametry.

V předchozí kapitole popsaný postup pro zachycení tisku na standardním výstupu, přesněji řečeno jeho jednotlivé body, lze realizovat například následujícím způsobem.

1. Zapamatování původního obsahu proměnné os.Stdout

Toto je velmi jednoduše implementovatelný bod. Postačuje nám do lokální proměnné uložit aktuální obsah proměnné os.Stdout, nezávisle na tom, zda se skutečně jedná o klasický standardní výstup, nebo o již dříve provedené přesměrování do souboru:

stdout := os.Stdout

2. Vytvoření objektu typu Pipe

Vytvoření objektu typu Pipe již známe, neboť jsme se tímto tématem již zabývali v předchozích částech tohoto seriálu. Pochopitelně nesmíme zapomenout na otestování chybového stavu, který (teoreticky) může nastat:

reader, writer, err := os.Pipe()
if err != nil {
        return "", err
}

Po provedení výše uvedeného bloku kódu máme k dispozici proměnné obsahující ukazatele na dvojici pseudosouborů – jeden je určený pro čtení, druhý pro zápis.

3. Nový obsah proměnné os.Stdout

Opět se jedná o snadno realizovatelný bod, neboť pouze nastavíme novou hodnotu proměnné os.Stdout a navíc zajistíme, aby se při ukončení celé funkce pro zachycení standardního výstupu obnovil původní obsah této proměnné. To je důležité, protože v opačném případě by přestaly pracovat všechny další tisky na standardní výstup (ovšem k chybě by nedošlo, a to i přesto, že se pipe uzavřela):

defer func() {
        os.Stdout = stdout
}()
os.Stdout = writer

4. Gorutina, která obsah Pipe přečte a převede na řetězec

V nejjednodušším případě tato asynchronně běžící gorutina přečte obsah Pipe, převede ho na řetězec a následně tento řetězec zapíše do kanálu, který je použit pro komunikaci s touto gorutinou:

captured := make(chan string)
go func() {
        var buf bytes.Buffer
        io.Copy(&buf, reader)
        captured <- buf.String()
}()

Korektnější je však počkat na to, až se gorutina skutečně spustí:

captured := make(chan string)
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
        var buf bytes.Buffer
        wg.Done()
        io.Copy(&buf, reader)
        captured <- buf.String()
}()
wg.Wait()

V tomto případě je použit synchronizační mechanismus představovaný objektem typu WaitGroup. V hlavní gorutině metodou Add nastavíme, že se má čekat na jedinou další gorutinu (předáme tedy hodnotu 1, kterou se inicializuje interní synchronizované počitadlo). V této gorutině (představované anonymní funkcí) zavoláme metodu Done, ovšem nikoli na konci gorutiny, ale na jejím začátku (ihned po její inicializaci). V hlavní gorutině poté metodou Wait čekáme na inicializaci asynchronně běžící gorutiny.

Poznámka: v dokumentaci go doc sync.WaitGroup je uveden odlišný příklad – čekání na dokončení gorutin, kdy je funkce wg.Done() na samotném konci těla gorutiny. Tento způsob použití nás ovšem nezajímá, protože pro tento účel používáme kanál captured. Pouze potřebujeme počkat na start gorutiny.

5. Zavolání testované funkce provádějící tisk na standardní výstup

Toto je jednoduchý bod – zavoláme jakoukoli funkci, která může (ale nutně nemusí) provádět tisk na standardní výstup:

function()

6. Obnovení obsahu proměnné os.Stdout, vrácení zachyceného řetězce

Po zavolání testované funkce uzavřeme Pipe a vrátíme obsah kanálu, do kterého zapsala řetězec (i prázdný) asynchronně běžící gorutina. Současně se čtení kanálu používá pro čekání na dokončení této gorutiny:

writer.Close()
return <-captured, nil

5. Úplný zdrojový kód funkce pro zachycení standardního výstupu

Úplný zdrojový kód balíčku s funkcí sloužící pro zachycení standardního výstupu může vypadat následovně:

package main
 
import (
        "bytes"
        "io"
        "os"
        "sync"
)
 
func CaptureStandardOutput(function func()) (string, error) {
        // backup of the real stdout
        stdout := os.Stdout
 
        // temporary replacement for stdout
        reader, writer, err := os.Pipe()
        if err != nil {
                return "", err
        }
 
        // temporarily replace real Stdout by the mocked one
        defer func() {
                os.Stdout = stdout
        }()
        os.Stdout = writer
 
        // channel with captured standard output
        captured := make(chan string)
        // synchronization object
        wg := new(sync.WaitGroup)
        // we are going to wait for one goroutine only
        wg.Add(1)
 
        go func() {
                var buf bytes.Buffer
                // goroutine is started -> inform main one via WaitGroup object
                wg.Done()
                io.Copy(&buf, reader)
                captured <- buf.String()
        }()
        // wait for goroutine to start
        wg.Wait()
        // provided function that (probably) prints something to standard output
        function()
        writer.Close()
        return <-captured, nil
}
Poznámka: popsaný zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article52/cap­ture01.go.
.

6. Otestování funkce pro zachycení standardního výstupu

To, zda výše popsaná funkce CaptureStandardOutput skutečně dokáže zachytit tisk na standardní výstup, lze relativně snadno ověřit.

Nejprve vyzkoušíme zachycení tisku provedeného funkcí fmt.Print:

func main() {
        str, err := CaptureStandardOutput(func() { fmt.Print("Hello world!") })
        if err != nil {
                panic(err)
        }
        fmt.Println("Captured output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledkem:

Captured output:
-------------------------------
Hello world!
-------------------------------

Vidíme, že se řetězec „Hello world!“ skutečně zachytil a je na standardní výstup vypsán až mnohem později (v reálných testech by se výpis neprováděl, pouze by se zjistilo, zda řetězec obsahuje potřebné informace).

Podobný příklad, ovšem zachycující tisk funkcí fmt.Println (s odřádkováním), je prakticky totožný s příkladem předchozím:

func main() {
        str, err := CaptureStandardOutput(func() { fmt.Println("Hello world!") })
        if err != nil {
                panic(err)
        }
        fmt.Println("Captured output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledkem:

Captured output:
-------------------------------
Hello world!
 
-------------------------------
Poznámka: povšimněte si přítomnosti dalšího znaku pro konec řádku.

Tabulka s vybranými hodnotami funkce sinus:

func printSinus() {
    epsilon := 1e-6
    for x := 0.0; x <= 2.0*math.Pi + epsilon; x+= math.Pi/6.0 {
        fmt.Printf("sin(%5.2f) = %+5.3f\n", x, math.Sin(x))
    }
}
 
func main() {
        str, err := CaptureStandardOutput(printSinus)
        if err != nil {
                panic(err)
        }
        fmt.Println("Captured output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledkem:

Captured output:
-------------------------------
sin( 0.00) = +0.000
sin( 0.52) = +0.500
sin( 1.05) = +0.866
sin( 1.57) = +1.000
sin( 2.09) = +0.866
sin( 2.62) = +0.500
sin( 3.14) = -0.000
sin( 3.67) = -0.500
sin( 4.19) = -0.866
sin( 4.71) = -1.000
sin( 5.24) = -0.866
sin( 5.76) = -0.500
sin( 6.28) = +0.000
 
-------------------------------
Poznámka: zachytit a otestovat je možné i velmi dlouhé tisky na standardní výstup.

Tisk do chybového výstupu funkcí println se ovšem nezachytí (což je ostatně korektní, tato základní funkce tiskne na chybový výstup a navíc obchází většinu funkcí z balíčku os):

func main() {
        str, err := CaptureStandardOutput(func() { println("Error output") })
        if err != nil {
                panic(err)
        }
         
        fmt.Println("Last line of captured output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledkem:

Error output
Captured output:
-------------------------------
 
-------------------------------

Alternativně lze tisk na chybový výstup provést explicitně:

func main() {
        str, err := CaptureStandardOutput(func() { fmt.Fprintln(os.Stderr, "Error output again") })
        if err != nil {
                panic(err)
        }
 
        fmt.Println("Last line of captured output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledkem:

Error output again
Captured output:
-------------------------------
 
-------------------------------

7. Zachycení tisku na standardní výstup v jednotkových testech

Jak jsme si již řekli v úvodní kapitole, je většinou zapotřebí zachytit tisk na standardní výstup v jednotkových testech. Se znalostmi, které jsme získali v předchozích kapitolách, je to ve skutečnosti velmi snadné. Ostatně se podívejme na následující příklad, v němž je nejdříve zopakována funkce pro zachycení výstupu (ovšem nikoli v balíčku main) a následně je pro tento balíček vytvořen jednoduchý jednotkový test:

package capture
 
import (
        "bytes"
        "io"
        "os"
        "sync"
)
 
func StandardOutput(function func()) (string, error) {
        // backup of the real stdout
        stdout := os.Stdout
 
        // temporary replacement for stdout
        reader, writer, err := os.Pipe()
        if err != nil {
                return "", err
        }
 
        // temporarily replace real Stdout by the mocked one
        defer func() {
                os.Stdout = stdout
        }()
        os.Stdout = writer
 
        // channel with captured standard output
        captured := make(chan string)
 
        // synchronization object
        wg := new(sync.WaitGroup)
        // we are going to wait for one goroutine only
        wg.Add(1)
 
        go func() {
                var buf bytes.Buffer
                // goroutine is started -> inform main one via WaitGroup object
                wg.Done()
                io.Copy(&buf, reader)
                captured <- buf.String()
        }()
        // wait for goroutine to start
        wg.Wait()
        // provided function that (probably) prints something to standard output
        function()
        writer.Close()
        return <-captured, nil
}

Jednotkový test pro výše vypsaný balíček by mohl vypadat následovně:

package capture_test
 
import (
        "fmt"
        "github.com/tisnik/go-capture"
        "os"
        "testing"
)
 
// TestNoOutput checks if empty standard output is captured properly
func TestNoOutput(t *testing.T) {
        captured, err := capture.StandardOutput(func() {
        })
        if err != nil {
                t.Fatal("Unable to capture standard output", err)
        }
        if captured != "" {
                t.Fatal("Standard should be empty")
        }
}
 
// TestEmptyOutput checks if empty standard output is captured properly
func TestEmptyOutput(t *testing.T) {
        captured, err := capture.StandardOutput(func() {
                fmt.Print("")
        })
        if err != nil {
                t.Fatal("Unable to capture standard output", err)
        }
        if captured != "" {
                t.Fatal("Standard should be empty")
        }
}
 
// TestOutputWithoutNewlines checks if standard output created by fmt.Print is captured properly
func TestOutputWithoutNewlines(t *testing.T) {
        captured, err := capture.StandardOutput(func() {
                fmt.Print("Hello!")
        })
        if err != nil {
                t.Fatal("Unable to capture standard output", err)
        }
        if captured != "Hello!" {
                t.Fatal("Incorrect output has been captured:", captured)
        }
}
 
// TestOutputWithNewlines checks if standard output created by fmt.Println is captured properly
func TestOutputWithNewlines(t *testing.T) {
        captured, err := capture.StandardOutput(func() {
                fmt.Println("Hello!")
        })
        if err != nil {
                t.Fatal("Unable to capture standard output", err)
        }
        if captured != "Hello!\n" {
                t.Fatal("Incorrect output has been captured:", captured)
        }
}
 
// TestOutputToStdErr checks whether output to stderr is captured or not
func TestOutputToStdErr(t *testing.T) {
        captured, err := capture.StandardOutput(func() {
                fmt.Fprint(os.Stderr, "Hello!")
        })
        if err != nil {
                t.Fatal("Unable to capture standard output", err)
        }
        if captured != "" {
                t.Fatal("Incorrect output has been captured:", captured)
        }
}
Poznámka: ve skutečnosti není pokrytí kódu stoprocentní, protože jsme neotestovali větev, ve které funkce os.Pipe vrátí chybu.

8. Zachycení tisku do chybového výstupu

Po přečtení předchozích kapitol vás pravděpodobně nepřekvapí, že chybový výstup, resp. přesněji řečeno tisk do chybového výstupu, se provede prakticky totožným programovým kódem, takže jen v krátkosti:

func CaptureErrorOutput(function func()) (string, error) {
        // backup of the real stderr
        stderr := os.Stderr
 
        // temporary replacement for stdout
        reader, writer, err := os.Pipe()
        if err != nil {
                return "", err
        }
 
        // temporarily replace real Stderr by the mocked one
        defer func() {
                os.Stderr = stderr
        }()
        os.Stderr = writer
 
        // channel with captured standard output
        captured := make(chan string)
        wg := new(sync.WaitGroup)
        wg.Add(1)
        go func() {
                var buf bytes.Buffer
                wg.Done()
                io.Copy(&buf, reader)
                captured <- buf.String()
        }()
        wg.Wait()
        // provided function that (probably) prints something to standard output
        function()
        writer.Close()
        return <-captured, nil
}

Otestování činnosti výše uvedené funkce:

func main() {
        str, err := CaptureErrorOutput(func() { fmt.Fprintln(os.Stderr, "Error output again") })
        if err != nil {
                panic(err)
        }
 
        fmt.Println("Captured error output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledky:

Captured error output:
-------------------------------
Error output again
 
-------------------------------
Poznámka: tisk na standardní výstup pochopitelně zachycen není, ovšem nyní již máte k dispozici všechny informace pro to, aby bylo možné vytvořit funkci zachycující zápisy do obou typů výstupů.

9. Zachycení tisku do logů

Nyní si vyzkoušejme, co se stane ve chvíli, kdy se pokusíme zachytit zápis do logu. Ve zdrojových kódech modulu log lze najít informaci o tom, do jakého výstupu se vlastně logy zapisují:

var std = New(os.Stderr, "", LstdFlags)

Ve skutečnosti ovšem nyní pouhá změna hodnoty os.Stderr není dostačující, protože si modul log drží vlastní referenci na chybový výstup. O tom se ostatně můžeme velmi snadno přesvědčit:

func main() {
        str, err := CaptureErrorOutput(func() { log.Print("log.Print") })
        if err != nil {
                panic(err)
        }
 
        fmt.Println("Captured standard output:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S tímto výsledkem:

2004/08/29 05:48:04 log.Print
Captured error output:
-------------------------------
 
-------------------------------

Vidíme, že se funkce log.Print zavolala zcela standardním způsobem, bez ohledu na to, že zachytáváme chybový výstup.

10. Implementace zachycení tisku do logů

Přímý přístup k proměnné log.std nemáme, musíme tedy použít nějaké jiné řešení. K dispozici je funkce log.SetOutput, které lze předat jiný objekt pro realizaci zápisu logu. Problém je zde vlastně jediný – neexistuje rozumná možnost, jak obnovit předchozí hodnotu, protože není k dispozici žádná funkce typu log.GetOutput. Částečné řešení (které obnoví zápis logů do chybového výstupu) může vypadat takto:

func CaptureLog(function func()) (string, error) {
        // temporary replacement for log output
        reader, writer, err := os.Pipe()
        if err != nil {
                return "", err
        }
 
        // temporarily replace real log output by the mocked one
        defer func() {
                log.SetOutput(os.Stderr)
        }()
        log.SetOutput(writer)
 
        // channel with captured standard output
        captured := make(chan string)
 
        // synchronization object
        wg := new(sync.WaitGroup)
        // we are going to wait for one goroutine only
        wg.Add(1)
 
        go func() {
                var buf bytes.Buffer
                // goroutine is started -> inform main one via WaitGroup object
                wg.Done()
                io.Copy(&buf, reader)
                captured <- buf.String()
        }()
        // wait for goroutine to start
        wg.Wait()
        // provided function that (probably) prints something to standard output
        function()
        writer.Close()
        return <-captured, nil
}

Otestování by nyní mělo být triviální:

func main() {
        str, err := CaptureLog(func() { log.Println("Hello world") })
        if err != nil {
                panic(err)
        }
 
        fmt.Println("Captured logs:")
        fmt.Println("-------------------------------")
        fmt.Println(str)
        fmt.Println("-------------------------------")
}

S výsledkem:

Captured logs:
-------------------------------
2020/01/15 20:13:31 Hello world
 
-------------------------------

11. Testování handlerů implementovaných v HTTP serveru

Ve druhé části článku se ve stručnosti zmíníme o dalším úkolu, který mnohdy čeká na autory jednotkových testů. Jedná se o nutnost otestování handlerů implementovaných v HTTP/HTTPS serveru (a není žádnou novinkou, že programovací jazyk Go se pro podobné aplikace velmi často používá). Jednotkové testy handlerů by měly do značné míry napodobit chování celého HTTP serveru, tj. mělo by být možné posílat požadavky (request) na mock HTTP serveru, zjišťovat, jaké informace se vrátily v odpovědi (response) atd. Pro tento účel se používá standardní knihovna net/http/httptest. Tato knihovna se skládá ze dvou částí – funkce NewRequest používané pro testování handlerů a datového typu Server, který (společně s příslušnými metodami) můžeme použít pro psaní (nejenom) jednotkových testů. Dnes se budeme zabývat především výše zmíněnou funkcí NewRequest.

Poznámka: pokud namísto jednotkových testů píšete testy funkcionální, je výhodnější použít například knihovny Goblin a Frisby.

12. Jednoduchý HTTP server

Nejprve si ukažme kód HTTP serveru, který budeme chtít testovat. Tento server po svém spuštění poskytuje statické soubory umístěné v aktuálním adresáři a na endpointech /data a /other odpovídá posláním odpovědi s nastaveným typem „application/json“. V obou případech je kód odpovědi 200 OK (a ve skutečnosti druhý handler nevrací validní JSON). Zdrojový kód tohoto HTTP serveru naleznete na adrese https://github.com/tisnik/go-root/blob/master/article52/httpSer­ver1.go

package main
 
import (
        "fmt"
        "log"
        "net/http"
)
 
func dataHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
        writer.WriteHeader(http.StatusOK)
        fmt.Fprintf(writer, `"x": [1, 2, 3, 4, 5]`)
}
 
func otherHandler(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
        writer.WriteHeader(http.StatusOK)
        fmt.Fprintf(writer, `foobar`)
}
 
func startHttpServer(address string) {
        log.Printf("Starting server on address %s", address)
        http.Handle("/", http.FileServer(http.Dir(".")))
        http.HandleFunc("/data", dataHandler)
        http.HandleFunc("/other", otherHandler)
        http.ListenAndServe(address, nil)
}
 
func main() {
        startHttpServer(":8080")
}

13. Implementace testu handleru HTTP serveru

Typickou úlohou je otestování funkcionality jednotlivých handlerů. Realizaci si ukážeme na testu pro handler obsluhující endpoint /data. Nejdříve vytvoříme objekt realizující dotaz provedený HTTP metodou GET:

request, err := http.NewRequest("GET", "/data", nil)
if err != nil {
        t.Fatal(err)
}

Dále vytvoříme objekt, který bude zaznamenávat provedené operace:

recorder := httptest.NewRecorder()

Třetím a posledním objektem je adaptér umožňující použít libovolnou funkci s příslušnou signaturou jako handler HTTP serveru:

handler := http.HandlerFunc(dataHandler)

Nyní spustíme „záznam“ činnosti HTTP serveru pro již dříve vytvořený dotaz (HTTP GET na endpointu /data):

handler.ServeHTTP(recorder, request)

Celý průběh se zaznamená, což znamená, že později můžeme činnost handleru prozkoumat čtením atributů struktury recorder.

Otestování HTTP kódu odpovědi (očekáváme 200 OK):

if status := recorder.Code; status != http.StatusOK {
        t.Errorf("improper status code: got %v instead of %v",
                status, http.StatusOK)
}

Otestování, zda odpověď obsahuje hlavičku „Content-Type“ s očekávaným obsahem „application/json“:

if ctype := recorder.Header().Get("Content-Type"); ctype != "application/json" {
        t.Errorf("content type header does not match: got %s want %s",
                ctype, "application/json")
}

A pochopitelně můžeme přistupovat i k datům poslaným v těle odpovědi:

body := recorder.Body.String()
if body != `"x": [1, 2, 3, 4, 5]` {
        t.Errorf("wrong response body: %s", body)
}

Úplný zdrojový kód jednotkového testu je umístěn na adrese: https://github.com/tisnik/go-root/blob/master/article52/httpSer­ver1_test.go

package main
 
import (
        "net/http"
        "net/http/httptest"
        "testing"
)
 
func TestDataHandler(t *testing.T) {
        request, err := http.NewRequest("GET", "/data", nil)
        if err != nil {
                t.Fatal(err)
        }
 
        recorder := httptest.NewRecorder()
        handler := http.HandlerFunc(dataHandler)
 
        handler.ServeHTTP(recorder, request)
 
        if status := recorder.Code; status != http.StatusOK {
                t.Errorf("improper status code: got %v instead of %v",
                        status, http.StatusOK)
        }
 
        body := recorder.Body.String()
        if body != `"x": [1, 2, 3, 4, 5]` {
                t.Errorf("wrong response body: %s", body)
        }
 
        if ctype := recorder.Header().Get("Content-Type"); ctype != "application/json" {
                t.Errorf("content type header does not match: got %s want %s",
                        ctype, "application/json")
        }
}
Poznámka: knihovna net/http/httptest nabízí i další možnosti, těm se ovšem budeme věnovat až příště.

14. Pokrytí kódu HTTP serveru testy

Jednotkové testy spustíme příkazem go test, ovšem navíc budeme specifikovat, že je nutné zjistit pokrytí kódu testy a uložit naměřená data do souboru nazvaného „coverage.out“:

$ go test -coverprofile coverage.out

Dále z vytvořeného souboru „coverage.out“ vytvoříme čitelný výpis s informacemi o tom, jaké funkce HTTP serveru byly skutečně otestovány:

$ go tool cover -func=coverage.out

Výsledek by mohl vypadat následovně (cesty se samozřejmě budou odlišovat):

MIF obecny

/home/tester/src/go/httpServer1.go:9:   dataHandler     100.0%
/home/tester/src/go/httpServer1.go:15:  otherHandler    0.0%
/home/tester/src/go/httpServer1.go:21:  startHttpServer 0.0%
/home/tester/src/go/httpServer1.go:29:  main            0.0%
total:                                  (statements)    25.0%

Vidíme, že handler realizovaný funkcí dataHandler je skutečně plně pokryt testy, na rozdíl od ostatního programového kódu.

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

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně pět až šest megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 capture01.go zachycení tisku na standardní výstup https://github.com/tisnik/go-root/blob/master/article52/cap­ture01.go
2 capture02.go alternativní příklad zachycení tisku na standardní výstup https://github.com/tisnik/go-root/blob/master/article52/cap­ture02.go
3 capture03.go delší standardní výstup https://github.com/tisnik/go-root/blob/master/article52/cap­ture03.go
4 capture04.go test, že chybový výstup není zachycen https://github.com/tisnik/go-root/blob/master/article52/cap­ture04.go
5 capture05.go test, že chybový výstup není zachycen https://github.com/tisnik/go-root/blob/master/article52/cap­ture05.go
6 capture06.go zachycení tisku do chybového výstupu https://github.com/tisnik/go-root/blob/master/article52/cap­ture06.go
7 capture07.go zachycení logování https://github.com/tisnik/go-root/blob/master/article52/cap­ture07.go
       
8 go-capture/capture.go implementace balíčku capture https://github.com/tisnik/go-root/blob/master/article52/go-capture/capture.go
9 go-capture/capture_test.go jednotkový test balíčku capture https://github.com/tisnik/go-root/blob/master/article52/go-capture/capture_test.go
       
10 httpServer1.go jednoduchý HTTP server https://github.com/tisnik/go-root/blob/master/article52/httpSer­ver1.go
11 httpServer1_test.go jednotkové testy pro HTTP server https://github.com/tisnik/go-root/blob/master/article52/httpSer­ver1_test.go

16. Odkazy na Internetu

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