如何在单元测试中伪造SqlDataAdapter,SqlConnection和SqlCommand

[英]How to fake SqlDataAdapter, SqlConnection and SqlCommand in unit tests


I'm trying to figure out how I can run unit tests on a class I created to execute queries on a database but I keep running around in circles trying to figure out how to fake all the dependencies. My class implements IDisposable which closes the SqlConnection if open. A typical method (I have several overloaded) for selecting data looks like this:

我试图找出如何在我创建的类上运行单元测试以在数据库上执行查询,但我一直在试图弄清楚如何伪造所有依赖项。我的类实现了IDisposable,如果打开则关闭SqlConnection。用于选择数据的典型方法(我有几个重载)如下所示:

public DataSet SelectData(string selectCommand)
    {
        if (!string.IsNullOrEmpty(selectCommand))
        {
            DataSet ds = new DataSet();
            ds.Locale = CultureInfo.InvariantCulture;
            SqlDataAdapter adapter = new SqlDataAdapter(selectCommand, Connection);
            adapter.Fill(ds);
            return ds;
        }
        throw new ArgumentException("SelectCommand was null or empty", "selectCommand");
    }

Note that the Connection parameter in the SqlCommand constructor is a property that returns the SqlConnection for this instance of my class. Obviously I need to somehow fake the SqlDataAdapter but that also means I have to fake the SqlCommand and SqlConnection used. All classes are sealed so I can't simply create a fake object that inherits from these classes. Creating a database sort of defeats the purpose of dependency injection so I'd like to avoid that. Does anyone have a suggestion on how to test this method?

请注意,SqlCommand构造函数中的Connection参数是一个属性,它返回此类的实例的SqlConnection。显然我需要以某种方式伪造SqlDataAdapter,但这也意味着我必须假装使用的SqlCommand和SqlConnection。所有类都是密封的,所以我不能简单地创建一个从这些类继承的伪对象。创建数据库排序会破坏依赖注入的目的,所以我想避免这种情况。有没有人建议如何测试这种方法?

2 个解决方案

#1


7  

As a general rule, to mock sealed classes (1) you need a mocking framework that can do it or (2) you need to write (unsealed) wrappers around the sealed class and use/mock those. TypeMock can mock sealed classes, but it costs money. But, beware, the ability to mock sealed classes and other typically non-mockable items, can keep you from having to refactor your code to a better design (assuming you agree that testable code is better code). Wrappers or adapters are relatively easy to write, but they are themselves not testable for precisely the same reason you write them. Because of their simplicity, though, you can often reason that they are correct by inspection.

作为一般规则,要模拟密封类(1),您需要一个可以执行此操作的模拟框架,或者(2)您需要在密封类周围编写(未密封)包装并使用/模拟它们。 TypeMock可以模拟密封的类,但它需要花钱。但是,请注意,模拟密封类和其他通常不可模拟的项目的能力可以使您不必将代码重构为更好的设计(假设您同意可测试代码是更好的代码)。包装器或适配器相对容易编写,但它们本身无法测试,原因与您编写它们的原因完全相同。但是,由于它们的简单性,您通常可以通过检查来证明它们是正确的。

As an aside, you might want to look at more modern data access mechanisms, using an object-relational mapper (ORM), for example. Entity framework, LINQ-to-SQL, nHibernate, Subsonic... all are better choices than writing your own data access layer at a low level in my opinion.

另外,您可能希望使用对象关系映射器(ORM)来查看更现代的数据访问机制。实体框架,LINQ-to-SQL,nHibernate,Subsonic ......在我看来,所有这些都比在低级别编写自己的数据访问层更好。

#2


5  

I think we have all the interfaces (IDbConnection, IDbTransaction, IDbCommand and IDbDataAdapter) to mock everything method we used, by using NSubstitute and Dependency Injection.

我认为我们有所有接口(IDbConnection,IDbTransaction,IDbCommand和IDbDataAdapter)来模拟我们使用的所有方法,使用NSubstitute和Dependency Injection。

//using init class to inject IDbDataAdapter
IDbDataAdapter adapter = init.DbAdapter("command");
adapter.Connection = connection;

As for SqlConnection and SqlCommand

至于SqlConnection和SqlCommand

using (IDbConnection connection = init.DbConnection("connection"))
{
  using (IDbTransaction transaction = connection.BeginTransaction())
  {
    using (IDbCommand command = init.DbCommand("sproc"))
    {  
       command.Transaction = transaction;
       command.Connection = connection;
       ...
    }
  }
}

Unit test will look something like this

单元测试看起来像这样

//arrange
var connection = Substitute.For<IDbConnection>();
var command = Substitute.For<IDbCommand>();
var transaction = Substitute.For<IDbTransaction>();
//this is the init class used before
var init = Substitute.For<ISqlInitializer>();
connection.Open();
connection.BeginTransaction(Arg.Any<IsolationLevel>()).Returns(transaction);
init.DbConnection(Arg.Any<string>()).Returns(connection);
init.DbCommand(Arg.Any<string>()).Returns(command);
var client = new SqlClient(init);

//act
var result = await client.CommandMultipleAsync(new SqlConfiguration(FakeConnection, new List<string> { "testSproc1", "testSproc2" }));

//assert
Assert.AreEqual(0, result);
command.Received(2).ExecuteNonQuery();
transaction.Received(1).Commit();

Please check the proof of concept project on GitHub for detailed usage and also any suggestion or feedback is welcome :p https://github.com/TianyuanC/dals/blob/master/DALs.Sql/SqlClient.cs

请查看GitHub上的概念验证项目以获取详细用法,欢迎提出任何建议或反馈:p https://github.com/TianyuanC/dals/blob/master/DALs.Sql/SqlClient.cs

智能推荐

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:http://www.itdaan.com/blog/2011/03/13/9913e205cfadc9d89059eae5ba781209.html



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告