単体テストとデータベース:どの時点で実際にデータベースに接続しますか?


37

データベースに接続するテストクラスの方法に関する質問への回答があります。たとえば、「テストクラスがサービスを接続する必要があります...」「ユニットテスト-データベース結合アプリ」などです。

したがって、要するに、データベースに接続する必要があるクラスAがあると仮定しましょう。Aに実際に接続させる代わりに、Aに接続に使用できるインターフェースを提供します。テストのために、このインターフェイスをいくつかのもので実装します-もちろん接続しません。クラスBがAをインスタンス化する場合、「実際の」データベース接続をAに渡す必要があります。ただし、Bはデータベース接続を開きます。つまり、Bをテストするには、接続をBに挿入します。ただし、BはクラスCなどでインスタンス化されます。

したがって、どの時点で「ここでデータベースからデータをフェッチし、このコードの単体テストを作成しません」と言う必要がありますか?

言い換えると、あるクラスのコードのどこかで呼び出す必要がある、sqlDB.connect()またはそれに似たものです。このクラスをテストするにはどうすればよいですか?

また、GUIまたはファイルシステムを処理する必要があるコードでも同じですか?


ユニットテストをしたいです。他の種類のテストは私の質問とは関係ありません。私はそれで1つのクラスのみをテストすることを知っています(キリアンに同意します)。現在、一部のクラスはDBに接続する必要があります。このクラスをテストして「これを行うには」と尋ねると、多くの人が「依存性注入を使用してください!」と言います。しかし、それは問題を別のクラスにシフトするだけですよね?だから、本当に、本当に接続を確立するクラスをテストするにはどうすればいいですか?

おまけの質問:ここでの答えは、「模擬オブジェクトを使用してください!」どういう意味ですか?テスト対象のクラスが依存するクラスをモックします。ここでテスト対象クラスをモックし、実際にモックをテストします(テンプレートメソッドを使用するという考え方に近づきます。以下を参照)。


テストしているのはデータベース接続ですか?一時メモリ内データベース(derbyなど)を作成しても問題ありませんか?

@MichaelTそれでも、メモリ内の一時DBを実際のデータベースに置き換える必要があります。どこ?いつ?どのようにユニットテストされていますか?または、このコードの単体テストを行わなくても大丈夫ですか?
TobiMcNamobi

3
データベースについて「単体テスト」することは何もありません。それは他の人によって維持されており、バグがあった場合は、自分で修正するのではなく、修正してもらう必要があります。クラスの実際の使用とテスト中の使用の間で異なる必要があるのは、データベース接続のパラメーターだけです。プロパティファイルの読み取りコード、Springインジェクションメカニズム、またはアプリを組み合わせるために使用するものが壊れている可能性は低いです(そうであった場合、自分で修正できませんでした、上記を参照)。配管機能のこのビットをテストしないでください。
キリアンフォス

2
@KilianFothは、純粋に作業環境と従業員の役割に関連しています。質問とは何の関係もありません。データベースの責任者が1人もいない場合はどうなりますか?
Reactgular

いくつかのモックフレームワークでは、プライベートオブジェクトや静的メンバーにさえも、モックオブジェクトをほとんど何でも注入できます。これにより、模擬DB接続などのテストが非常に簡単になります。Mockito + Powermockが最近の私にとって有効なものです(Javaで、あなたが何をしているのかわかりません)。
FrustratedWithFormsDesigner

回答:


21

単体テストのポイントは、1つのクラスをテストすることです(実際、通常は1つのメソッドをテストする必要があります)。

これは、classをテストするときにA、テストデータベースをインジェクトすることを意味します-自己記述型、または超高速のインメモリデータベースなど、仕事を遂行するものは何でも。

ただし、のBクライアントであるclassをテストする場合A、通常、Aオブジェクト全体を別の何か、おそらくプリミティブで事前にプログラムされた方法でジョブを実行するものでモックします-実際のAオブジェクトを使用せず、データを使用せずにbase(Aデータベース接続全体を呼び出し元に戻す場合を除きます-しかし、それはあまりにも恐ろしいので、考えたくありません)。同様に、のCクライアントであるクラスの単体テストを作成する場合B、の役割を担うものをモックしBA完全に忘れます。

そうしないと、単体テストではなく、システムまたは統合テストになります。これらも同様に非常に重要ですが、魚のまったく異なるケトルです。そもそも、彼らは通常、セットアップと実行に多くの労力を費やしています。チェックインの前提条件として渡すことを要求することは実用的ではありません。


11

データベース接続に対して単体テストを実行することは完全に正常であり、一般的な方法です。puristシステム内のすべてが依存性注入可能なアプローチを作成することは、単に不可能です。

ここで重要なのは、一時データベースまたはテスト専用データベースに対してテストし、そのテストデータベースを構築するための可能な限り軽い起動プロセスを用意することです。

CakePHPの単体テストには、と呼ばれるものがありますfixtures。フィクスチャは、単体テスト用にオンザフライで作成される一時的なデータベーステーブルです。フィクスチャには、それらを作成するための便利なメソッドがあります。テストデータベース内の運用データベースからスキーマを再作成するか、単純な表記法を使用してスキーマを定義できます。

これで成功するための鍵は、ビジネスデータベースを実装せず、テストするコードの側面のみに焦点を当てることです。データモデルが公開されたドキュメントのみを読み取ることを検証する単体テストがある場合、そのテストのテーブルスキーマには、そのコードに必要なフィールドのみが含まれている必要があります。そのコードをテストするためだけに、コンテンツ管理データベース全体を再実装する必要はありません。

いくつかの追加の参照。

http://en.wikipedia.org/wiki/Test_fixture

http://phpunit.de/manual/3.7/en/database.html

http://book.cakephp.org/2.0/en/development/testing.html#fixtures


28
同意しません。データベース接続を必要とするテストは単体テストではありません。その性質上、テストには副作用があるためです。これは、自動化されたテストを作成できないことを意味するものではありませんが、そのようなテストは定義により統合テストであり、コードベースを超えてシステムの領域を実行します。
キース

5
私を純粋主義者と呼んでください。しかし、単体テストでは、テストランタイム環境の「サンドボックス」から離れるアクションを実行すべきではないという考え方を固守しています。データベース、ファイルシステム、ネットワークソケットなどに触れてはいけません。これはいくつかの理由によるもので、少なくとも外部状態に対するテストの依存性ではありません。もう1つはパフォーマンスです。ユニットテストスイートは迅速に実行される必要があり、これらの外部データストアとのインターフェイスはテストの速度が桁違いに遅くなります。私自身の開発では、部分モックを使用してリポジトリのようなものをテストし、サンドボックスに「エッジ」を定義することに抵抗はありません。
キース

2
@gbjbaanb-最初は問題ないように聞こえますが、私の経験では非常に危険です。最適に設計されたテストスイートとフレームワークでも、このトランザクションをロールバックするコードは実行されない場合があります。テストランナーがクラッシュしたり、テスト内で中断したり、テストでSOEまたはOOMEがスローされたりした場合、最良のケースは、接続が切断されるまで、接続したテーブルをロックするDBに接続とトランザクションがハングしていることです。こうした、DBテストとしてSQLiteのを使用するなど、あなたがこの原因となる問題を防ぐ方法は、例えば、実際にあなたがいない、自分の欠点を持って実際に行使本当の DBを。
キース

5
@KeithS私たちはセマンティクスについて議論していると思います。単体テストの定義や統合テストの定義についてではありません。フィクスチャを使用して、データベース接続に依存するコードをテストします。それが統合テストの場合、私はそれで大丈夫です。テストに合格することを知る必要があります。依存関係、パフォーマンス、またはリスクを気にすることはできませんでした。そのテストに合格しない限り、そのコードが機能するかどうかはわかりません。大部分のテストでは依存関係はありませんが、存在するテストではそれらの依存関係を切り離すことはできません。あるべきだと言うのは簡単ですが、そうすることはできません。
Reactgular

4
私たちもそうだと思います。統合テストにも「ユニットテストフレームワーク」(NUnit)を使用しますが、これら2つのカテゴリのテスト(多くの場合、別々のライブラリ)を分離するようにします。私がやろうとしていたポイントは、繰り返しの赤緑リファクタリングの方法論に従って、各チェックインの前に1日に数回実行するユニットテストスイートが完全に分離可能であり、実行できることです同僚のつま先を踏まずに、これらのテストを1日に数回行います。
キース

4

コードベースのどこかに、リモートDBに接続する実際のアクションを実行するコード行があります。このコード行は、10回に9回、言語と環境に固有のランタイムライブラリによって提供される「組み込み」メソッドの呼び出しです。そのため、「自分の」コードではないため、テストする必要はありません。単体テストの目的で、このメソッド呼び出しが正しく実行されることを信頼できます。ユニットテストスイートでまだテストできること、またテストすべきことは、接続文字列が正しいことを確認する、SQLステートメントまたはストアドプロシージャ名。

これは、単体テストがランタイムの「サンドボックス」を離れて外部状態に依存しないという制限の背後にある目的の1つです。実際には非常に実用的です。ユニットテストの目的は、コードことを確認することですあなたが書いた(またはTDDで、書き込みしようとしているが)あなたはそれが思ったように動作します。データベース操作を実行するために使用しているライブラリなど、記述しなかったコードは、記述していないという非常に簡単な理由により、単体テストのスコープの一部であってはなりません。

あなたには、統合テストスイート、これらの制限が緩和されています。今、あなたはできるデータベースに触れる設計テスト、あなたが書いたコードがあなたがしなかったコードでうまく動作することを確認します。ただし、ユニットテストスイートは実行速度が速いほど効果的であるため(これらの2つのテストスイートは分離されたままである必要があります(したがって、開発者がコードについて行ったすべてのアサーションがまだ保持されていることをすばやく確認できます))外部リソースへの依存関係が追加されるため、桁違いに遅くなります。ビルドボットが完全な統合スイートを数時間ごとに実行し、外部リソースをロックするテストを実行できるようにして、開発者がこれらの同じテストをローカルで実行してお互いの足指を踏まないようにします。そして、ビルドが壊れた場合、何をしますか?ビルドボットがビルドを失敗しないようにすることは、本来あるべきことよりもはるかに重要です。


さて、これをどれだけ厳密に守ることができるかは、データベースへの接続とクエリの正確な戦略に依存します。ADO.NETのSqlConnectionオブジェクトやSqlStatementオブジェクトなど、「ベアボーン」データアクセスフレームワークを使用する必要がある多くの場合、ユーザーが開発したメソッド全体は、組み込みのメソッド呼び出しと、そのため、この状況でできる最善の方法は、機能全体をモックし、統合テストスイートを信頼することです。また、テストの目的で特定のコード行を置き換えることができるようにクラスを設計する意欲にも依存します(Tobiが提案するTemplate Methodパターンは、「部分的なモック」を許可するため、良い方法です)

データ永続性モデルがデータレイヤーのコード(トリガー、ストアドプロシージャなど)に依存している場合、データレイヤー内に存在するか、またはクロスするテストを開発する以外に、自分で書いているコードを実行する方法は他にありませんアプリケーションランタイムとDBMSの境界。純粋主義者は、この理由で、ORMのようなものを支持してこのパターンを避けるべきだと言うでしょう。私はそこまで行かないと思います。言語統合クエリや、コンパイラがチェックする他のドメイン依存の永続化操作の時代でも、ストアドプロシージャを介して公開された操作のみにデータベースをロックダウンする価値があります。もちろん、そのようなストアドプロシージャは自動化を使用して検証する必要がありますテスト。しかし、このようなテストではありませんユニットテスト。それらは統合です テスト。

この区別に問題がある場合、それは通常、完全な「コードカバレッジ」、つまり「ユニットテストカバレッジ」に置かれている重要性に基づいています。コードのすべての行が単体テストでカバーされるようにします。その顔には高貴な目標がありますが、私はホグウォッシュと言います。その考え方は、このような実行assertionlessのテストを書く限り、この特定のケースを超えて伸びアンチパターン、に適していますがない運動あなたのコード。これらの種類のエンドランは、カバレッジ数だけのために、最小カバレッジを緩和するよりも有害です。コードベースのすべての行が何らかの自動化されたテストによって実行されることを保証したい場合、それは簡単です。コードカバレッジメトリックを計算するときは、統合テストを含めます。さらに一歩進んで、これらの論争のある「Itino」テスト(「名前の統合のみ」)を分離し、ユニットテストスイートとこのサブカテゴリの統合テスト(まだかなり高速に実行されるはずです)を分離することもできますほぼ完全なカバレッジに近い。


2

単体テストは決してデータベースに接続しないでください。定義上、システムの残りの部分から完全に分離して、それぞれ単一のコードユニット(メソッド)をテストする必要があります。そうでない場合、それらは単体テストではありません。

セマンティクスは別として、これが有益な理由は無数にあります。

  • テストは桁違いに速く実行されます
  • フィードバックループは瞬時になります(例として、TDDの1秒未満のフィードバック)
  • ビルド/展開システムのテストを並行して実行できます
  • テストを実行するためにデータベースを使用する必要はありません(ビルドをはるかに簡単に、または少なくともより速くします)

単体テストは、作業を確認する方法です。特定のメソッドのすべてのシナリオの概要を示す必要があります。これは通常、メソッドを通るすべての異なるパスを意味します。複式簿記と同様に、作成する仕様です。

あなたが説明しているのは、別のタイプの自動化されたテスト:統合テストです。それらも非常に重要ですが、理想的にははるかに少ないものです。ユニットのグループが互いに適切に統合されていることを確認する必要があります。

それでは、データベースアクセスでどのようにテストしますか?すべてのデータアクセスコードは特定のレイヤーに配置する必要があるため、アプリケーションコードは実際のデータベースではなく、モック可能なサービスとやり取りできます。これらのサービスが、あらゆる種類のSQLデータベース、インメモリテストデータ、またはリモートWebサービスデータに裏打ちされているかどうかは気にするべきではありません。それは彼らの関心事ではありません。

理想的には(そしてこれは非常に主観的です)、単体テストでコードの大部分をカバーすることが望まれます。これにより、各ピースが独立して機能するという自信が得られます。ピースが構築されたら、それらをまとめる必要があります。例-ユーザーのパスワードをハッシュすると、この正確な出力が得られます。

各コンポーネントは約5つのクラスで構成されているとしましょう。それらのクラス内のすべての障害点をテストする必要があります。これは、すべてが適切に配線されていることを確認するためのテストがはるかに少ないことに相当します。例-指定されたユーザー名/パスワードでデータベースからユーザーを見つけることができるかテストします。

最後に、ビジネス目標を達成していることを実際に確認するための受け入れテストが必要です。これらはさらに少ないです。アプリケーションが実行されていることを確認し、実行するために構築されたものを実行します。例-このテストデータがあれば、ログインできるはずです。

これら3つのタイプのテストをピラミッドと考えてください。すべてをサポートするために多くの単体テストが必要であり、そこから先に進みます。


1

Template Methodパターンは役立つかもしれません。

データベースへの呼び出しをprotectedメソッドでラップします。このクラスをテストするには、実際のデータベース接続クラスから継承し、保護されたメソッドをオーバーライドする偽オブジェクトを実際にテストします。

このように、データベースへの実際の呼び出しはユニットテストの下に決してありません、そうです。しかし、これらの数行のコードだけです。そしてそれは受け入れられます。


1
なぜ私が自分の質問に答えるのか疑問に思う場合:はい、これ答えかもしれませんが、それが正しいものであるかどうかは確かではありません。
TobiMcNamobi

-1

外部データを使用したテストは統合テストです。ユニットテストは、ユニットのみをテストしていることを意味します。ほとんどの場合、ビジネスロジックで行われます。コードユニットをテスト可能にするには、ユニットをコードの他の部分から独立させる必要があるなど、いくつかのガイドラインに従う必要があります。ユニットテスト中にデータが必要な場合は、依存性注入でそのデータを強制的に注入する必要があります。いくつかのモックとスタブフレームワークがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.