そして、テスト自体がサポートするようにします

今日、私はテストを書くための異常なアプローチについてお話したいと思います。それは何とか静かにさまざまなサイズのいくつかのプロジェクトに取り組んでいて、何らかの理由で他の人とは純粋な形で会いませんでしたが、一般的には、表面にあります。 最近、Goでいくつかのコードを記述し始めました。テストの記述に関して疑問が生じるとすぐに、このアプローチを思い出しました。



テストは通常​​どのようなものですか?



非常に概略的に、各ユニットテストは通常​​、次の手順で構成されます。



  1. 入力データの初期化。
  2. ビジネスロジックの実行と結果の取得。
  3. 結果と標準との比較。


多くの場合、入力と出力はコード自体の中にあります。 コードの変更により出力に予想される変更が導入された場合、参照結果を手動で編集する必要があります。 場合によっては、テストのデータが膨大な場合、それらは別々のファイルに取り出されますが、参照データと比較ロジックのサポートは開発者の肩に残ります。



しかし、これはすべて統一することができます!



単体テストの本体では、一般に結果と標準との比較がないことを想像してください。 テスト自体が参照データを作成できると想像してください。 すべての入力データと出力データが構造化された形式であり、テストコードがよりコンパクトで統一され、読みやすくなることを想像してください。 提示?



アジェンダのテスト-テスト



私はこのアプローチをアジェンダテストと呼んでいます。なぜなら、私は略語が大好きだからです。実際、アジェンダは固有のデータです。 その本質は何ですか?



  1. テストの入力と出力はファイルに保存されます(JSONまたはその他-重要ではありません)。
  2. テストは2つのモードで機能します。



    • 初期化モード:テストは出力データを計算し、このデータを標準ファイルに保存します。



    • テストモード:テストは出力データを計算し、以前に保存された参照データを読み取り、それらを比較します。 データが異なる-テストは失敗します。


  3. データの読み取り、書き込み、比較などのすべての補助コードは、補助ライブラリ/関数/クラスに転送され、個々のテストではその本質のみが残ります。


それだけですか?..そしてそれだけです! Goの例でこれがどのように機能するかを見てみましょう。Goについては、 小さなライブラリを公​​開しており、他の言語に簡単に移植できます。



まず、「ビジネスロジック」ファイルを作成します。テストするコードは次のとおりです。



ファイルexample.go

package example import "errors" type Movie struct { TotalTime int `json:"total_time"` CurrentTime int `json:"current_time"` IsPlaying bool `json:"is_playing"` } func (m *Movie) Rewind() { m.CurrentTime = 0 } func (m *Movie) Play() error { if m.IsPlaying { return errors.New("Movie is already playing") } m.IsPlaying = true return nil }
      
      





次にテストを作成します。



ファイルexample_test.go

 package example import ( "encoding/json" "testing" "github.com/iafan/agenda" ) func TestMovie(t *testing.T) { agenda.Run(t, ".", func(path string, data []byte) ([]byte, error) { type MovieTestResult struct { M *Movie `json:"movie"` Err interface{} `json:"play_error"` } in := make([]*Movie, 0) //  data       , //      if err := json.Unmarshal(data, &in); err != nil { return nil, err } out := make([]*MovieTestResult, len(in)) for i, m := range in { // , "-"  //  Rewind()    m.Rewind() // Play()  nil   err := m.Play() //   ""  // 1)      Movie // 2)         out[i] = &MovieTestResult{m, agenda.SerializableError(err)} } //        //         return json.MarshalIndent(out, "", "\t") }) }
      
      





この行のアジェンダテストのすべての魔法:



 agenda.Run(t, ".", func(...){...}}
      
      





これは、現在のディレクトリ内のすべてのテストファイル(デフォルトでは、これらは.json拡張子を持つファイル)を取得し、それぞれに対してパラメーターとして渡された関数を実行します。



次に、テストデータを含むファイルを作成します。



ファイルtest_data.json

 [ {"total_time":100,"current_time":0,"is_playing":false}, {"total_time":150,"current_time":35,"is_playing":true}, {"total_time":95,"current_time":4,"is_playing":true}, {"total_time":125,"current_time":110,"is_playing":false} ]
      
      





初期化モードでテストを実行できます。

 $ go test -args init
      
      





同時に、参照データを含むファイルが入力ファイルの隣に作成されます。



ファイルtest_data.json.result

 [ { "movie": { "total_time": 100, "current_time": 0, "is_playing": true }, "play_error": null }, { "movie": { "total_time": 150, "current_time": 0, "is_playing": true }, "play_error": "Movie is already playing" }, { "movie": { "total_time": 95, "current_time": 0, "is_playing": true }, "play_error": "Movie is already playing" }, { "movie": { "total_time": 125, "current_time": 0, "is_playing": true }, "play_error": null } ]
      
      





このファイルを分析し、出力が期待どおりであることを確認する必要があります。 すべてが正常な場合、そのような生成されたファイルは、テストデータとともにリポジトリにコミットされます。



これで、通常モードでテストを実行できます。



 $ go test
      
      





もちろん、テストはエラーなしで合格するはずです。



ここで、プロジェクトの存続期間中にコードに変更を加えた場合、そのようなテストで作業するために2つのシナリオを使用します。





コードとテストデータの分離には、長所と短所の両方があります。



欠点は、コミットに存在するファイルの数が多いことです。 制限されたサイズの単純なデータを使用した単純な単体テストには表形式のテストがより適しています。



はるかに多くの利点があります:テスト(コードとデータの両方)の読みやすさ、特にテストされたデータの複雑な構造の場合、結果をチェックするときに何かを見落とす可能性が低く、コードを再コンパイルする必要なくテスターがテストデータを補充および検証する可能性があります。



All Articles