- 投稿日:2020-05-28T20:12:36+09:00
【Go】Golandでテストから書く開発
前書き
- 私のPC & OS
- 今回のソースコードはこちら https://github.com/wataboru/golang-test-driven-dev
前準備
プロジェクト作る
- Golandにて、
File -> New -> Project...
ディレクトリ作成
今回はサンプルとして型変換処理を作成します。
以下の通りディレクトリを作成- goalng-test-driven-dev(root) ∟ pkg ∟ convert処理の定義を行う
後ほどテストを記述する際に、Golandが補完してくれて便利なので、先に定義してしまいましょう。
/pkg/convert
にGoファイルを作成。- 今回はstring型へ型変換処理を行う関数を作成
- 引数の型は自由とし、戻り値の型はstringとする。
- とりあえずコンパイルが通る様に無理やりstringを返却する。
convert.gopackage convert func ToString(v interface{}) (string) { return "" }テスト駆動で開発してみる
テスト書く
Golandはテストファイルの自動生成が便利です。
以下の通り利用しましょう
- テストしたいfuncやファイル内にフォーカスした状態で、
Cmd + Shift + T
Test for function
やTest for file
等、自動生成したい範囲を選択- 以下の通り自動生成されたら、
// TODO:
にケースを追記していく- 今回はとりあえず、期待値としてstring,bool,intを受け取る様にした。
convert_test.gopackage 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) } }) } }テストを実行する
テストを書いたらすぐに実行しましょう。
もちろん、まだ処理を書いていないのでエラーが発生するはずですね。処理を実装する
それでは、テストが通る様に実装をしていきましょう
テストの期待値を満たす様に、型別にstringへ変換してreturnする様にします。convert.gopackage 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 "" } }再実行する
追加実装する場合
基本的にはやることは変わりません。
1. テストに追記
2. テストFailedを確認
3. 処理を実装
4. テストの再実行
5. テストが通る様になるまで2~4を繰り返す例) エラーハンドリングを追加する
先ほどの関数に対して、以下の通り期待動作を足します。
string,int,bool以外の型がきた場合はエラーを返却する
テストに追記
convert_test.gopackage 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.gopackage 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を確認
エラーを発生させる処理のみテストが落ちています。
それ以外のケースについては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") } }テストの再実行
無事に通りましたね!
カバレッジをとってみる
ソフトウェア開発において、出来上がったプログラムのテストをする際に、どの程度をテスト対象とする(ことができる)かをカバレッジ(テストカバレッジ)という。
コード量が増えてくると、ちゃんとソース上の各ステップを網羅できてるか確認したくなりますよね。
こちらで実行をすると、以下の通り、カバー率がでます。
もし網羅できていない部分がある場合、ソースの該当箇所が赤くマーキングされて、テストを行っていない箇所が明確になります。
後書き
テストファイルの自動生成が便利ですね。
細かい単位でテストを行うことで、自然とテストが容易なコードを書ける様になり、疎結合なコードを書いていきたくなりますね。
- 投稿日:2020-05-28T08:38:53+09:00
【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) }
- 投稿日:2020-05-28T07:37:46+09:00
golang bulitinのprintとruntime
golangでCPU数やgoroutine数を知るためのruntimeを流してみた。ついでに、builtinのprintを使ってみた
go routineを呼んで、その数が変わることを確かめた。
test_runtime.gopackage 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:darwingoroutine(ゴルーチン)の終了条件は
関数の処理が終わる。
returnで抜ける。
runtime.Goexit() を実行するおまけ appleのこのOSって、darwinっていうんだ!
*wikiより
Darwin(ダーウィン)はアップルが開発するUnix系のPOSIX準拠オペレーティングシステム (OS) である。 技術的にはNEXTSTEPからOPENSTEPに続く流れを汲み、Mach 3.0+BSDをベースとし、一部の機能は他のBSD系OSからも取り入れている。
- 投稿日:2020-05-28T00:46:41+09:00
並列・並行処理(できるだけ簡単な方法)
こんにちは。
並列・並行処理の(できるだけ簡単な)方法を調べてみました。Unix
$ seq 10 | xargs -n 1 -P 8 exec_somethingGo
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()
- 投稿日:2020-05-28T00:46:41+09:00
並列・並行処理(できるだけ簡単な実現方法)
こんにちは。
並列・並行処理の実現方法を調べてみました。できるだけ簡単な方法が他にもあれば書き足したいです。Unix
$ seq 10 | xargs -n 1 -P 8 exec_somethingGo
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()