デイリー日々

生活ライフ

自動テストと初期化 - (3)初期データの勘所

※続きものです。前のやつはコチラ、最初の記事はコチラです!

はじめに

初期化がいかに大事なのか、2記事に渡って書いてきました。
ようやく、実際にどうするべきか?というところを書いていきます。

また「世界」の話が出ます。別のいい言い方が思い浮かばないので…ご容赦ください!

データの捉え方

ズバリ、ルートオブジェクトがあるかどうかによって並列性などの考え方が変わってきます。

ルートオブジェクトあり

一つルートとなる何かがあり、それに紐づいてデータができているパターンです。逆に言えば、全てのデータをたどると、最終的にルートとなるデータにたどり着きます。
toBのSaaSのプロダクトだとこの形式が多いかもしれません。

プロダクト例 ルートオブジェクト
病院カルテシステム 病院
BIツール 企業/法人

この場合、ルートオブジェクトが異なるものは独立しているので、それぞれの「世界」にそれぞれルートオブジェクトがあり、紐づくデータが含まれるわけです。このパターンは並列化も容易です。

それぞれの「世界」が干渉しないため、「世界」ごとにconstruct / destructしてテストを実施することができます。

ルートオブジェクトなし

分散型、とでもいうのでしょうか。ある「世界」にN個のオブジェクトがあったり、オブジェクトとオブジェクトがN対Nで結びついたりしているものです。

プロダクト例
証券口座システム
社内管理システム

私が業務で扱っているのは「ルートオブジェクトあり」のものなので、この「ルートオブジェクトなし」のものについてはあまり明るくないです…。
今扱っているプロダクトに「社内管理画面」があり、そのテストの知見を少し書きます。

コチラは若干面倒で、「世界」を区切るのが面倒です。そのため、このパターンだと実行順序性などを考慮する必要があります。

例えば、BIツールSaaSプロダクトの導入企業管理システムで「導入社名で検索する」ようなユースケースがあると思います。このとき、テストを並列実行すると、各テストストリームで似た名前の社名を使用している場合にテスト実行のコンテキストによって表示される導入社の件数が異なることがありえます。

結局どうしたらいいの?

基本戦略は大体こんな感じです。

  • ルートオブジェクトあり / なしで分ける
    • あり: 「世界」ごとにシナリオを分け、並列実行する
    • なし
      • 並列実行せず、ルートオブジェクトなしシナリオだけ直列実行する(最低限にする)
      • プロダクト環境も並列化し、テストストリームごとに一つのプロダクト環境をアサインし、干渉しないようにする

ルートオブジェクトがあるものは並列化し放題なので、基本的にルートオブジェクトがあるものを起点に考えるといいと思います。
また、ルートオブジェクトなしパターンでも、細かくみていけばルートオブジェクトありパターンにすることができます。例えば証券口座システムでも、一つの証券口座をルートオブジェクトとみなし、その情報の設定などをテストすることができます。
さらに、ルートオブジェクトありパターンでも、ルートオブジェクトが常に同種とは限らないこともあります。電子カルテシステムなら通常は病院がルートオブジェクトですが、医療法人(複数病院を保持する)をルートオブジェクトとすることも考えられますし、病院内の1つのカルテをルートオブジェクトと考えて診断内容・投薬内容などを記載することも考えられます。

実例

電子カルテシステムのテストで、setupをスクリプト的に行なったものとして記載します。今私が携わるプロダクトでも同じように初期化スクリプトを作成しています。

/* ルートオブジェクト(病院)のinsert */
const hospital_id = await DB.insert(
  'hospitals',
  [
    {
      'name': 'dummy病院',
      ...
    },
  ]
);
/* 付帯データのinsert */
// (病院の)医師
const doctor_id = await DB.insert(
  'doctors',
  [
    {
      'name': 'ドクターX',
      hospital_id,
      ...
    }
  ]
);
// (病院の)患者
const patient_id = await DB.insert(
  'patients',
  [
    {
      'name': '患者A',
      hospital_id,
      ...
    }
  ]
);
// (患者の)カルテ
const karte_id = await DB.insert(
  'kartes',
  [
    {
      hospital_id,
      patient_id,
      ...
    }
  ]
);

ルートオブジェクト(病院)から芋づる式に付帯データまでつながっているのがわかるかと思います。逆に言えば、このようにルートオブジェクトから全てがつながっていなければ、ルートオブジェクトなしパターンです。

なお、このとき、teardownは逆順(付帯データ→ルートオブジェクト)としたほうがいいです。ID類を内部にストアしておいて、自動teardownを作成することもできます。
というより、そもそもteardownをする必要もないかもしれません。全て個別の「世界」で完結していれば不要です!

いかがでしたか?

初期化について3記事に渡って書いてきました。さすがに読んでて疲れますね〜。でもここに書いたのがほぼ全てです。

本当に、初期化を制すことができれば、自動テストも怖くないです。初期化がうまくできれば、あとはテスト内容に注力できます。これは結構強力だと思うんです。特にUI使うテストだと安定化させるために何回も実行する必要があるので、再実行が容易なことが実質必須です。そういったことも結局初期化がうまくできているかどうかです。

というわけで誰かの役に立てたらいいなと思います。幸あれ!