20200528のGoに関する記事は5件です。

【Go】Golandでテストから書く開発

前書き

  • 私のPC & OS

image.png

前準備

プロジェクト作る

  • Golandにて、File -> New -> Project...

image.png

ディレクトリ作成

今回はサンプルとして型変換処理を作成します。
以下の通りディレクトリを作成

- goalng-test-driven-dev(root)
  ∟ pkg
    ∟ convert

処理の定義を行う

後ほどテストを記述する際に、Golandが補完してくれて便利なので、先に定義してしまいましょう。

  1. /pkg/convertにGoファイルを作成。
  2. 今回はstring型へ型変換処理を行う関数を作成
  3. 引数の型は自由とし、戻り値の型はstringとする。
  4. とりあえずコンパイルが通る様に無理やりstringを返却する。
convert.go
package convert

func ToString(v interface{}) (string) {
    return ""
}

テスト駆動で開発してみる

テスト書く

test.gif

Golandはテストファイルの自動生成が便利です。
以下の通り利用しましょう

  1. テストしたいfuncやファイル内にフォーカスした状態で、Cmd + Shift + T
  2. Test for functionTest for file等、自動生成したい範囲を選択
  3. 以下の通り自動生成されたら、// TODO:にケースを追記していく
  4. 今回はとりあえず、期待値としてstring,bool,intを受け取る様にした。
convert_test.go
package convert

import "testing"

func TestToString(t *testing.T) {
    type args struct {
        v interface{}
    }
    tests := []struct {
        name string
        args args
        want string
    }{
        {name: "String", args: args{v: "hoge"}, want: "hoge"},
        {name: "Int", args: args{v: 1},want: "1"},
        {name: "Bool", args: args{v: true}, want: "true"},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := ToString(tt.args.v); got != tt.want {
                t.Errorf("ToString() = %v, want %v", got, tt.want)
            }
        })
    }
}

テストを実行する

テストを書いたらすぐに実行しましょう。
もちろん、まだ処理を書いていないのでエラーが発生するはずですね。

  • convert_test.goを右クリックし、Run 'convert_test.go'を選択
    貼り付けた画像_2020_05_28_19_35.png

  • 結果を確認、ちゃんとエラーで落ちてますね!
    image.png

処理を実装する

それでは、テストが通る様に実装をしていきましょう
テストの期待値を満たす様に、型別にstringへ変換してreturnする様にします。

convert.go
package convert

import (
    "strconv"
)

func ToString(v interface{}) string {
    switch vt := v.(type) {
    case string:
        return vt
    case int:
        return strconv.Itoa(vt)
    case bool:
        return strconv.FormatBool(vt)
    default:
        return ""
    }

}

再実行する

全てのテストをPASSしました!
image.png

追加実装する場合

基本的にはやることは変わりません。
1. テストに追記
2. テストFailedを確認
3. 処理を実装
4. テストの再実行
5. テストが通る様になるまで2~4を繰り返す

例) エラーハンドリングを追加する

先ほどの関数に対して、以下の通り期待動作を足します。
string,int,bool以外の型がきた場合はエラーを返却する

テストに追記

convert_test.go
package convert

import "testing"

func TestToString(t *testing.T) {
    type args struct {
        v interface{}
    }
    tests := []struct {
        name string
        args args
        want string
        wantError bool // エラーを期待するか
    }{
        {name: "String", args: args{v: "hoge"}, want: "hoge", wantError: false},
        {name: "Int", args: args{v: 1},want: "1", wantError: false},
        {name: "Bool", args: args{v: true}, want: "true", wantError: false},
        {name: "Other", args: args{v: 123.0}, want: "", wantError: true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ToString(tt.args.v)
            // エラーを期待しているにもかかわらず発生しない
            if err == nil && tt.wantError {
                t.Error("No error occurred.")
                return
            }
            // エラーを期待していないにもかかわらず発生した
            if err != nil && !tt.wantError {
                t.Error("An error has occurred.")
                t.Error(err.Error())
                return
            }
            if got != tt.want {
                t.Errorf("ToString() = %v, want %v", got, tt.want)
            }
        })
    }
}

コンパイルを通すために最低限実装

関数の戻り値の数が変更されてしまっているため、コンパイルが落ちてしまいます。
テストを実行するため、最低限実装してあげましょう。

convert.go
package convert

import (
    "strconv"
)

func ToString(v interface{}) (string, error) { // errorを追加
    switch vt := v.(type) {
    case string:
        return vt, nil // とりあえずnilを返却
    case int:
        return strconv.Itoa(vt), nil
    case bool:
        return strconv.FormatBool(vt), nil
    default:
        return "", nil
    }


}

テストFailedを確認

image.png

エラーを発生させる処理のみテストが落ちています。
それ以外のケースについてはnilで合っているので通ってますね。

処理を実装

package convert

import (
    "errors"
    "strconv"
)

func ToString(v interface{}) (string, error) {
    switch vt := v.(type) {
    case string:
        return vt, nil
    case int:
        return strconv.Itoa(vt), nil
    case bool:
        return strconv.FormatBool(vt), nil
    default:
        return "", errors.New("Error")
    }

}

テストの再実行

image.png

無事に通りましたね!

カバレッジをとってみる

カバレッジ 【 coverage 】 カバー率

ソフトウェア開発において、出来上がったプログラムのテストをする際に、どの程度をテスト対象とする(ことができる)かをカバレッジ(テストカバレッジ)という。

コード量が増えてくると、ちゃんとソース上の各ステップを網羅できてるか確認したくなりますよね。

image.png

こちらで実行をすると、以下の通り、カバー率がでます。

image.png

もし網羅できていない部分がある場合、ソースの該当箇所が赤くマーキングされて、テストを行っていない箇所が明確になります。
image.png

後書き

テストファイルの自動生成が便利ですね。
細かい単位でテストを行うことで、自然とテストが容易なコードを書ける様になり、疎結合なコードを書いていきたくなりますね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】interface型

【Golang】interface型

Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。

package main 
//インターフェース
//指定したメソッドを保持して欲しい時にインターフェースを使う
//異なるstructで共通のメソッドを持たせたい。

/*interface用途

1、異なる構造体で共通のメソッドをまとめられる。

2、どんな型も入るが、関数内で計算などを処理する場合は型アサーションする必要がある。

3、カスタムエラー、Stringerを設定できる。
*/



//interface1の使い方
import (
    "fmt"
)

//interface 型みたいな感じ
//同じメソッドを持つstructをまとめる
type Human interface {
    //共通のメソッドを持つ
    Say() string
}

type Man struct {
    Name string
}

type Woman struct {
    Name string
}

type Dog struct {
    Name string
}


//名前を書き換える場合 アドレスで渡す必要がある
func (m *Man) Say() string {
    m.Name = "Mr." + m.Name
    fmt.Println(m.Name)
    return m.Name
}

//名前を書き換える場合 アドレスで渡す必要がある
func (w *Woman) Say() string {
    w.Name = "Mrs." + w.Name
    fmt.Println(w.Name)
    return w.Name
}

//interfaceがHumanの関数
//Sayを持つ型なので、実行できる
func Speak(h Human){
    h.Say()
}


func main() {
    //structを作成 Human
    //指定したメソッドを保持して欲しい時にインターフェースを使う
    //say()メソッドを持つ
    //変数名 intarface = struct
    //var mike Human = Person{"Mike"}
    //mike.Say()でもできる。ので正直メリットがわからない筆者。
    var mike Human = &Man{"Mike"}
    var nancy Human = &Woman{"Nancy"}
    Speak(mike)
    Speak(nancy)

    //Dogはintarfaceを持たない
    //Say()を持たないとエラーになる
    var dog Dog = Dog{"dog"}
    //Speak(dog)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golang bulitinのprintとruntime

golangでCPU数やgoroutine数を知るためのruntimeを流してみた。ついでに、builtinのprintを使ってみた

go routineを呼んで、その数が変わることを確かめた。

test_runtime.go
package main                                                                    

import (
    "runtime"
    "time"
)

func RuntimeSurvey() {
    print("NumCPU:", runtime.NumCPU(), "\n")
    print("NumGoroutine:", runtime.NumGoroutine(), "\n")
    print("Version:", runtime.Version(), "\n")
    print("OS:", runtime.GOOS, "\n")
}

func main() {
    go func() {
        time.Sleep(time.Second * 1)
    }() 
    print("Before sleep")
    RuntimeSurvey()

    time.Sleep(time.Second * 1)
    print("After sleep")
    RuntimeSurvey()
}

結果はこういうふうになります。goroutineの数が、2になって、そのあと1になってます。
処理が終わって待機中みたいになったら、数に含まれなくなるのかな。

$ go run test_runtime
Before sleepNumCPU:8
NumGoroutine:2
Version:go1.14.2
OS:darwin
After sleepNumCPU:8
NumGoroutine:1
Version:go1.14.2
OS:darwin

goroutine(ゴルーチン)の終了条件は

関数の処理が終わる。
returnで抜ける。
runtime.Goexit() を実行する

おまけ appleのこのOSって、darwinっていうんだ!

*wikiより
Darwin(ダーウィン)はアップルが開発するUnix系のPOSIX準拠オペレーティングシステム (OS) である。 技術的にはNEXTSTEPからOPENSTEPに続く流れを汲み、Mach 3.0+BSDをベースとし、一部の機能は他のBSD系OSからも取り入れている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

並列・並行処理(できるだけ簡単な方法)

こんにちは。
並列・並行処理の(できるだけ簡単な)方法を調べてみました。

Unix

$ seq 10 | xargs -n 1 -P 8 exec_something

Go

maxProc := 8
limiter := make(chan struct{}, maxProc)
items := []int{1,2,3,4,5,6,7,8,9,10}
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func(i int) {
        limiter <- struct{}{}
        defer wg.Done()
        exec_something(i)
        <-limiter
    }(item)
}
wg.Wait()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

並列・並行処理(できるだけ簡単な実現方法)

こんにちは。
並列・並行処理の実現方法を調べてみました。できるだけ簡単な方法が他にもあれば書き足したいです。

Unix

$ seq 10 | xargs -n 1 -P 8 exec_something

Go

maxProc := 8
limiter := make(chan struct{}, maxProc)
items := []int{1,2,3,4,5,6,7,8,9,10}
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func(i int) {
        limiter <- struct{}{}
        defer wg.Done()
        exec_something(i)
        <-limiter
    }(item)
}
wg.Wait()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む