web-dev-qa-db-ja.com

ダーティデータベースを回避するための統合テスト中のクリーンアップと配置のプラクティス

私はC#でテストをコーディングしていますが、次の構造で解決しました。

try
{
    // ==========
    // ARRANGE
    // ==========

    // Insert into the database all test data I'll need during the test

    // ==========
    // ACT
    // ==========

    // Do what needs to be tested

    // ==========
    // ASSERT
    // ==========

    // Check for correct behavior
}
finally
{
    // ==========
    // CLEANUP
    // ==========

    // Inverse of ARRANGE, delete the test data inserted during this test
}

コンセプトは「すべてのテストがそれが作る混乱をきれいにする」でした。ただし、someテストはデータベースをダーティのままにし、その後のテストに失敗します。

これを行う正しい方法は何ですか?(エラーを最小限に抑え、実行時間を最小限に抑えます)

  • Deletes everythingInsert defaultsInsert test data "テストを実行しますか?
  • Insert defaultsInsert test data "テストを実行" Delete everything

  • 現在

    • (セッションごと) Deletes everythingInsert defaults
    • (テストごと) Insert test data "テストを実行" Delete test data
9
dialex

これは単体テストではなく統合テストであるという事実に加えて、記述した操作は通常、SetupメソッドやTeardownメソッドで行われます。 nUnitのようなフレームワークを使用すると、これらの属性を持つクラスメソッドを装飾して、メソッドがセットアップメソッドであるかティアダウンメソッドであるかを示すことができます。

次に、セットアップとクリーンアップがテスト自体の外で行われるため、テストはよりクリーンで小さくなります。

ほとんどの場合、複数のテストで同じデータを再利用できるため、すべてのテストで挿入/削除を行うのではなく、プラスになります。 nUnitに戻ると、FixtureSetupおよびFixtureTeardown属性は、複数のテストのデータを一度にセットアップするのに役立ちます。

これらのテスト機能の多くはフレームワーク自体に組み込まれているため、try/catchではなくテストフレームワークを使用します。 xUnit、nUnit、さらにはMicrosoftの組み込みテストフレームワークもすべて確実な選択肢であり、一貫した方法でデータベースレコードのセットアップとクリーンアップを支援します。

7
Jon Raynor

このようなテストで目指すべきポイントは、できるだけ多くのテストがデータベース自体ではなく、データベースのモックと相互作用することです。これを実現する標準的な方法は、インターフェイスを使用して、ここでテストするロジックにDBアクセスレイヤーを挿入することです。このようにして、テストコードは各テストの前にメモリ内データセットを作成し、その後それらを破棄できます。その後、すべてのテストを並行して実行でき、互いに影響を与えることはありません。これにより、テストがより速く、より簡単に記述および理解でき、より堅牢になります。

次に、実際のDBアクセス層自体をテストする必要があります。これらのテストは数個しかないため、たとえば、そのテストに固有のテストテーブル(またはデータベース)を作成して、テストデータを入力できます。テストが実行された後、テストテーブル/ DB全体が破棄されます。繰り返しますが、これらのテストは並行して実行できるはずです。したがって、全体的なテスト実行時間に大きな影響を与えることはありません。

8
David Arno

SQL Serverおよび PetaPoco を使用してC#サーバーで作業する場合、これは単体テストでデータをクリーンアップするために採用したアプローチです。

典型的な単体テストでは、次のようにセットアップとティアダウンがあります。

[TestFixture]
internal class PlatformDataObjectTests
{
    private IDatabaseConfiguration _dbConfig;
    private Database _pocoDatabase;
    private PlatformDataObject _platformDto;

    [SetUp]
    public void Setup()
    {
        _dbConfig = new CommonTestsAppConfig().GetDatabaseConfiguration();
        _pocoDatabase = new Database(_dbConfig.ConnectionString, SqlClientFactory.Instance);
        _platformDto = new PlatformDataObject(_pocoDatabase);
        _platformDto.BeginTransaction();
    }

    [TearDown]
    public void TearDown()
    {
        Console.WriteLine("Last Sql: {0}", _pocoDatabase.LastCommand);

        _platformDto.RollbackTransaction();
        _platformDto.Dispose();
    }

    // ... 
}

PlatformDataObjectは、データベースとの通信を担当するクラスです。 Select Insert Update Deletesを実行します。すべての* DataObjectタイプはServerDataObjectを継承します。基本クラスには、トランザクションを中止、ロールバック、またはコミットするためのメソッドがあります。

/// <summary>
/// A Data-Transfer Object which allows creation and querying of Platform types from the database
/// </summary>
[ExportType(typeof(IPlatformDataObject))]
public class PlatformDataObject : ServerDataObject, IPlatformDataObject
{
    private static readonly ILog Log = LogManager.GetLogger(typeof (ProductDataObject));

    private const string PlatformTable = "t_Platform";

    public PlatformDataObject(IPocoDatabase pocoDatabase) : base(pocoDatabase)
    {
    }

    ... 
}

/// <summary>
/// A base Data-Transfer Object type
/// </summary>
public abstract class ServerDataObject : IServerDataObject
{
    protected const string Star = "*";

    private readonly IPocoDatabase _pocoDatabase;

    public ServerDataObject(IPocoDatabase pocoDatabase)
    {
        _pocoDatabase = pocoDatabase;
    }

    public string LastCommand
    {
        get { return PocoDatabase.LastCommand; }
    }

    public IPocoDatabase PocoDatabase
    {
        get { return _pocoDatabase; }
    }

    public int TransactionDepth
    {
        get { return _pocoDatabase.TransactionDepth; }
    }

    public bool TransactionAborted { get; private set; }

    public void BeginTransaction()
    {
        _pocoDatabase.BeginTransaction();
    }

    public void AbortTransaction()
    {
        _pocoDatabase.AbortTransaction();
    }

    public void RollbackTransaction()
    {
        TransactionAborted = true;
    }

    public virtual void Dispose()
    {
        if (TransactionAborted)
            _pocoDatabase.AbortTransaction();
        else
            _pocoDatabase.CompleteTransaction();
    }
}

すべての単体テストはRollbackTransaction()を呼び出し、最終的にIDbTransaction.Rollback()を呼び出します。

テストでは、* DataObjectの新しいインスタンスを作成し、Insertステートメントを使用していくつかの行を作成し、それらに対してテスト(選択、更新など...)を実行してからロールバックするルーチンが見つかりました。

SetUpFixture -すべてのテストが実行される前にクラスが1回実行され、すべてのテストが実行される前に一連のテストデータをセットアップし、すべてのテストが実行された後にティアダウンでデータを削除またはロールバックします。

データベースと(ユニット)テストの大きな問題は、データベースは永続化に非常に優れていることです。

通常の解決策はnotを単体テストで実際のデータベースを使用することですが、代わりにデータベースをモックするか、テスト間で完全にワイプできるメモリ内データベースを使用します。
実際にデータベースが使用されるのは、データベースと直接やり取りするコードをテストする場合、またはエンドツーエンドのテストの場合のみです。