2

私は、サードパーティベンダーのSQLiteデータテーブルからすべての行をプルし、それらのレコードからビジネスオブジェクトを作成し、新しいビジネスオブジェクトを別のクラスに送信するという任務を負っています。

擬似コード:

var databasePath = "%user profile%\application data\some3rdPartyVendor\vendor.sqlite"
var connection = OpenSqliteConnection(databasePath);
var allGizmoRecords = connection.Query(...);
var businessObjects = TransformIntoBizObjs(allGizmoRecords);
someOtherClass.HandleNewBizObjs(businessObjects);

私はそれをすべて機能させています。

私の質問は、ユニットをテストできるようにこのクラスを作成するにはどうすればよいですか?

するべきか:

  • リポジトリパターンを使用して、データアクセスをモックアウトします
  • ユニットテストで実際にダミーのSQLiteデータベースを提供する

またはより良いアイデアはありますか?私はC#を使用していますが、この質問は言語に依存しないようです。

4

4 に答える 4

2

以下のようにコードをリファクタリングすることで、テスト専用の Sqlite データベースを非常に簡単に挿入できます。しかし、どのように結果を主張していますか? ビジネス オブジェクトは に渡されsomeOtherClassます。を注入する場合ISomeOtherClass、そのクラスのアクションも表示される必要があります。ちょっと痛そうです。

public class KillerApp
{
    private String databasePath;
    private ISomeOtherClass someOtherClass;

    public KillerApp(String databasePath, ISomeOtherClass someOtherClass)
    {
        this.databasePath = databasePath;
        this.someOtherClass = someOtherClass;
    }

    public void DoThatThing()
    {
        var connection = OpenSqliteConnection(databasePath);
        var allGizmoRecords = connection.Query(...);
        var businessObjects = TransformIntoBizObjs(allGizmoRecords);
        someOtherClass.HandleNewBizObjs(businessObjects);
    }
}

[TestClass]
public class When_Doing_That_Thing
{
    private const String DatabasePath = /* test path */;
    private ISomeOtherClass someOtherClass = new SomeOtherClass();
    private KillerApp app;

    [TestInitialize]
    public void TestInitialize()
    {
        app = new KillerApp(DatabasePath, someOtherClass);
    }

    [TestMethod]
    public void Should_convert_all_gizmo_records_to_busn_objects()
    {
        app.DoThatThing();
        Assert.AreEqual(someOtherClass.Results, /* however you're confirming */);
    }
}

を使用すると、このクラスからコードの一部が削除されるため、実装IRepositoryをモックしたり、テスト用に偽装したりできます。IRepository

public class KillerApp
{
    private IRepository<BusinessObject> repository;
    private ISomeOtherClass someOtherClass;

    public KillerApp(IRepository<BusinessObject> repository, ISomeOtherClass someOtherClass)
    {
        this.repository = repository;
        this.someOtherClass = someOtherClass;
    }

    public void DoThatThing()
    {
        BusinessObject[] entities = repository.FindAll();
        someOtherClass.HandleNewBizObjs(entities);
    }
}

[TestClass]
public class When_Doing_That_Thing
{
    private const String DatabasePath = /* test path */;
    private IRepository<BusinessObject> repository;
    private ISomeOtherClass someOtherClass = new SomeOtherClass();
    private KillerApp app;

    [TestInitialize]
    public void TestInitialize()
    {
        repository = new BusinessObjectRepository(DatabasePath);
        app = new KillerApp(repository, someOtherClass);
    }

    [TestMethod]
    public void Should_convert_all_gizmo_records_to_busn_objects()
    {
        app.DoThatThing();
        Assert.AreEqual(someOtherClass.Results, /* however you're confirming */);
    }
}

しかし、これはまだかなり面倒に感じます。理由は 2 つあります。1)最近、Ayendeから、Repository パターンが悪い評判を受け います。そして 2)独自のデータアクセスを書いて何をしているのですか!? NHibernateActiveRecordを使用してください。

[ActiveRecord] /* You define your database schema on the object using attributes */
public BusinessObject
{
    [PrimaryKey]
    public Int32 Id { get; set; }

    [Property]
    public String Data { get; set; }

    /* more properties */
}

public class KillerApp
{
    private ISomeOtherClass someOtherClass;

    public KillerApp(ISomeOtherClass someOtherClass)
    {
        this.someOtherClass = someOtherClass;
    }

    public void DoThatThing()
    {
        BusinessObject[] entities = BusinessObject.FindAll() /* built-in ActiveRecord call! */
        someOtherClass.HandleNewBizObjs(entities);
    }
}

[TestClass]
public class When_Doing_That_Thing : ActiveRecordTest /* setup active record for testing */
{
    private ISomeOtherClass someOtherClass = new SomeOtherClass();
    private KillerApp app;

    [TestInitialize]
    public void TestInitialize()
    {
        app = new KillerApp(someOtherClass);
    }

    [TestMethod]
    public void Should_convert_all_gizmo_records_to_busn_objects()
    {
        app.DoThatThing();
        Assert.AreEqual(someOtherClass.Results, /* however you're confirming */);
    }
}

その結果、クラスが大幅に小さくなり、ビジネス オブジェクトとデータ層がより簡単に変更できるようになります。また、データベース呼び出しをモックする必要さえありません。ActiveRecord を構成および初期化して、テスト データベース (メモリ内でも) を使用することができます。

于 2009-06-04T20:47:01.310 に答える
1

ここで実際にテストする必要があるのは TransformIntoBizObjs だけです。接続コードは別の場所で記述/テストする必要があるためです。表示される可能性のあるものを単に Transform に渡し、正しいものが飛び出すかどうかを確認するだけで済みます。

Transform のすべてのユースケースをテストすることを忘れないでください。おそらく関数呼び出しで終わるべきではないが、そうなる可能性のある奇妙な項目であってもテストしてください。人々が自分のデータベースに何を押し込んでいるのかは決してわかりません。

于 2009-06-04T19:51:38.117 に答える
0

制御の反転 (IoC) と依存性注入 (DI) は、コードをよりテストしやすくするために大いに役立ちます。これを支援するフレームワークはたくさんありますが、目的のためには必ずしもすべての努力をする必要はありません。

次のようなインターフェイスを抽出することから始めます。

Interface ISqlLiteConnection
{
     public IList<GizmoRecord> Query(...);

}

それが完了したら、具体的な実装ではなく、ISqlLiteConnection のインスタンスを返すように OpenSqlLiteConnection() メソッドをリファクタリングする必要があります。テストするには、インターフェイスを実装するクラスを作成するだけです。これにより、実際の DB クエリと接続が確定的な結果でモックアップされます。

于 2009-06-04T20:35:23.503 に答える
-1

データベースは複雑であり、クエリコードをテストする必要があり、実際のsqliteインスタンスに対してテストする必要があります。そうしないと、まれなsqliteの癖やバグに遭遇していないことを確認できません。

クエリをテストする唯一の方法は実際のsqliteファイルで実行することであり、そのようなファイルをテストに含めるのは非常に簡単なので、「より」テスト可能にするため、または「純粋な」ユニットテスト。

考えられるすべての奇妙なエッジケースをサンプルファイルに追加してください。

于 2009-06-04T20:19:59.353 に答える