データベースクエリを使用してオブジェクトを単体テストする方法
-
09-06-2019 - |
質問
単体テストは「とても素晴らしい」、「本当にクール」、「あらゆる点で優れている」と聞いたことがありますが、ファイルの 70% 以上がデータベース アクセス (一部は読み取り、一部は書き込み) に関係しているため、どのように行うのかわかりません。これらのファイルの単体テストを作成します。
私は PHP と Python を使用していますが、これはデータベース アクセスを使用するほとんど/すべての言語に当てはまる質問だと思います。
解決
データベースへの呼び出しを模擬することをお勧めします。モックは基本的に、同じプロパティやメソッドなどを持つという意味で、メソッドを呼び出そうとしているオブジェクトに似たオブジェクトです。発信者が利用できます。ただし、特定のメソッドが呼び出されたときに実行するようにプログラムされているアクションを実行するのではなく、それを完全にスキップして、結果だけを返します。通常、その結果は事前に定義されます。
オブジェクトをモック用に設定するには、おそらく、次の疑似コードのように、ある種の制御/依存関係注入パターンの反転を使用する必要があります。
class Bar
{
private FooDataProvider _dataProvider;
public instantiate(FooDataProvider dataProvider) {
_dataProvider = dataProvider;
}
public getAllFoos() {
// instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
return _dataProvider.GetAllFoos();
}
}
class FooDataProvider
{
public Foo[] GetAllFoos() {
return Foo.GetAll();
}
}
ここで、単体テストで FooDataProvider のモックを作成します。これにより、実際にデータベースにアクセスせずに GetAllFoos メソッドを呼び出すことができます。
class BarTests
{
public TestGetAllFoos() {
// here we set up our mock FooDataProvider
mockRepository = MockingFramework.new()
mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);
// create a new array of Foo objects
testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}
// the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
// instead of calling to the database and returning whatever is in there
// ExpectCallTo and Returns are methods provided by our imaginary mocking framework
ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)
// now begins our actual unit test
testBar = new Bar(mockFooDataProvider)
baz = testBar.GetAllFoos()
// baz should now equal the testFooArray object we created earlier
Assert.AreEqual(3, baz.length)
}
}
簡単に言えば、よくある嘲笑のシナリオです。もちろん、実際のデータベース呼び出しの単体テストも行う必要があるでしょう。そのためにはデータベースにアクセスする必要があります。
他のヒント
理想的には、オブジェクトは永続的に無視されるべきです。たとえば、リクエストを行ってオブジェクトを返す「データ アクセス層」が必要です。こうすることで、その部分を単体テストから除外したり、単独でテストしたりできます。
オブジェクトがデータ層と密接に結合している場合、適切な単体テストを行うのは困難です。単体テストの最初の部分は「単体」です。すべてのユニットは個別にテストできる必要があります。
私の C# プロジェクトでは、完全に独立したデータ層を持つ NHibernate を使用しています。私のオブジェクトはコア ドメイン モデル内に存在し、アプリケーション層からアクセスされます。アプリケーション層は、データ層とドメイン モデル層の両方と通信します。
アプリケーション層は「ビジネス層」と呼ばれることもあります。
PHP を使用している場合は、特定のクラスのセットを作成します。 のみ データアクセス。オブジェクトがどのように永続化されるかを認識しないようにし、アプリケーション クラスで 2 つを結び付けてください。
別のオプションは、モッキング/スタブを使用することです。
データベースにアクセスしてオブジェクトを単体テストする最も簡単な方法は、トランザクション スコープを使用することです。
例えば:
[Test]
[ExpectedException(typeof(NotFoundException))]
public void DeleteAttendee() {
using(TransactionScope scope = new TransactionScope()) {
Attendee anAttendee = Attendee.Get(3);
anAttendee.Delete();
anAttendee.Save();
//Try reloading. Instance should have been deleted.
Attendee deletedAttendee = Attendee.Get(3);
}
}
これにより、基本的にトランザクションのロールバックと同様にデータベースの状態が元に戻されるため、副作用なしに何度でもテストを実行できます。私たちはこのアプローチを大規模なプロジェクトでうまく活用してきました。私たちのビルドの実行には少し時間がかかります (15 分) が、1800 個の単体テストがあるので、それほどひどいことではありません。また、ビルド時間が懸念される場合は、複数のビルドを行うようにビルド プロセスを変更できます。1 つは src のビルド用で、もう 1 つはその後起動して単体テスト、コード分析、パッケージ化などを処理します。
クラスを単体テストする場合は、データベース アクセスをモックする必要があります。結局のところ、単体テストでデータベースをテストする必要はありません。それは統合テストになります。
呼び出しを抽象化し、期待されるデータのみを返すモックを挿入します。ただし、クラスがクエリの実行以外のことを行わない場合は、テストする価値さえないかもしれません...
おそらく、大量の「ビジネス ロジック」SQL 操作を含む中間層プロセスの単体テストを検討し始めたときの経験を少しお見せできるかもしれません。
まず、適切なデータベース接続を「スロットイン」できる抽象化レイヤーを作成しました (この場合、単一の ODBC タイプの接続をサポートしただけです)。
これを配置すると、コード内で次のようなことを実行できるようになります (C++ で作業しますが、アイデアは理解できると思います)。
GetDatabase().ExecuteSQL( "INSERT INTO foo (なんとか、何とか)" )
通常の実行時には、GetDatabase() は、すべての SQL (クエリを含む) を ODBC 経由でデータベースに直接供給するオブジェクトを返します。
次に、インメモリ データベースに注目し始めました。これまでのところ、最も優れているのは SQLite のようです。(http://www.sqlite.org/index.html)。セットアップと使用が非常に簡単で、GetDatabase() をサブクラス化してオーバーライドして、実行されるテストごとに作成および破棄されるメモリ内データベースに SQL を転送できます。
まだ初期段階にありますが、今のところ順調に見えています。ただし、必要なテーブルを作成し、テスト データを入力する必要があります。ただし、ここでは、これらすべてを私たちに代わって実行できるヘルパー関数の汎用セット。
全体として、これは TDD プロセスに非常に役立ちました。特定のバグを修正するためにまったく無害に見える変更を行うと、SQL/データベースの性質上、システムの他の (検出が難しい) 領域に非常に奇妙な影響を与える可能性があるためです。
明らかに、私たちの経験は C++ 開発環境を中心に行ってきましたが、おそらく PHP/Python でも同様のものを動作させることができると思います。
お役に立てれば。
本 xUnit テスト パターン データベースにヒットする単体テスト コードを処理するいくつかの方法について説明します。遅いからやりたくないと言っている他の人たちには同意しますが、いつかはやらなければなりません、私の意見では。データベース接続をモックアウトして高レベルのものをテストすることは良い考えですが、実際のデータベースと対話するために実行できることについての提案については、本書を参照してください。
オプションは次のとおりです。
- 単体テストを開始する前にデータベースを消去するスクリプトを作成し、事前定義されたデータのセットをデータベースに入力してテストを実行します。すべてのテストの前にこれを実行することもできます。時間はかかりますが、エラーが発生する可能性は低くなります。
データベースを注入します。(疑似 Java の例ですが、すべての OO 言語に適用されます)
class Database { public Result query(String query) {... real db here ...} }
現在、運用環境では通常のデータベースを使用し、すべてのテストにはアドホックに作成できるモック データベースを挿入するだけです。class mockdatabase拡張データベース{public result query(string query){return "mock result";}}
class ObjectThatusESDB {public ObjectThatusESDB(データベースdb){this.database = db;}}
- ほとんどのコードでは DB をまったく使用しないでください (いずれにせよ、これは悪い習慣です)。結果を返す代わりに通常のオブジェクトを返す「データベース」オブジェクトを作成します(つまり、戻ります
User
タプルの代わりに{name: "marcin", password: "blah"}
)すべてのテストをアドホックに構築して作成します 本物 オブジェクトを作成し、この変換が正常に機能することを確認するデータベースに依存する 1 つの大きなテストを作成します。
もちろん、これらのアプローチは相互に排他的ではなく、必要に応じて組み合わせて使用できます。
私は通常、オブジェクト (および存在する場合は ORM) のテストとデータベースのテストの間にテストを分割しようとします。私はデータアクセス呼び出しをモックすることでオブジェクト側をテストしますが、データベース側はデータベースとのオブジェクトの相互作用をテストすることでテストしますが、これは私の経験では通常かなり制限されています。
以前は、データ アクセス部分のモックを開始するまで単体テストの作成にイライラしていました。そのため、テスト データベースを作成したり、その場でテスト データを生成したりする必要がなくなりました。データをモックすることで、実行時にデータをすべて生成し、オブジェクトが既知の入力で適切に動作することを確認できます。
PHP でこれを実行したことはありませんし、Python を使用したこともありませんが、やりたいことはデータベースへの呼び出しを模擬することです。そのためには、いくつかを実装できます IoC サードパーティのツールであっても、自分で管理する場合でも、データベース呼び出し元の模擬バージョンを実装して、その偽の呼び出しの結果を制御することができます。
単純な形式の IoC は、インターフェイスにコーディングするだけで実行できます。これには、コード内で何らかのオブジェクト指向が行われている必要があるため、あなたのやっていることには当てはまらないかもしれません(PHP と Python について言及するだけなので、そう言います)
これがお役に立てば幸いです。少なくとも、今検索する用語がいくつかあります。
私は最初の投稿に同意します。データベースへのアクセスは、インターフェースを実装する DAO 層に取り除かれるべきです。次に、DAO レイヤーのスタブ実装に対してロジックをテストできます。
使用できます モックフレームワーク データベース エンジンを抽象化します。PHP/Python にいくつかの機能があるかどうかはわかりませんが、型付き言語 (C#、Java など) には選択肢がたくさんあります。
また、以前の投稿で述べたように、一部の設計は他の設計よりも単体テストが容易であるため、データベース アクセス コードをどのように設計したかによっても異なります。
プロジェクト全体で高い凝集性と疎結合があれば、データベース アクセスの単体テストは十分に簡単です。こうすることで、すべてを一度にテストすることなく、各特定のクラスが実行することのみをテストできます。
たとえば、ユーザー インターフェイス クラスを単体テストする場合、作成するテストでは、その関数の背後にあるビジネス ロジックやデータベース アクションではなく、UI 内のロジックが期待どおりに動作することのみを検証する必要があります。
実際のデータベース アクセスを単体テストしたい場合は、ネットワーク スタックとデータベース サーバーに依存するため、実際にはより多くの統合テストが必要になりますが、SQL コードが要求どおりに動作することを確認できます。する。
私個人にとって単体テストの隠れた力は、単体テストを使用しない場合よりもはるかに優れた方法でアプリケーションを設計できることです。これは、「この関数ですべてを実行する必要がある」という考え方から抜け出すのに非常に役立ったからです。
申し訳ありませんが、PHP/Python の具体的なコード例はありませんが、.NET の例を見たい場合は、 役職 これは、私がこれと同じテストを行うために使用したテクニックを説明しています。
単体テスト用のテスト データをセットアップするのは困難な場合があります。
Java に関しては、単体テストに Spring API を使用すると、トランザクションを単体レベルで制御できます。つまり、データベースの更新/挿入/削除を伴う単体テストを実行し、変更をロールバックできます。実行の最後には、データベース内のすべてのものを、実行を開始する前と同じ状態のままにします。私にとって、それは可能な限り良いことです。