自動受け入れテストは継続的デリバリをサポートする

自動受け入れテストは、継続的デリバリのスタイルを持つテスト戦略に欠くことのできないコンポーネントであり、システムの振る舞いに対して、異なった重要な洞察を与えるものだ。Dave Farley氏の主張によると、開発者は、受け入れテストが動作して通ることに責任を持たなければならない。開発チームとは別にQAチームを持つのは望ましいことではないだろう。

独立したソフトウェア開発者であり、コンサルタントのDave Farley氏が、Craft 2017において、継続的デリバリのための受け入れテストについて話した。InfoQは、このカンファレンスをQ&A、要約、記事で扱う。

よい受け入れテストは、システムの振る舞いの実行可能な仕様だとFarley氏は言う。Farley氏は、テストを書く時に、ドメインの言語を使うように提案した。そうすれば、テストは読みやすくなり、プロダクトオーナーや顧客等に説明しやすくなる。また、テストケースをメンテナンスするのも、より簡単になるだろう。

受け入れテストの時間の依存を扱うのに、2つの選択肢がある。テストケースで時間を考慮しないこともできるが、そうすることで、テストできないケースができて、エラーを見逃すかもしれない。もう1つの選択肢は、外部的な依存として、時間を扱うことだ。時計の機能を提供するスタブを作成して時間を制御し、時間を操作するために、テストフレームワークに関数を追加する方法について、Farley氏が例を挙げた。

InfoQは、何が受け入れテストを難しくするのか、また、どのように受け入れテストをするのかについて、Dave Farley氏にインタビューした。

InfoQ:受け入れテストは、どこに当てはまりますか? テストの目的は何ですか?

Dave Farley氏:私は、継続的デリバリスタイルのテスト戦略の重要なコンポーネントとして、自動受け入れテストを見ています。テスト駆動開発(TDD)の成果として作成された、下流レベルのユニットテストと組み合わせることで、受け入れテストは、システムの振る舞いに異なった重要な洞察を与えます。

受け入れテストは、製品と同じテスト環境で、外部ユーザの観点からシステムを評価します。システムの振る舞いの「実行可能な仕様」の形式で、もっともいいテストが作成されます。

そして、この実行可能な仕様の考えを提供するのに加えて、ユーザと私たちのソフトウェアの機能契約を肯定しながら、自動化されたDoneの定義と、自動配置とシステムの構成を繰り返すための理想的な機会を私たちに提供します。

InfoQ:受け入れテストを難しくするものは何ですか?

Farley氏:結合することです! この種のハイレベルな機能テストは、テスト対象システムとゆるく繋がるようにすることに、大抵の企業はそれほど懸命に取り組んでいません。そのため、システム配下のテストが変更されると、テストが失敗するので、修正しなければなりません。私の話は、主に、この問題に注目しています。「テスト」よりも「実行可能な仕様」を定義するために、ドメイン特化言語を使う技術があります。テストを分離し、「どうやって」するかよりも、システムは「何を」する必要があるのかに注目します。これは、テストケース(仕様)とテスト対象システムの関係を、効果的に分類しておくのに、非常に役立ちます。

このようなテストを生かしておくための問題を解決するのに、もう1つ知っておくべき重要なことは、正しい人の手に責任を持たせることです。QAチームの目的がテストを自動化することであっても、開発チームとは別にQAチームを持つのは、この業界で広く見られるアンチパターンです。

システムの振る舞いのための本物の「実行可能な仕様」をうまく作成した場合、テストが失敗する原因となった変更を開発者が持ち込んだ時に、その問題を修正するのは、開発者の責任でなければなりません。結局、失敗した変更は、システムがもはや機能仕様(一連の受け入れテストによって記述されている)に合っていないという意味です。これらのテストは誰でも書けますが、最初にテストが実行された時から、テストを実行して通すのは、開発者の責任です。

InfoQ:どのようにテストケースを分離しますか?

Farley氏:私が重要だと思う分離は、3レベルあります。システム、機能、時間です。

システムレベルの分離は、テスト対象システムの境界について、かなり特定したものになります。テスト対象システムの状態は、非常に正確に制御できなければなりません。そうするために、システムの境界において、テストが正しい必要があります。上流システムと影響を与え合いながら、システムをテストしたくありません。そのような場合、難しい境目のケースを検査しようとしても、十分に制御できないでしょう。また、下流の外部システムから結果を収集したくありません。普通のインタフェースを使って、システムの振る舞いを呼び出したいのです。どのような形式であれ、直接、結果を収集し、時には、外部依存のためのスタブを使います。そうすれば、アサーションのためのデータを収集したり、振る舞いを呼び出すデータを注入したりできます。

機能的分離によって、お互いにテストケースを分離するためのマルチユーザシステムの中で、本来の境界を使うことができます。これにより、大きく複雑なシステムの起ち上げコストを共有し、多くのテストを一緒に実行できます。同時に、お互いに妨害することはありません。このアイデアはとても簡単です。各テストケースのセットアップの一部として、新しいアカウント、製品、マーケットプレイス(どのようなコンセプトが、システムの本来の機能的境界を表していても) を作り出し、1つのテストケースのコンテキストでのみ、これらの機能的要素を使います。Amazonブックストアのテストを作成する場合は、各テストは、新しいアカウントを登録して、販売するための新しい本を作成するところから始まります。

時間的な分離によって、繰り返し、同じテストを実行し、同じ結果を得ることができます。もう一度言いますが、私は、テスト毎にシステムを壊さなければならないようにはしたくありません。コストがかかりすぎます。代わりに、実行中のデプロイしたシステムの同じインスタンスで、同じテストケースを何度も実行したいのです。このため、上記で述べた機能的な分離技術と、プロキシネーミング技術の使用を組み合わせます。テストが、新しいアカンウトや本を作成するように求めた時に、テストインフラストラクチャが介在します。テストケースで与えた名前を使って、アイテムに名前を付ける代わりに、別名をつけて、その別名を使います。テストケースの範囲内で、テストは、選んだ名前をいつも使えますが、テストインフラストラクチャは、どのテストの実行の範囲でも、選んだ唯一の名前にマップされます。これにより、同じテストケースを繰り返し実行できて、機能的分離の利益も得られます。

InfoQ:あなたは、私たちがUIの記録/再生システムを使うべきではないと提案しました。その理由を説明してもらえますか? 他に選択肢はありますか?

Farley氏:実行可能な仕様は、ユーザインタフェースをどのように実装したのかという詳細ではなく、ユーザの振る舞いに注目させたいのです。「placeOrder」や「payForPuchases」のように、問題のドメインからテストケースをエンコードできるならば、システムのインタフェースが何であっても、同じテストケースを使って、振る舞いを評価できます。そのシステムにおいて、グラフィカルユーザインタフェースやREST APIを通して「placeOrder」できる場合、テストを1つ書けば、そのテストは、コミュニケーションの両方のチャネルにとって、存続可能な仕様になります。関係の分離は完了しました。そうできれば、ウェブUIを書き直すような比較的小さな変更は、テストケース(実行可能な仕様)を存続可能なように対処して、維持するのは簡単です。

本来、UIの記録/再生システムは、ユーザがほしいシステムの振る舞いよりも、UIがテストに注目していると仮定して始められます。これは、実は、振る舞いに注目したテストではなく、技術に注目したテストです。その結果、このようなテストは常に脆弱で、テスト対象システムへの比較的小さな変更であるにもかかわらず、壊れやすくなります。これは、長期間でみれば、作業が多くなり過ぎるので、私は避けるようにしています。

InfoQ:どうすれば、テストケースと受け入れテストを、全体的により効率的にできますか?

Farley氏:テストの効率性は、全体に影響します。正しい場所で、正しいことをテストすることを考える必要があります。

受け入れテストで、細かく、1行毎にコードをテストしたくはありません。コストがかかり過ぎます。そのようなテストはTDDを使い、結果として、素早くフィードバックを手に入れます。

テストには、製品データを使いたくありません。製品データは大き過ぎますし、テスト環境の供給、テスト対象システムの開始、テストケース自体の実行を遅らせます。テストデータを最小にして、テストしようとする振る舞いを、正確にターゲットにできるようにしたいのです。

例えば、アカウント登録のような通常とは違う操作で、アプリケーションを最適化するようにします。テストとアプリケーションの起動時間を改善すれば、受け入れテストは、より素早く答えを得られるものになります。

テスト中に、スリープさせたり、待たせたりするのは避けます。これらは、競合状態から隠そうとするために、通常、一時的な解決方法として使われます。そうすると、効率が悪くなり、大抵、本当の問題を解決する代わりに、どこか別の場所に競合状態が移動することになります。

受け入れテストは、高品質なテスト戦略の重要な部分ではありますが、それはほんの一部でしかありません。受け入れテストを、TDDや、TDDが作り出す下流レベルのユニットテストに置き換えるべきではありません。そのようなテストには敬意を表するべきです。

私の最小のデプロイのパイプライン、最も簡単なプロジェクトでさえ作成するパイプラインは、「コミットテスト」(TDD)、「受け入れテスト」(実行可能な仕様)、「製品への自動デプロイ」を含みます。