【読書メモ】単体テストの考え方/使い方 第2章
こんにちは。@regina_t_rexです。
小中学生向けのAI型教材開発を行う事業会社で1人目QAとして2年弱の活動後、現在はQA体制強化のため採用/採用広報担当として奮闘中です。
しかし個人でテストの学習を続け、さらなる進化に向けて土台を強化していければと思います。。!
本記事は単体テスト本の第2章!超ゆっくりですが読み進めています。
本記事の所感以外の内容については、すべて下記の書籍より引用しています。
第2章 単体テストとは何か?
この章のトピック
単体テストとは何か?
共有依存、プライベート依存、揮発性依存の違い
単体テストにおける2つの学派である古典学派とロンドン学派について
単体テスト、統合テスト、E2Eテストの違い
2.1 単体テストの定義
次の3つの性質を備えているのが単体テスト。
・「単体(unit)」と呼ばれる少量のコードを検証する
・実行時間が短い
・隔離された状態で実行されるこの3つ目の「隔離」の定義によって古典学派・ロンドン学派のスタイルがわかれる。・ロンドン学派の隔離
・テスト対象システム(SUT)から協力者オブジェクト(collaborator)を隔離すること
・テスト対象となるクラスが他のクラスに依存している場合、その依存をすべてテスト・ダブルに置き換えなければならない
・テスト・ダブルとは
・リリース対象のオブジェクトと似たような見た目と振る舞いを持っていながらも、複雑さを減少させて簡潔になることでテストを行いやすくするオブジェクトのこと ・テストダブルを使う利点
・テスト失敗時の発生箇所が明確にできる
・オブジェクトグラフを分離でき、循環依存なども解消できる
・1つのテストケースは1つのクラスしか検証しないという指針をプロジェクト全体に普及させられる
【所感】
古典学派とロンドン学派という2つの学派から単体テストを考える。
何を確認するか?と同時に、テストしたい対象「だけ」の状態に隔離してテストする際のスタイルの違いで派がわかれているとのこと
2.1.2 古典学派の隔離
・古典学派の隔離
・古典学派では、単体テストにおいて隔離する必要があるのはコードではなくテストケースであり、各テストケースをお互いに影響を与えることなく実行できるようにしなくてはならない、と考える
・3つの依存
・共有依存(揮発性依存)
・プライベート依存
・プロセス外依存・共有依存は単体テストのテストケース間で共有される依存のことであり、テスト対象となるクラス間での共有ではない
・Singletonパターンを採用したオブジェクトはテストケースごとに新しいインスタンスを作成するようにしている限り共有依存にはならない
・例)設定(configuration)に関する情報をもつクラスの場合、通常生成されるインスタンスは1つしか無く、そのインスタンスがプロダクションコードのいたるところで使われるようになっている
・しかし、このインスタンスがほかの依存と同じ方法でテスト対象システムに注入されるようになっている場合(コンストラクタ経由での受け渡しなど)、テストケースごとに新しいインスタンスを生成すれば、そのインスタンスがほかのテストコードと共有されることはない
・ファイルシステムやデータベースの場合だと、テストケースごとに新しいインスタンス生成ができないため、これらの依存をテストケース間で共有させるか、テストダブルを使って置き換えるかのどちらかを選択することになる
・その他、共有依存をテストダブルに置き換える理由は、テストの実行速度をあげるため
・データベースやファイルシステムなどの共有依存への呼び出しはプライベート依存に比べ時間がかかる
・共有依存を扱うようなテストケースは単体テストではなく統合テスト(integration)テストに含める必要がある
・古典学派では「何を単体とするのか」という見解もロンドン学派と異なる
・共有依存が含まれないかぎり、複数クラスを単体テストで検証しても問題ない
【所感】
以前Javaの現場で開発をしていた時期があったり、超基礎の勉強がてらJavaアソシエイツの資格も取っていたので、このあたりの考え方の話であればわりとするっと理解ができました。よ、よかった。。
ただJUnitでテストコード書いていた時はこんなことまでちゃんと考えておらず。古典学派とロンドン学派との違いは、隔離の考え方と何を単体とするか、の違いなのだということがわかりました。
これから章を読み進める度に、よりシステムに関する理解が求められて来そうと思います。
ブラックボックスのその先のテストをもっと改善できたら良いなと思います。
2.2 古典学派およびロンドン学派が考える単体テスト
・2つの学派の違い
・隔離対象:単体(ロンドン学派)、テストケース(古典学派)
・単体の意味:1つのクラス(ロンドン学派)、1つのクラスもしくは同じ目的を達成するためのクラスの1グループ(古典学派)
・テストダブルの置き換え対象:不変依存を除くすべての依存(ロンドン学派)、共有依存(古典学派) ・「依存」は共有依存、「プライベート依存」は可変依存(mutable dependency)か不変依存(immutable dependency)のどちらかになる
・依存が不変(immutable)であればその依存はテストダブルに置き換える必要はない
【所感】
クラスやオブジェクト、ミュータブル、イミュータブルなど、改めて用語を確認しようとScalaのコップ本を引っ張り出して読んだところで今日は終わってしまった。今度こそちゃんと読む。。!
この本ではコードがC#で書かれていますが、せっかくなのでScalaで勉強してみるのも良いのではと思ってきました。
Column 協力者オブジェクト(collabprator)と依存との関係
・協力者オブジェクトは共有依存もしくは可変依存のどちらかになるもの
・DBアクセスを提供するようなクラスの場合、DBが共有依存であるためそのクラスのオブジェクトは協力者オブジェクトになる
・一方Product型のオブジェクトやリテラルの「5」も依存であるが、協力者オブジェクトではなく、値・値オブジェクトになる
・共有依存であってもプロセス依存ではないもの
・シングルトンやクラス内に定義されたstaticフィールドなど
・DBは共有依存であり、プロセス外依存、可変依存である
・読み込み専用のAPIサービスはプロセス外依存にはなるが、単体テストを実施しても状態が変わることがないため共有依存にならない
・「共有依存」と「プロセス外依存」は異なる意味を持つが交換可能なものとして扱う
・複数のテストケースで扱われる依存が同じプロセス上にある場合、テストケースごとにその依存のインスタンスを作成すれば共有されないようにすることが簡単にできる
【所感】
扱う要素によって何依存にあたるのか、が細かく分かれています。
まだ解説を見る分には追いつけていますが、コードベースでの具体的な理解がないとこのあたりの把握や扱いが難しいと思うので、システム設計の考え方含めて深掘りしたい気持ち。
2.3 単体テストにおける古典学派とロンドン学派の違い
・古典学派の方が下記の点でプロダクトの持続的な成長を促す、という所で向いているかも
・より細かな粒度で検証ができる
・依存関係が複雑になっていても簡単にテストすることができる
・テストが失敗した際、どの機能に問題があったのかを正確に見つけられるようになる
2.3.1 細かな粒度による検証(1つのテストケースに対して1つのクラスを検証すること)
・通常、開発者はクラスのことをすべてのコードベースの基盤となる分解できない構成要素として見る
・単体テストでは、「1単位のコード」を検証するのではなく、「1単位の振る舞い」を検証するようにする
・問題領域において意味のあるもの(理想はビジネス観点で有用であると考える何か)の検証
・この検証で意味する単体は複数のクラスにまたがることもあれば、単一のクラスにまたがることもある
・コードの粒度を細かくしようとすることは単体テストにおいてあまり意味がない
・少なくとも1単位の振る舞いが検証されてさえいれば、それは良い単体テストとなる
・単体テストにおいて各テストケースがすべきこと
・そのテストに関わる人達にテスト対象のコードが解決しようとしている物語(story)を伝えること
・その物語を伝えるためには凝集度(cohesion)を高め、非開発者でも理解できるようにすることが必要
【所感】
凝集度の高い物語
「私が犬の名前を呼ぶと、その犬は私のところに寄ってきます。」
そうではない場合
「私が犬の名前を呼ぶと、その犬は左前足を動かし、次に右前足を動かし、頭を私の方に向け、しっぽを振り始め・・・」テスト対象を「個々のクラス」にしてしまうと、凝集度が低い表現のようなことが起こるため、振る舞いの単位でのテストが必要となる。単体、を振る舞いの単位で分ける考え方と理解。
あくまで、「本質的な問題はプロダクションコードにある」ということを前提に、テストで設計の問題点を発見していきたい。
2.3 単体テストにおける古典学派とロンドン学派の違い(続き)
2.3.2 複雑な依存関係を持つものに対する単体テスト
・テスト対象のクラスが必要とするものが多く、それらの依存がさらに別の依存を必要とするような複数層の複雑な依存関係を築いている場合、テスト対象のクラスが直接アクセスする依存をテストダブルに置き換えることで、この複雑な依存関係を断ち切れる様になる
・しかし、これを古典学派のスタイルでテストするには、テスト対象システムを適切に機能させるためだけにすべての依存を用意して複雑な依存関係を作り上げなくてはならない
・本来考えるべきことは、膨大で複雑な依存関係をもつクラスの検証方法を見つけ出すことではなく、複雑な依存関係を構築しなくてもすむようにする方法
・このような複雑な依存関係が必要になってしまうのは間違った設計が行われた事が原因であるため
・単体テストをしようとすることで、この問題が明確になる
・単体テストはプロダクションコードの欠点を見つけ出すことを得意としており、単体テストの準備フェーズがあまりにも大きくなるようであれば、何らかの設計の問題がそこにある可能性が高い2.3.3 テストが失敗した際、どこに問題があったのかが正確にわかる
・ロンドン学派のスタイルでテストケースが書かれている場合、失敗の際はテスト対象のクラスに存在することになる
・古典学派のスタイルでは、テスト対象システムが呼び出す依存に不具合があるときもテスト失敗する
・単体テストを頻繁に実施していれば、最後に修正した箇所にテスト失敗要因があるため、どこでバグが発生したのかがわかるため、これはあまりデメリットにはならない
・失敗がテストスイートのいたるところに広がる場合、問題のあったコードが多くのクラスに依存されることの証明となり、設計や変更などでコードを扱う際に把握していると有用な情報となる2.3.4 その他の古典学派とロンドン学派の違い
・テスト駆動開発(TDD)を用いたシステム設計
・テストコードが把握することになるプロダクションコードの詳細
【所感】
単体テストできる状態にするのがめっちゃ大変な場合は元々のシステムの設計に問題があるぜ!というお話
単体テストを行うことでこの問題点が見つかる場合があるので、プロダクションコードの設計に関する問題への対処方法を第2部で詳しく見ていく。
古典学派のスタイルでのテストを行うことで、依存関係の把握にも役立つという副次効果も。
プロダクションコード側も学べるよう、プライベートのWindowsPCにScalaの開発環境を構築してみました。最初の変数定義、関数定義、のところからやり直しています。
ScalaとPlay FrameworkでまるっとWebアプリを作ってみよう!みたいな参考書やサイトはないか。。とちょっと探してみています
2.4 古典学派およびロンドン学派における統合(integration)テスト
・古典学派とロンドン学派の間では統合(integration)テストの定義も異なる
・ロンドン学派では実際の協力者オブジェクトを使って行うテストをすべて統合テストとみなすため、古典学派のスタイルで書かれたテストはほとんどロンドン学派の視点だと統合テストに分類されることになる
・古典学派では下記3つの性質を1つでも損ねているテストを統合テストと扱う
・「単体(unit)」と呼ばれる少量のコードを検証する(1単位の振る舞いを検証する)
・実行時間が短い
・(他のテストケースから)隔離された状態で実行される・統合テストの一種としてのE2E(End-to-End)テスト
・統合(integration)テストとは、共有依存やプロセス外依存、さらには同じ組織内の異なるチームによって開発されたコードが統合された状態で想定どおりに機能することを検証するテストのこと
・E2E(End-to-End)テストは一種の統合テストともいえる
・E2Eテストはエンドユーザー視点でシステムを検証するためほぼ全てのプロセス外依存を扱う
・E2EテストはUIテスト、GUIテスト、機能テストなどと呼ばれることもある
【所感】
統合テスト(結合テスト)、E2Eテスト、機能テストetc… ここではテストタイプとしてではなくE2Eテストを機能テストと呼ぶ場合もあるのか。用語定義しないといけないやつですね。
テスト対象システムの中のクラスやデータベース、外部サービスなど、システムを構成する一つ一つの要素を把握していないと検証の単位を捉えるのは難しそうです。うーむやはりシステム全体の把握。
第2章 まとめ
この本での単体テストの定義は次の性質をすべてもつもの
「単体(unit)」と呼ばれる少量のコードを検証する
実行時間が短い
隔離された状態で実行される
「隔離」の考え方によって古典学派・ロンドン学派という学派が存在する
統合テストとは、単体テストがもつべき3つの性質を1つでも欠いたテストのこと。一方E2Eテストは統合テストの一種であり、テスト対象のアプリケーションが使用するすべてのプロセス外依存をそのまま使ってテストすることになる
単体テストとは?というところの解像度が上がりました。
あくまで、「本質的な問題はプロダクションコードの設計にある」ということを前提に、テストコードをどう書くか?ではなく、目的を忘れずにテストコードを利用していきたいです。
また、古典学派・ロンドン学派も完全な対立軸ではなく、プロダクションコードの設計によって使い分ける、が正解な気がします。
次の第3章は単体テストの構造的解析!
この記事が気に入ったらサポートをしてみませんか?