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


9

私は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
}

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

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

  • Deletes everything» Insert defaults» Insert test data»テストを実行しますか?
  • Insert defaults» Insert test data»テストを実行» Delete everything

  • 現在

    • (セッションごと)Deletes everything»Insert defaults
    • (テストごと)Insert test data»テストを実行»Delete test data

回答:


7

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

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

ほとんどの場合、複数のテストで同じデータを再利用できるため、すべてのテストで挿入/削除を行うのではなく、プラスになります。戻ってNUnitのをFixtureSetupFixtureTeardown一度に複数のテストのための設定データに助けを属性。

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


8

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

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


さて、それは今の私たちにとって少しの変更です(3か月前に単体テストを開始しました)。とりあえず、実際のデータベースをテストに使用すると仮定すると、これを実行する標準/安全な順序は何ですか?すべてを削除し、すべてを挿入してから、テストを実行しますか?
dialex 2016年

1
再構築が問題外である場合、-あなたの場合-@JonRaynorの答えがあなたの最良のオプションを提供します。
David Arno、2016年

5

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

通常の解決策はするいないあなたのユニットテストで実際のデータベースを使用し、その代わりに、データベースを模擬または容易に試験の間に、完全に払拭することができるインメモリデータベースを使用します。
データベースと直接対話するコードをテストする場合、またはエンドツーエンドのテストでのみ、実際のデータベースが使用されます。


5

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回実行され、すべてのテストが実行された後にティアダウンでデータを削除またはロールバックします。

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