14

私は MsTest と Moq で最初の一歩を踏み出しており、Linq2SQL リポジトリ クラスを単体テストしたいと考えています。問題は、単体テストで開発データベースを永続的に変更したくないことです。

このシナリオに最適なアプローチはどれですか?

  • 各テストが実際の開発データベースで動作するようにしますが、各テストが終了したら必ずクリーンアップするようにしてください
  • 単体テスト用に開発データベースと dbml の複製を作成し、代わりにそのコンテキストを使用して、各テストを実行する前にデータベース全体をクリアできるようにします。
  • Datacontext をモックする精巧な方法を見つけてください (私は完全な Moq 初心者であることを覚えておいてください)。
  • 全然違うもの?おそらく、各テストの実行前にデータベースのセットアップを自動化するものでしょうか?

編集: MBUnit には、テスト ケースによって実行されたデータベース操作を元に戻すロールバック属性があることを知りました。私は特に MSTest に愛着があるわけではないので、これは私の問題に対する簡単な答えでしょうか?

4

3 に答える 3

14

いくつかのラッパー クラス + http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspxに基づく偽の実装を使用して、データベースをモック/偽造しました。エンティティの部分クラス実装で検証ロジックをテストするために、偽のデータ コンテキスト ラッパーに SubmitChanges ロジックを実装することになったことに注意してください。これが、Tokeley の実装と大きく異なる唯一のトリッキーな部分だったと思います。

以下に FakeDataContextWrapper の実装を含めます。

public class FakeDataContextWrapper : IDataContextWrapper
{

    public DataContext Context
    {
        get { return null; }
    }

    private List<object> Added = new List<object>();
    private List<object> Deleted = new List<object>();

    private readonly IFakeDatabase mockDatabase;

    public FakeDataContextWrapper( IFakeDatabase database )
    {
        mockDatabase = database;
    }

    protected List<T> InternalTable<T>() where T : class
    {
        return (List<T>)mockDatabase.Tables[typeof( T )];
    }

    #region IDataContextWrapper Members

    public virtual IQueryable<T> Table<T>() where T : class
    {
        return mockDatabase.GetTable<T>();
    }

    public virtual ITable Table( Type type )
    {
        return new FakeTable( mockDatabase.Tables[type], type );
    }

    public virtual void DeleteAllOnSubmit<T>( IEnumerable<T> entities ) where T : class
    {
        foreach (var entity in entities)
        {
            DeleteOnSubmit( entity );
        }
    }

    public virtual void DeleteOnSubmit<T>( T entity ) where T : class
    {
        this.Deleted.Add( entity );
    }

    public virtual void InsertAllOnSubmit<T>( IEnumerable<T> entities ) where T : class
    {
        foreach (var entity in entities)
        {
            InsertOnSubmit( entity );
        }
    }

    public virtual void InsertOnSubmit<T>( T entity ) where T : class
    {
        this.Added.Add( entity );
    }

    public virtual void SubmitChanges()
    {
        this.SubmitChanges( ConflictMode.FailOnFirstConflict );
    }

    public virtual void SubmitChanges( ConflictMode failureMode )
    {
        try
        {
            foreach (object obj in this.Added)
            {
                MethodInfo validator = obj.GetType().GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
                if (validator != null)
                {

                    validator.Invoke( obj, new object[] { ChangeAction.Insert } );
                }
                this.mockDatabase.Tables[obj.GetType()].Add( obj );
            }

            this.Added.Clear();

            foreach (object obj in this.Deleted)
            {
                MethodInfo validator = obj.GetType().GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
                if (validator != null)
                {
                    validator.Invoke( obj, new object[] { ChangeAction.Delete } );
                }
                this.mockDatabase.Tables[obj.GetType()].Remove( obj );
            }

            this.Deleted.Clear();

            foreach (KeyValuePair<Type, IList> tablePair in this.mockDatabase.Tables)
            {
                MethodInfo validator = tablePair.Key.GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
                if (validator != null)
                {
                    foreach (object obj in tablePair.Value)
                    {
                        validator.Invoke( obj, new object[] { ChangeAction.Update } );
                    }
                }
            }
        }
        catch (TargetInvocationException e)
        {
            throw e.InnerException;
        }
    }

    public void Dispose() { }

    #endregion
}
于 2009-04-07T17:37:44.960 に答える
2

Linq から Sql へのクラスを単体テストするという同様のニーズがあったため、クエリにモック データ コンテキスト、ITables、および IQueryables を取得するための小さなクラス セットを作成しました。

このコードは、ブログ記事「Mock and Stub for Linq to Sql」に掲載しました。Moqを使用しており、データベースにアクセスすることなく、必要なテストに十分な機能を提供する可能性があります.

于 2009-04-29T12:46:19.087 に答える
1

MBUnit を少し使ってみたところ、ほとんどのテスト ケースでは、MBUnit の [ROLLBACK] 属性を使用してデータ コンテキストをモックしなくても済むことがわかりました。

残念ながら、データベースから linq エンティティをロードし、(submitchanges なしで) 1 つのプロパティを変更し、同じエンティティを再度ロードするなど、属性が奇妙な副作用を引き起こす場合もあります。通常、これによりデータベースに対する更新クエリは発生しませんが、テスト メソッド内からは、linq エンティティ プロパティを変更するとすぐに更新が実行されるように見えます。

完璧な解決策ではありませんが、[ROLLBACK] 属性を使用する方が手間がかからず、私にとっては十分に機能するため、この属性を使用すると思います。

于 2009-04-09T15:39:53.097 に答える