【问题标题】:c# unit testing, mocking stored procedurec#单元测试,模拟存储过程
【发布时间】:2016-08-15 12:14:50
【问题描述】:

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

但是,我无法弄清楚如何使用 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
}

那么我如何模拟存储过程输出,以便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
                }
            }
        }
    }
}

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

【问题讨论】:

    标签: c# unit-testing stored-procedures mocking system.data


    【解决方案1】:

    我认为我们拥有所有接口(@98​​7654321@、IDbTransactionIDbCommandIDataReader)并借用 EF(IDbConnectionFactory)的想法来抽象所需的一切,以便可以模拟和使用它们依赖注入。我认为SqlConnection 和其余的更多的是实现细节,可以抽象出来。

    根据 Entity Framework 的想法,您可以创建一个连接工厂

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

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

    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;
        }
    }
    

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

    [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
            //...
        }
    }
    

    【讨论】:

    • 谢谢,看起来它会起作用,如果我没看错的话,我需要模拟的唯一方法是 IDbCommand.ExecuteReader() [我还需要模拟 IDbConnection。 CreatCommand 返回模拟的 IDbCommand 但这应该是微不足道的]。如果其他人有任何其他想法,我将暂时保留这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 2011-04-11
    • 2011-09-15
    • 2014-06-25
    相关资源
    最近更新 更多