c#单元测试,模拟存储过程

C. *_*ght 7 c# unit-testing stored-procedures mocking system.data

在过去,当我实施单元测试时,我一直在努力为数据访问层设置"体面"的单元测试,因为它们通常将数据库作为外部依赖.在理想的世界中,我会模拟存储过程调用,以便删除外部依赖项.

但是,我一直无法解决如何使用MOQ模拟框架,或找到任何其他支持此框架的框架.相反,我已经恢复创建一个包含已知数据的脚本化测试数据库(这样我总能得到我期望的输出),但这与模拟该层略有不同.

任何人都可以建议如何模拟数据访问层的这一部分[我知道实体框架https://effort.codeplex.com/存在]?


细节 例如,如果我有以下方法

public object RunStoredProc()
{
    //Some Setup

    using (SqlConnection conn = new SqlConnection(CONNNECTION_STRING))
    {
        using (SqlCommand comm = new SqlCommand("storedProcName", conn))
        {
            conn.Open();
            comm.CommandType = CommandType.StoredProcedure;
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                while (reader.Read())
                {
                    //Logic
                }
            }
        }
    }

    //Return object based on logic
}
Run Code Online (Sandbox Code Playgroud)

那么如何模拟存储过程输出以SQLDataReader包含指定的数据.在更高的级别,我可以模拟该RunStoredProc()方法 - 但这无助于我测试该方法中的逻辑是否正确.或者,我可以将其SQLReader剥离成另一种方法

public object RunStoredProc()
{
    //Some Setup

    List<object> data = GetData();
    //Logic

    //Return object based on logic
}

private List<object> GetData()
{
    using (SqlConnection conn = new SqlConnection(CONNNECTION_STRING))
    {
        using (SqlCommand comm = new SqlCommand("storedProcName", conn))
        {
            conn.Open();
            comm.CommandType = CommandType.StoredProcedure;
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                while (reader.Read())
                {
                    //place into return object
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但由于'GetData'方法应该是私有的(不是已发布接口的一部分),因此我无法模拟它,因此问题仍然存在.

Nko*_*osi 4

我认为我们拥有所有接口 ( IDbConnectionIDbTransactionIDbCommandIDataReader) 并借用 EF ( ) 的思想IDbConnectionFactory来抽象所需的所有内容,以便可以模拟它们并将其与依赖注入一起使用。我认为SqlConnection其余的更多的是实现细节并且可以抽象。

遵循实体框架的想法,您可以创建一个连接工厂

public interface IDbConnectionFactory {
    /// <summary>
    ///  Creates a connection based on the given database name or connection string.
    IDbConnection CreateConnection(string nameOrConnectionString);
}
Run Code Online (Sandbox Code Playgroud)

然后您可以重构示例方法以仅使用抽象。

public class MyDataAccessClass {
    private IDbConnectionFactory dbConnectionFactory;
    private string CONNNECTION_STRING = "Connection string here";

    public MyDataAccessClass(IDbConnectionFactory dbConnectionFactory) {
        this.dbConnectionFactory = dbConnectionFactory;
    }

    public object RunStoredProc() {
        //Some Setup
        List<object> result = new List<object>();

        using (IDbConnection conn = dbConnectionFactory.CreateConnection(CONNNECTION_STRING)) {
            using (IDbCommand comm = conn.CreateCommand()) {
                comm.CommandText = "storedProcName";
                conn.Open();
                comm.CommandType = CommandType.StoredProcedure;
                using (IDataReader reader = comm.ExecuteReader()) {
                    while (reader.Read()) {
                        //...Logic to populate result
                    }
                }
            }
        }

        //Return object based on logic
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

从那里,您可以使用您选择的模拟框架来模拟接口,或者创建您自己的伪造品来注入和测试您的方法。

[TestClass]
public class StoredProcedureUnitTest {
    [TestMethod]
    public void TestRunStoredProc() {
        //Arrange
        var connectionFactory = new Mock<IDbConnectionFactory>();
        //..Setup...

        var sut = new MyDataAccessClass(connectionFactory.Object);

        //Act
        var actual = sut.RunStoredProc();

        //Assert
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)