DEV Community

HyperRedStart
HyperRedStart

Posted on

Golang Unit Test

Golang Unit Test

使用 Go 進行單元測試

package main
import  "testing"
func  TestHello(t *testing.T) {
    got  :=  Hello()
    want  :=  "Hello, world"
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}
Enter fullscreen mode Exit fullscreen mode
package main
import  "fmt"
func  Hello() string {
    return  "Hello, world"
}
func  main() {
    fmt.Println(Hello())
}
Enter fullscreen mode Exit fullscreen mode
go test
PASS
ok      .../Golang_TDD 0.086s
Enter fullscreen mode Exit fullscreen mode

Array

TDD 模式 先寫測試在寫code

// 測試數字相加
func  TestSumAll(t *testing.T) {
    got  :=  SumAll([]int{1, 2}, []int{0, 9})
    want  := []int{3, 9}
    if  !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
}
// 測試去除第一數陣列後相加
func  TestSumAllTails(t *testing.T) {
    got  :=  SumAllTails([]int{1,2}, []int{0,9})
    want  := []int{2, 9}
    if  !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
}
Enter fullscreen mode Exit fullscreen mode
func  Sum(numbers []int) int {
    sum  :=  0
    for  _, number := range numbers {
        sum += number
    }
    return sum
}
func  SumAllTails(numbersToSum ...[]int)  []int  {
    var sums []int
    for  _, numbers := range numbersToSum {
         tail := numbers[1:]
         sums =  append(sums,  Sum(tail))
    }
    return sums
}
Enter fullscreen mode Exit fullscreen mode

製造錯誤

// 使用空陣列讓原有程式造成 panic
t.Run("safely sum empty slices", func(t *testing.T) {
    got  :=  SumAllTails([]int{}, []int{3, 4, 5})
    want  := []int{0, 9}
    if  !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
})
// error logs
panic: runtime error: slice bounds out of range [recovered]
panic: runtime error: slice bounds out of range
Enter fullscreen mode Exit fullscreen mode

修正錯誤

func  SumAllTails(numbersToSum ...[]int) []int {
    var  sums []int
    for  _, numbers  :=  range numbersToSum {
        // 增加判斷numbers 為空判斷
        if  len(numbers) ==  0 {
            sums  =  append(sums, 0)
        } else {
            tail  := numbers[1:]
            sums  =  append(sums, Sum(tail))
        }
    }
    return sums
}
Enter fullscreen mode Exit fullscreen mode

減少 test 重複 code

checkSums  :=  func(t *testing.T, got, want []int) {
    // 告知 testing 這是一個 helper function 當發生與預期不符時會指向呼叫 checksums的行數
    t.Helper()
    if  !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
}
Enter fullscreen mode Exit fullscreen mode

結構與介面

func  TestArea(t *testing.T) {
    // shape interface
    checkArea  :=  func(t *testing.T, shape Shape, want float64) {
        t.Helper()
        got  := shape.Area()
        if got != want {
            t.Errorf("got %.2f want %.2f", got, want)
        }
    }
    t.Run("rectangles", func(t *testing.T) {
        rectangle  := Rectangle{12, 6}
        // Rectangle struct have Area member function
        checkArea(t, rectangle, 72.0)
    })

    t.Run("circles", func(t *testing.T) {
        circle  := Circle{10}
        // Circle struct have Area member function
        checkArea(t, circle, 314.1592653589793)
    })
}
Enter fullscreen mode Exit fullscreen mode
// 介面
type  Shape  interface {
    Area() float64
}
// 結構
type  Rectangle  struct {
    Width float64
    Height float64
}
// 結構方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
type  Circle  struct {
    Radius float64
}
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}
Enter fullscreen mode Exit fullscreen mode

Table Driven Tests

func  TableDrivenTestsArea(t *testing.T) {
    // 批次測試
    areaTests  := []struct {
    shape Shape
    want float64
    }{
        {Rectangle{12, 6}, 72.0},
        {Circle{10}, 314.1592653589793},
    }

    for  _, tt  :=  range areaTests {
        got  := tt.shape.Area()
        if got != tt.want {
            t.Errorf("got %.2f want %.2f", got, tt.want)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

執行特定測試

func  TestTableDrivenArea(t *testing.T) {
    areaTests  := []struct {
        name string
        shape Shape
        hasArea float64
    }{
        {name: "Rectangle", shape: Rectangle{Width: 12, Height: 6}, hasArea: 72.0},
        {name: "Circle", shape: Circle{Radius: 10}, hasArea: 314.1592653589793},
    }
    for  _, tt  :=  range areaTests {
        t.Run(tt.name, func(t *testing.T) {
            got  := tt.shape.Area()
            if got != tt.hasArea {
                t.Errorf("%#v got %.2f want %.2f", tt.shape, got, tt.hasArea)
            }
        })
    }
}
go test -run TestTableDrivenArea/Rectangle
Enter fullscreen mode Exit fullscreen mode

Extends繼承

package main
import "fmt"

type Person struct {
    Id   int
    Name string
}
type Tester interface {
    Test()
    Eat()
}
func (this *Person) Test() {
    fmt.Println("\tthis =", &this, "Person.Test")
}
func (this *Person) Eat() {
    fmt.Println("\tthis =", &this, "Person.Eat")
}
// Employee從Person繼承,並直接繼承Eat方法,並且將Test方法覆蓋。
type Employee struct {
    Person
}
func (this *Employee) Test() {
    fmt.Println("\tthis =", &this, "Employee.Test")
    this.Person.Test() // 調用父類別方法
}
func main() {
    fmt.Println("An Employee instance :")
    var nu Employee
    nu.Id = 2
    nu.Name = "NTom"
    nu.Test()
    nu.Eat()
    fmt.Println()

    fmt.Println("A Tester interface to Employee instance :")
    var t Tester
    t = &nu
    t.Test()
    t.Eat()
    fmt.Println()

    fmt.Println("A Tester interface to Person instance :")
    t = &nu.Person
    t.Test()
    t.Eat()
}
Enter fullscreen mode Exit fullscreen mode

Pointer

以下範例以帳號內存取現金示範
使用 TDD 開發需先撰寫測試文件

func  assertNoError(t *testing.T, got error) {
    t.Helper()
    if got !=  nil {
        t.Fatal("got an error but didnt want one")
    }
}

func  assertBalance(t *testing.T, wallet Wallet, want Bitcoin) {
    t.Helper()
    got  := wallet.Balance()
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}

func assertError(t *testing.T, got error, want error) {
    t.Helper()
    if got ==  nil {
        t.Fatal("didn't get an error but wanted one")
    }
    if got != want {
        t.Errorf("got '%s', want '%s'", got, want)
    }
}

func  TestWallet(t *testing.T) {
    t.Run("Deposit", func(t *testing.T) {
        wallet  := Wallet{}
        // 使用 & 檢查 Wallet 記憶體位置是否一至
        fmt.Printf("address of Wallet in test is %p \n", &wallet)
        wallet.Deposit(Bitcoin(10))
        assertBalance(t, wallet, Bitcoin(10))
    })
    t.Run("Withdraw", func(t *testing.T) {
        wallet  := Wallet{Bitcoin(20)}
        err  := wallet.Withdraw(Bitcoin(10))
        assertBalance(t, wallet, Bitcoin(10))
        assertNoError(t, err)
    })
    t.Run("Withdraw insufficient funds", func(t *testing.T) {
        wallet  := Wallet{Bitcoin(20)}
        err  := wallet.Withdraw(Bitcoin(100))
        assertBalance(t, wallet, Bitcoin(20))
        assertError(t, err, ErrInsufficientFunds)
    })
}
Enter fullscreen mode Exit fullscreen mode
// 創建一個 error 發生
var  ErrInsufficientFunds  = errors.New("cannot withdraw, insufficient funds")
// 設定新 type 並指定為 int
type  Bitcoin  int
// Bitcoin to string 時回傳 %d BTC 結果
func (b Bitcoin) String() string {
    return fmt.Sprintf("%d BTC", b)
}

// 建構 Wallet struct 並擁有型別為 Bitcoin 的成員 balance 
type  Wallet  struct {
    balance Bitcoin
}
// 建立成員方法 (w *Wallet) *代表使用記憶體位置取得 Wallet struct
func (w *Wallet) Deposit(amount Bitcoin) {
    // w *Wallet 的pointer(*) w 已經代表記憶體位置本身,所已不需 &
    fmt.Printf("address of Wallet in Deposit is %p \n", w)
    w.balance += amount
}

func (w *Wallet) Withdraw(amount Bitcoin) error {
    // 檢查所領款的金額是否超過存款金額
    if amount > w.balance {
        // 如果超過回傳 error
        return ErrInsufficientFunds
    }
    w.balance -= amount
    return  nil
}

func (w *Wallet) Balance() Bitcoin {
    return w.balance
}
Enter fullscreen mode Exit fullscreen mode

Map

定義一個 map[KeyType]ValueType 的成員

// 建立 map 兩種方法
dictionary =  map[string]string{}
// OR
dictionary =  make(map[string]string)
Enter fullscreen mode Exit fullscreen mode

撰寫測試文件

func  TestSearch(t *testing.T) {
    dictionary  :=  map[string]string{"test": "this is just a test"}
    got  :=  Search(dictionary, "test")
    want  :=  "this is just a test"
    if got != want {
        t.Errorf("got '%s' want '%s' given, '%s'", got, want, "test")
    }
}
Enter fullscreen mode Exit fullscreen mode

根據測試文件撰寫符合規格的程式

func Search(dictionary map[string]string, word string) string {
    return dictionary[word]
}
Enter fullscreen mode Exit fullscreen mode

優化重構程式

func  TestSearchRefactor(t *testing.T) {
    dictionary  := Dictionary{"test": "this is just a test"}
    // 檢查 Dictionary記憶體位置是否一至,這邊不需用&取得記憶體位置,Map 本身就是 reference types
    fmt.Printf("address of Dictionary in test is %p \n", dictionary)
    t.Run("known word", func(t *testing.T) {
        got, _  := dictionary.Search("test")
        want  :=  "this is just a test"
        assertStrings(t, got, want)
    })

    t.Run("unknown word", func(t *testing.T) {
        _, err  := dictionary.Search("unknown")
        want  :=  "could not find the word you were looking for"
        if err ==  nil {
            t.Fatal("expected to get an error.")
        }
        assertStrings(t, err.Error(), want)
    })
}
Enter fullscreen mode Exit fullscreen mode
type Dictionary map[string]string
var ErrNotFound = errors.New("could not find the word you were looking for")
// 這邊的 Dictionary 物件成員並沒有使用 * 因為 map 本身就是 reference types 所以不需重新指向記憶體位置
func (d Dictionary) Search(word string) (string, error) {
    fmt.Printf("address of Dictionary in Search is %p \n", d)
    definition, ok := d[word]
    if !ok {
        return  "", ErrNotFound
    }
    return definition, nil
}
Enter fullscreen mode Exit fullscreen mode

新增新功能 Add

func  TestAdd(t *testing.T)  {
    dictionary := Dictionary{}
    dictionary.Add("test",  "this is just a test")
    want :=  "this is just a test"
    got, err := dictionary.Search("test")
    if err !=  nil  {
        t.Fatal("should find added word:", err)
    }
    if want != got {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}
Enter fullscreen mode Exit fullscreen mode

根據測試文件開發功能

func (d Dictionary)  Add(word, definition string)  {
     d[word]  = definition
}
Enter fullscreen mode Exit fullscreen mode

優化重構程式

func assertDefinition(t *testing.T, dictionary Dictionary, word, definition string)  {
    t.Helper()
    got, err := dictionary.Search(word)
    if err !=  nil  {
        t.Fatal("should find added word:", err)
    }
    if definition != got {
        t.Errorf("got '%s' want '%s'", got, definition)
    }
}

func TestAdd(t *testing.T)  {
    t.Run("new word",  func(t *testing.T)  {
        dictionary := Dictionary{}
        word :=  "test"
        definition :=  "this is just a test"
        err := dictionary.Add(word, definition)
        assertError(t, err,  nil)
        assertDefinition(t, dictionary, word, definition)
    })
    t.Run("existing word",  func(t *testing.T)  {
        word :=  "test"
        definition :=  "this is just a test"
        dictionary := Dictionary{word: definition}
        err := dictionary.Add(word,  "new test")
        assertError(t, err, ErrWordExists)
        assertDefinition(t, dictionary, word, definition)
    })
}
Enter fullscreen mode Exit fullscreen mode
const  (
 ErrNotFound =  DictionaryErr("could not find the word you were looking for")
 ErrWordExists =  DictionaryErr("cannot add word because it already exists")
)
type DictionaryErr string
func (e DictionaryErr) Error()  string  {
    return  string(e)
}

func (d Dictionary) Add(word, definition string) error {
    _, err := d.Search(word)
    switch err {
    case ErrNotFound:
        d[word]  = definition
    case  nil:
        return ErrWordExists
    default:
        return err
    }
    return  nil
}
Enter fullscreen mode Exit fullscreen mode

新增新功能Update

t.Run("existing word", func(t *testing.T) {
    word := "test"
    definition := "this is just a test"
    newDefinition := "new definition"
    dictionary := Dictionary{word: definition}
    err := dictionary.Update(word, newDefinition)
    assertError(t, err, nil)
    assertDefinition(t, dictionary, word, newDefinition)
})

t.Run("new word", func(t *testing.T) {
    word := "test"
    definition := "this is just a test"
    dictionary := Dictionary{}
    err := dictionary.Update(word, definition)
    assertError(t, err, ErrWordDoesNotExist)
})
Enter fullscreen mode Exit fullscreen mode
const (
    ErrNotFound         = DictionaryErr("could not find the word you were looking for")
    ErrWordExists       = DictionaryErr("cannot add word because it already exists")
    // new
    ErrWordDoesNotExist = DictionaryErr("cannot update word because it does not exist")
)
func (d Dictionary) Update(word, definition string) error {
    _, err := d.Search(word)
    switch err {
    case ErrNotFound:
        return ErrWordDoesNotExist
    case nil:
        d[word] = definition
    default:
        return err
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

新增新功能Delete

func TestDelete(t *testing.T) {
    word := "test"
    dictionary := Dictionary{word: "test definition"}
    dictionary.Delete(word)
    _, err := dictionary.Search(word)
    if err != ErrNotFound {
        t.Errorf("Expected '%s' to be deleted", word)
    }
}
Enter fullscreen mode Exit fullscreen mode
func (d Dictionary) Delete(word string) {
    // go lang 內建刪除功能
    delete(d, word)
}
Enter fullscreen mode Exit fullscreen mode

Dependency Injection

使用依賴性注入將會有已下優點

  • 不需要一個特定框架
  • 不會將你的code變得更加複雜
  • 更有助於程式測試
  • 您將會編寫出色的通用功能方法

讓我們回到最初的hello方法

func TestGreet(t *testing.T) {
    buffer := bytes.Buffer{}
    Greet(&buffer,"Chris")
    got := buffer.String()
    want := "Hello, Chris"
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}
Enter fullscreen mode Exit fullscreen mode
func Greet(writer *bytes.Buffer, name string)  {
    // fmt.Fprintf 就像是 fmt.Printf 一樣,但他用 writer 取代了預設的 stdout
    fmt.Fprintf(writer, "Hello, %s", name)
}
Enter fullscreen mode Exit fullscreen mode

讓我們看看 go 的 fmt 實現

// It returns the number of bytes written and any write error encountered.
func  Printf(format string, a ...interface{}) (n int, err error) {
    return  Fprintf(os.Stdout, format, a...)
}

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintf(format, a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

type Writer interface {
    Write(p []byte)  (n int, err error)
}
Enter fullscreen mode Exit fullscreen mode

我們嘗試使用 Greet function

// 失敗因為 os.Stdout 不是 *bytes.Buffer type
Greet(os.Stdout,  "Elodie")
Enter fullscreen mode Exit fullscreen mode

修正錯誤

// 我們使用 io.Writer 來解決寫入問題
func Greet(writer io.Writer, name string) {
    fmt.Fprintf(writer, "Hello, %s", name)
}
Enter fullscreen mode Exit fullscreen mode

現在已改寫完成了 Greet 方法,我們來嘗試用他來輸出至網頁上

package main

import (
    "fmt"
    "io"
    "net/http"
)

func Greet(writer io.Writer, name string) {
    fmt.Fprintf(writer, "Hello, %s", name)
}

func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
    Greet(w, "world")
}

func main() {
    http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler))
}
Enter fullscreen mode Exit fullscreen mode

Mocking

我們開發了一個程式,必須到數3秒代碼如下:

func TestCountdown(t *testing.T) {
    buffer := &bytes.Buffer{}
    Countdown(buffer)
    got := buffer.String()
    want := `3
2
1
Go!`
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
}
Enter fullscreen mode Exit fullscreen mode
const finalWord =  "Go!"
const countdownStart =  3

func Countdown(out io.Writer) {
    for i := countdownStart; i > 0; i-- {
        time.Sleep(1 * time.Second)
        fmt.Fprintln(out, i)
    }

    time.Sleep(1 * time.Second)
    fmt.Fprint(out, finalWord)
}
Enter fullscreen mode Exit fullscreen mode

不難發現當執行測試時,必須花費超過3秒的時間,想像當我們程式更加複雜後,必須要花多久時間執行測試呢?
所以以下我們改變了寫法 :

func  TestCountdownMock(t *testing.T) {
    buffer  :=  &bytes.Buffer{}
    // 我們創建了一個假的 sleep 方法
    spySleeper  :=  &SpySleeper{}
    CountdownMock(buffer, spySleeper)
    got  := buffer.String()
    want  :=  `3
    2
    1
    Go!`
    if got != want {
        t.Errorf("got '%s' want '%s'", got, want)
    }
    if spySleeper.Calls !=  4 {
        t.Errorf("not enough calls to sleeper, want 4 got %d", spySleeper.Calls)
    }
}
Enter fullscreen mode Exit fullscreen mode
type  Sleeper  interface {
    Sleep()
}
type  SpySleeper  struct {
    Calls int
}
func (s *SpySleeper) Sleep() {
    s.Calls++
}

type DefaultSleeper struct {}
func (d *DefaultSleeper) Sleep() {
    time.Sleep(1 * time.Second)
}

func CountdownMock(out io.Writer, sleeper Sleeper) {
    for  i  := countdownStart; i >  0; i-- {
        sleeper.Sleep()
        fmt.Fprintln(out, i)
    }
    sleeper.Sleep()
    fmt.Fprint(out, finalWord)
}

func main() {
    // 真實 sleep
    sleeper := &DefaultSleeper{}
    Countdown(os.Stdout, sleeper)
}
Enter fullscreen mode Exit fullscreen mode

通過以上代碼我們測試時明顯減少了等待時間,但是仍然有些問題我們還尚未測試,他是否是依照 先執行 sleep 後再執行 print 直到最後。

const write = "write"
const sleep = "sleep"

type CountdownOperationsSpy struct {
    // 字串陣列,記錄呼叫的方法
    Calls []string
}
// 記錄Sleep
func (s *CountdownOperationsSpy) Sleep() {
    s.Calls = append(s.Calls, sleep)
}
// 記錄Write
func (s *CountdownOperationsSpy) Write(p []byte) (n int, err error) {
    s.Calls = append(s.Calls, write)
    return
}
// 監控 Time 時間
type SpyTime struct {
    durationSlept time.Duration
}
// 記錄下睡眠時間
func (s *SpyTime) Sleep(duration time.Duration) {
    s.durationSlept = duration
}

func TestCountdown(t *testing.T) {
    // 測試輸出資料是否正確
    t.Run("prints 5 to Go!", func(t *testing.T) {
        buffer := &bytes.Buffer{}
        Countdown(buffer, &CountdownOperationsSpy{})
        got := buffer.String()
        want := `3
2
1
Go!`
        if got != want {
            t.Errorf("got '%s' want '%s'", got, want)
        }
    })
    // 測試執行 function 順序是否正確
    t.Run("sleep after every print", func(t *testing.T) {
        spySleepPrinter := &CountdownOperationsSpy{}
        Countdown(spySleepPrinter, spySleepPrinter)
        want := []string{
            sleep,
            write,
            sleep,
            write,
            sleep,
            write,
            sleep,
            write,
        }
        if !reflect.DeepEqual(want, spySleepPrinter.Calls) {
            t.Errorf("wanted calls %v got %v", want, spySleepPrinter.Calls)
        }
    })
}

func TestConfigurableSleeper(t *testing.T) {
    // 測試 ConfigurableSleeper 是否執行正確的睡眠時間
    sleepTime := 5 * time.Second
    spyTime := &SpyTime{}
    sleeper := ConfigurableSleeper{sleepTime, spyTime.Sleep}
    sleeper.Sleep()
    if spyTime.durationSlept != sleepTime {
        t.Errorf("should have slept for %v but slept for %v", sleepTime, spyTime.durationSlept)
    }
}
Enter fullscreen mode Exit fullscreen mode
// Sleeper 介面
type Sleeper interface {
    Sleep()
}

// ConfigurableSleeper 實做 Sleeper 且定義 delay 時間
type ConfigurableSleeper struct {
    duration time.Duration
    sleep    func(time.Duration)
}

// Sleep將會依照 duration 時間暫停程式執行
func (c *ConfigurableSleeper) Sleep() {
    c.sleep(c.duration)
}

const finalWord = "Go!"
const countdownStart = 3

// Countdown 將根據 delay 時間印出倒數
func Countdown(out io.Writer, sleeper Sleeper) {
    for i := countdownStart; i > 0; i-- {
        sleeper.Sleep()
        fmt.Fprintln(out, i)
    }
    sleeper.Sleep()
    fmt.Fprint(out, finalWord)
}

func main() {
    sleeper := &ConfigurableSleeper{1 * time.Second, time.Sleep}
    Countdown(os.Stdout, sleeper)
}
Enter fullscreen mode Exit fullscreen mode

Concurrency

我們以測試網站是否正常為例

func  mockWebsiteChecker(url string) bool {
    if url ==  "waat://furhurterwe.geds" {
        return  false
    }
    return  true
}
func  TestCheckWebsites(t *testing.T) {
    websites  := []string{
        "http://google.com",
        "http://blog.gypsydave5.com",
        "waat://furhurterwe.geds",
    }
    want  :=  map[string]bool{
        "http://google.com": true,
        "http://blog.gypsydave5.com": true,
        "waat://furhurterwe.geds": false,
    }
    got := CheckWebsites(mockWebsiteChecker, websites)
    if  !reflect.DeepEqual(want, got) {
        t.Fatalf("Wanted %v, got %v", want, got)
    }
}

func slowStubWebsiteChecker(_ string) bool {
    time.Sleep(20 * time.Millisecond)
    return true
}

func BenchmarkCheckWebsites(b *testing.B) {
    urls := make([]string, 100)
    for i := 0; i < len(urls); i++ {
        urls[i] = "a url"
    }

    for i := 0; i < b.N; i++ {
        CheckWebsites(slowStubWebsiteChecker, urls)
    }
}
Enter fullscreen mode Exit fullscreen mode
type WebsiteChecker func(string) bool
type result struct {
    string
    bool
}

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
    results := make(map[string]bool)
    resultChannel := make(chan result)

    for _, url := range urls {
        go func(u string) {
            resultChannel <- result{u, wc(u)}
        }(url)
    }

    for i := 0; i < len(urls); i++ {
        result := <-resultChannel
        results[result.string] = result.bool
    }

    return results
}
Enter fullscreen mode Exit fullscreen mode

Select

func TestRacer(t *testing.T) {
    t.Run("compares speeds of servers, returning the url of the fastest one", func(t *testing.T) {
        slowServer := makeDelayedServer(20 * time.Millisecond)
        fastServer := makeDelayedServer(0 * time.Millisecond)
        // defer 延遲動作,用於當需要執行完function後close物件
        defer slowServer.Close()
        defer fastServer.Close()

        slowURL := slowServer.URL
        fastURL := fastServer.URL

        want := fastURL
        got, err := Racer(slowURL, fastURL)
        if err != nil {
            t.Fatalf("did not expect an error but got one %v", err)
        }
        if got != want {
            t.Errorf("got '%s', want '%s'", got, want)
        }
    })

    t.Run("returns an error if a server doesn't respond within 10s", func(t *testing.T) {
        server := makeDelayedServer(25 * time.Millisecond)
        defer server.Close()
        _, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
        if err == nil {
            t.Error("expected an error but didn't get one")
        }
    })
}

func makeDelayedServer(delay time.Duration) *httptest.Server {
    return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(delay)
        w.WriteHeader(http.StatusOK)
    }))
}
Enter fullscreen mode Exit fullscreen mode
var tenSecondTimeout = 10 * time.Second
// 測試 a b 網站速度,超過 10s timeout
func Racer(a, b string) (winner string, error error) {
    return ConfigurableRacer(a, b, tenSecondTimeout)
}
// 比較 a b 網站 並回傳速度較快的一個
func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, error error) {
    // 使用 select 同時並發動作 
    select {
    case <-ping(a):
        return a, nil
    case <-ping(b):
        return b, nil
    case <-time.After(timeout):
        return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
    }
}
func ping(url string) chan bool {
    ch := make(chan bool)
    go func() {
        http.Get(url)
        ch <- true
    }()
    return ch
}
Enter fullscreen mode Exit fullscreen mode

Pointer Panic

type  Calc  interface {
    increment()
    decrement()
}

type  Math struct {
    count int
}

func (m *Math) increment() {
    println(m)
    m.count +=  1
    println("increment!", m.count)
}

func (m *Math) decrement() {
    println(&m)
    m.count -=  1
    println("decrement!", m.count)
}

func (m Math) decrement2() {
    m.count -=  1
    println("decrement!", m.count)
}

func  main() {
    // explodes -> math -> nil
    var  math  *math =  nil
    (*math).increment() // panic: value method main.math.decrement called using nil *math pointer
    var  explodes Explodes = math
    println(math, explodes) // '0x0 (0x10a7060,0x0)'
    if explodes !=  nil {
        println("Not nil!") // 'Not nil!'
        explodes.increment() // Normal
        explodes.decrement2() // panic: value method main.math.decrement called using nil *math pointer
    } else {
        println("nil!")
    }
    var  math1 math = math{}
    println(&math1)
    math1.increment()
    println(&math1)
    math1.increment()
    println(&math1)
    math1.increment()
    println(math1.count)

    var  math2  *math =  &math{}
    println(&math2)
    math2.increment()
    println(&math2)
    math2.increment()
    println(&math2)
    math2.increment()
    println(math2.count)
}
Enter fullscreen mode Exit fullscreen mode

對於 golang 並無try catch 的解釋
https://www.ithome.com.tw/voice/103455

Panic Recover

package main

import (
"fmt"
"os"
)

func  check(err error) {
    if err !=  nil {
        panic(err)
    }
}

func  main() {
    f, err  := os.Open("/tmp/dat")
    defer  func() {
        if  err  :=  recover(); err !=  nil {
            fmt.Println(err) // 這已經是頂層的 UI 介面了,想以自己的方式呈現錯誤
        }
        if f !=  nil {
            if  err  := f.Close(); err !=  nil {
                panic(err) // 示範再拋出 panic
            }
        }
    }()
    check(err)
    b1  :=  make([]byte, 5)
    n1, err  := f.Read(b1)
    check(err)
    fmt.Printf("%d bytes: %s\n", n1, string(b1))
}
Enter fullscreen mode Exit fullscreen mode

Summary

優點
使用 goroutines、channels 快速開發並型程序
Go 性能明顯優越於近代語言(除了 Rust)
標準內建的測試框架
有 Defer功能不會忘記關閉清除物件
缺點
沒有泛型
沒有 enum
數據結構只有 map,slice
擁有指標特性可能使開發者混亂

Reference

TDD 開發模式
CI/CD 使用
https://github.com/drone/drone
ORM Library 測試
https://github.com/mattn/go-oci8/tree/master/_example
https://www.jianshu.com/p/add47894c446
https://gocodecloud.com/blog/2016/08/09/accessing-an-oracle-db-in-go/
https://blog.csdn.net/notbaron/article/details/78168026
https://o7planning.org/en/10467/installing-c-cpp-compiler-mingw
https://github.com/wendal/go-oci8
https://www.oracle.com/technetwork/topics/winx64soft-089540.html
https://www.144d.com/post-490.html
https://www.cnblogs.com/ghj1976/p/3540257.html
https://medium.com/@kfoss/oracle-and-go-on-windows-64-bit-2b37496fe179
gRPC 使用
https://grpc.io/docs/quickstart/go.html#prerequisites
MicroServices 架構開發
https://yq.aliyun.com/articles/2764
https://lostechies.com/andrewsiemer/2016/01/11/testing-microservices/
https://blog.csdn.net/tengxing007/article/details/78289645
https://juejin.im/post/5a3257496fb9a044fc44c3d3
https://medium.com/seek-blog/microservices-in-go-2fc1570f6800
https://outcrawl.com/go-microservices-cqrs-docker
https://dzone.com/articles/go-microservices-blog-series-part-1
https://en.wikipedia.org/wiki/Software_architecture

Go library
https://github.com/avelino/awesome-go

Top comments (0)