166

単体テストは「まったく素晴らしい」、「本当にクール」、「あらゆる点で優れている」と聞いたことがありますが、私のファイルの 70% 以上がデータベース アクセス (一部の読み取りと一部の書き込み) に関係しており、その方法がわかりません。これらのファイルの単体テストを作成します。

私は PHP と Python を使用していますが、データベース アクセスを使用するほとんど/すべての言語に当てはまる質問だと思います。

4

13 に答える 13

88

データベースへの呼び出しをモックアウトすることをお勧めします。モックは基本的に、メソッドを呼び出そうとしているオブジェクトと同じように見えるオブジェクトであり、呼び出し元が利用できる同じプロパティ、メソッドなどを持っているという意味です。ただし、特定のメソッドが呼び出されたときにプログラムされているアクションを実行する代わりに、それを完全にスキップして、結果を返すだけです。その結果は通常、事前に定義されます。

オブジェクトをモッキング用に設定するには、次の疑似コードのように、制御/依存性注入パターンの何らかの反転を使用する必要がある可能性があります。

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)
    }
}

一言で言えば、一般的なモック シナリオです。もちろん、実際のデータベース呼び出しも単体テストしたいと思うでしょう。そのためには、データベースにアクセスする必要があります。

于 2008-08-27T18:30:47.227 に答える
29

理想的には、オブジェクトは永続的に無知であるべきです。たとえば、オブジェクトを返すリクエストを行う「データアクセスレイヤー」が必要です。このようにして、その部分を単体テストから除外するか、分離してテストすることができます。

オブジェクトがデータ層に密結合している場合、適切な単体テストを行うことは困難です。ユニットテストの最初の部分は「ユニット」です。すべてのユニットを個別にテストできる必要があります。

私の c# プロジェクトでは、完全に独立したデータ レイヤーで NHibernate を使用しています。オブジェクトはコア ドメイン モデルに存在し、アプリケーション層からアクセスされます。アプリケーション層は、データ層とドメイン モデル層の両方と通信します。

アプリケーション層は「ビジネス層」と呼ばれることもあります。

PHP を使用している場合は、データ アクセス専用の特定のクラスセットを作成します。オブジェクトがどのように永続化されているかをオブジェクトが認識していないことを確認し、アプリケーション クラスで 2 つを結び付けます。

別のオプションは、モッキング/スタブを使用することです。

于 2008-08-27T17:49:24.407 に答える
13

データベースアクセスを使用してオブジェクトを単体テストする最も簡単な方法は、トランザクションスコープを使用することです。

例えば:

    [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つは後で起動して単体テスト、コード分析、パッケージ化などを処理します。

于 2008-08-27T18:14:54.220 に答える
11

大量の「ビジネス ロジック」SQL 操作を含む中間層プロセスの単体テストを検討し始めたときの経験を少し紹介できると思います。

最初に、合理的なデータベース接続を「スロットイン」できるようにする抽象化レイヤーを作成しました (この場合、単純に単一の ODBC タイプの接続をサポートしていました)。

これが配置されると、コードで次のようなことができるようになりました (私たちは C++ で作業していますが、アイデアはお分かりいただけると思います)。

GetDatabase().ExecuteSQL( "INSERT INTO foo (何とか、何とか)" )

通常の実行時、GetDatabase() は、ODBC 経由でデータベースに直接、すべての SQL (クエリを含む) を供給するオブジェクトを返します。

次に、インメモリ データベースの調査を開始しました。これまでのところ、最も優れているのは SQLite のようです。( http://www.sqlite.org/index.html )。セットアップと使用は非常に簡単で、GetDatabase() をサブクラス化してオーバーライドし、テストが実行されるたびに作成および破棄されたメモリ内データベースに sql を転送することができました。

これはまだ初期段階にありますが、これまでのところ問題ないように見えますが、必要なテーブルを作成してテスト データを入力する必要があります。これらすべてを実行できるヘルパー関数の一般的なセットです。

SQL/データベースの性質上、特定のバグを修正するために非常に無害に見える変更を加えても、システムの他の (検出が困難な) 領域に非常に奇妙な影響を与える可能性があるため、全体として、TDD プロセスに非常に役立ちました。

明らかに、私たちの経験は C++ 開発環境を中心にしていますが、おそらく PHP/Python で同様の作業を行うことができると確信しています。

お役に立てれば。

于 2008-09-06T23:04:23.023 に答える
11

クラスの単体テストを行う場合は、データベース アクセスをモックする必要があります。結局のところ、単体テストでデータベースをテストする必要はありません。それは統合テストになります。

呼び出しを抽象化し、期待されるデータを返すだけのモックを挿入します。クラスがクエリを実行する以上のことをしない場合は、それらをテストする価値さえないかもしれません...

于 2008-08-27T17:49:28.213 に答える
7

xUnitTestPatternsは、データベースにヒットする単体テストコードを処理するいくつかの方法を説明しています。遅いのでやりたくないと言っている他の人たちにも同意しますが、いつかやらなきゃいけない、IMO。db接続をモックアウトして高レベルのものをテストすることは良い考えですが、実際のデータベースと対話するためにできることについての提案については、この本をチェックしてください。

于 2008-08-27T17:59:51.123 に答える
5

私は通常、オブジェクト(および存在する場合はORM)のテストとデータベースのテストの間にテストを分割しようとします。私はデータアクセス呼び出しをモックすることによって物事のオブジェクト側をテストしますが、私の経験では通常かなり制限されているdbとのオブジェクトの相互作用をテストすることによって物事のdb側をテストします。

データアクセス部分をモックし始めるまで、単体テストを書くことに不満を感じていたので、テストデータベースを作成したり、その場でテストデータを生成したりする必要はありませんでした。データをモックすることで、実行時にすべてのデータを生成し、オブジェクトが既知の入力で正しく機能することを確認できます。

于 2008-08-27T17:51:40.610 に答える
4

あなたが持っているオプション:

  • 単体テストを開始する前にデータベースを消去するスクリプトを作成し、dbに事前定義されたデータセットを入力してテストを実行します。すべてのテストの前にこれを行うこともできます。速度は遅くなりますが、エラーが発生しにくくなります。
  • データベースを注入します。(疑似Javaの例ですが、すべてのオブジェクト指向言語に適用されます)

    クラスデータベース{
     public Result query(String query){... real db here ...}
    }

    クラスMockDatabaseはデータベースを拡張します{ public Result query(String query){ 「模擬結果」を返します。 } }

    クラスObjectThatUsesDB{ public ObjectThatUsesDB(Database db){ this.database = db; } }

    現在、本番環境では通常のデータベースを使用し、すべてのテストで、アドホックに作成できるモックデータベースを挿入するだけです。

  • ほとんどのコードでDBを使用しないでください(とにかくそれは悪い習慣です)。結果を返すUser代わりに通常のオブジェクトを返す(つまり、タプルの代わりに返す{name: "marcin", password: "blah"})「データベース」オブジェクトを作成します。アドホックに構築された実際のオブジェクトを使用してすべてのテストを記述し、この変換を確認するデータベースに依存する1つの大きなテストを記述します。正常に動作します。

もちろん、これらのアプローチは相互に排他的ではなく、必要に応じてそれらを組み合わせることができます。

于 2008-08-27T18:11:37.787 に答える
4

プロジェクト全体の凝集度が高く、結合度が低い場合、データベースアクセスの単体テストは非常に簡単です。このようにして、すべてを一度にテストすることなく、特定の各クラスが実行することだけをテストできます。

たとえば、ユーザーインターフェイスクラスを単体テストする場合、作成するテストでは、UI内のロジックが期待どおりに機能することを確認するだけで、その機能の背後にあるビジネスロジックやデータベースアクションは確認できません。

実際のデータベースアクセスを単体テストする場合は、ネットワークスタックとデータベースサーバーに依存するため、実際には統合テストが多くなりますが、SQLコードが要求どおりに機能することを確認できます。行う。

個人的にユニットテストの隠れた力は、アプリケーションがない場合よりもはるかに優れた方法でアプリケーションを設計することを余儀なくされることです。これは、「この関数はすべてを実行する必要がある」という考え方から脱却するのに本当に役立ったためです。

申し訳ありませんが、PHP / Pythonの特定のコード例はありませんが、.NETの例を見たい場合は、これとまったく同じテストを行うために使用した手法を説明する投稿があります。

于 2008-08-27T18:00:38.500 に答える
3

モックフレームワークを使用して、データベースエンジンを抽象化できます。PHP / Pythonがいくつかあるかどうかはわかりませんが、型付き言語(C#、Javaなど)にはたくさんの選択肢があります

また、以前の投稿で述べたように、一部の設計は他の設計よりも単体テストが簡単であるため、これらのデータベースアクセスコードをどのように設計したかにも依存します。

于 2008-08-27T17:51:47.320 に答える
3

私はこれをPHPで行ったことがなく、Pythonを使用したこともありませんが、データベースへの呼び出しをモックアウトする必要があります。これを行うには、サードパーティのツールであるか自分で管理するかにかかわらず、 IoCを実装できます。次に、偽の呼び出しの結果を制御するデータベース呼び出し元のモックバージョンを実装できます。

IoCの単純な形式は、インターフェイスにコーディングするだけで実行できます。これには、コード内で何らかのオブジェクト指向が行われている必要があるため、あなたの行動には当てはまらない可能性があります(私が続けなければならないのは、PHPとPythonについての言及だけです)。

それがお役に立てば幸いです。他に検索する用語がない場合は。

于 2008-08-27T17:55:53.377 に答える
3

私は最初の投稿に同意します-データベースアクセスは、インターフェイスを実装するDAOレイヤーに取り除かれる必要があります。次に、DAOレイヤーのスタブ実装に対してロジックをテストできます。

于 2008-08-27T17:56:19.320 に答える