デイリー日々

生活ライフ

自動テストと初期化 - (1)なぜ初期化が重要なのか?ストーリー分割の観点から

※続きものです。次のやつはコチラ、最後のやつはコチラ

はじめに

以前投稿した、登壇内容のセルフライナーノーツ記事で、「初期化大事だよ」って話をしました。今回はその、もうちょい詳細です。

blog.dashi-no-ajiwai.net

基本的にはUIベースのE2E的なテストの場合の話ですが、他のテスト(APIテストや単体テスト)にも適用できると思います。

あったらうれしい事前知識

そんなに難しい話にはならないつもりですが、「状態遷移図」「プロセスフローダイアグラム(PFD)」あたりご存知だとわかりやすいと思います。
それぞれ「各状態がイベントを介してつながっている図」「プロセスが成果物を介してフローでつながっている図」くらいの認識があればOKです。

自動テストにおける初期化・初期データとは

自動テストのパターンには、3Aと呼ばれるArrange - Act - Assertのパターンだったり、BDD的なGiven - When - Thenのパターンをよく見聞きすると思います。
どちらも、「被テスト状況を生み出す」というところから始まります。これがこの記事で指す初期化です。

一つ注意しておくと、「初期化」と「初期データ」は必ずしも同一ではありません。
データがない状態で画面から操作してデータを作成するのも「初期化」です。しかし、この方法はUIベースのE2E的なテストでは避けておくべきです。イタズラにFlakinessを増すだけです。
というわけで、ここから先は「初期化」は「初期データをうまく構成することで(初期化操作は最少にして)被テスト状況を生み出す」ことだと考えてください。

テストシナリオ(ストーリー)を分割する

あるアプリ内のカレンダーで、「設定をトグルして、日曜始まり・月曜始まりが変更できる」ということをテストすると、こうなります。

非常にアレだなあ、という感じですが、ただただ「人の操作をシナリオに起こしたもの」だと、こんな感じになりえますよね?

で、このままのシナリオだと、Flakinessが下げられません。Flakinessは確率的にステップ数乗で表されるので、基本的にステップ数が少ない方が有利です。「初期データをうまく構成して被テスト状況を生み出す」ことを前提に、シナリオを細かいストーリーに分割します。

この通り、初期化がうまくできれば、テストシナリオが細かいストーリーに切り出せて、Flakinessも下げられて、HAPPYですね!

「世界」?

ここで私個人の考え方なのですが、「世界」というものを用いています。
状態遷移図の各状態と捉えてもらってもいいですし、PFDの「成果物」を抽象化したものと思ってもらってかまいません。
もしくは群論とか圏論とか数学的な捉え方をしてもらってもいいと思います。

特にレガシーシステムにおいては、「どの操作をするとDBのどのテーブルのどのレコードの値が変わるかわからない」ということがあると思います。
それを諦めつつ初期データをうまく作成するために、「ある操作をした後のDBをダンプしておき、それを次のテストの初期状態とする」ことがあります。PFDの「成果物」よりもブラックボックスで曖昧なため、これを「世界」と表現している、と思ってください。

ということで「世界」はブラックボックスではありますが、ストーリーを細かくするにつれ、操作前後の「世界」の差分は小さくなっていくはずです。そうすると、「どの操作をするとDBのどのテーブルのどのレコードの値が変わるか」ということがわかってきます。これがわかると、レガシーシステムにおいてはかなり有益です。

「世界」と expect / assert

先ほど切り出した細かいストーリーについて、「世界」を起点に整理してみます。

日曜始まりの「世界」でカレンダーを開けば日曜始まりになっている、月曜始まりの「世界」でカレンダーを開けば月曜始まりになっている、はずです。
ということは、「日曜始まりの『世界』になっている」ことを確認できれば、「カレンダーが日曜始まりになっている」といえる、ということです。

これの嬉しさは、「画面の表示をアサーションしなくとも、DBのレコードを確認するなどすればOKとみなせる」ことにあります。
E2E的なテストの難しさは、「何をもってOKとみなすか」というところにあります。「〜という文言が表示されている」であれば明確ですが、上記の例のカレンダーのようなものであれば「th要素の1番目が〜」などと一気にしゃらくさくなります。明示的でないステップはFlakinessも上がりがちです。
「日曜始まりの『世界』になっているとき、カレンダーが日曜始まりになっている」ことを1つのテストシナリオで確認できれば、残りのテストシナリオでは「日曜始まりの『世界』になっている」ことを確認できれば良くなります。このようにできると、テストシナリオ群全体が一気に安定します。DBアサーションならFlakinessはありません。

さらにいうと、「世界」を起点とすると、細かいストーリーを結合して大きなストーリーを表現することができます。
もちろん、プロダクト自体が素直にステートレスに実装されていることが前提なんですけどね。何かそれまでの操作に依存して…のようなことになると、ここまでのストーリー分割の話は全く無意味になります。残念なことに、そういうのもなくはないです。

いかがでしたか?

表現したいことは状態遷移図・PFDと大差ないのですが、「初期化」にスコープしたいので「世界」という変な表現を持ち出しました。伝わるはず…と思っていますが、何かあればぜひマサカリを投げつけるなどしてください!

今回の記事はストーリー分割の話だったので、まだそこまで自動化っぽい話は出ていません。次からはもっと実装っぽい話になります。役に立てば幸いです〜。