20

我一直在尝试寻找一种有效的方法来在 C# 中对我的数据访问层进行单元测试。我是一名初级 Java 开发人员,只使用 C# 大约 6 个月,过去我使用一个名为 DBUnit 的库来针对已知状态数据库进行测试。我还没有找到可以使用的类似活动库,最接近的似乎是 nDBUnit,但它已经有一段时间没有活动了。

关于 C# 中的方式和原因似乎有很多相互矛盾的方法。理想情况下,我想使用模拟测试数据访问层,而无需连接到数据库,然后在单独的一组测试中对存储过程进行单元测试。

在我正在处理的系统中,数据访问层是使用 ADO.net(不使用实体框架)来调用 SQL Server 上的存储过程。

以下是我必须使用的示例代码;要走模拟路径,我必须能够模拟 SqlCommand(使用 IDbCommand)和/或模拟 SqlConnection。

所以我的问题是什么似乎是最好的方法(如果有这样的事情)来做到这一点?到目前为止,唯一的方法是制作传递给构造函数的 Proxy 对象,以便它可以返回模拟的 Sql* 对象进行测试。

我还没有机会查看所有可用的 C# 模拟库。

public class CustomerRepository : ICustomerRepository
{
   private string connectionString;

   public CustomerRepository (string connectionString)
   {
     this.connectionString = connectionString;
   }

   public int Create(Customer customer)
   {

     SqlParameter paramOutId = new SqlParameter("@out_id", SqlDbType.Int);
     paramOutId.Direction = ParameterDirection.Output;
     List<SqlParameter> sqlParams = new List<SqlParameter>()
     {
       paramOutId,
       new SqlParameter("@name", customer.Name)
     }

     SqlConnection connection = GetConnection();
     try
     {
       SqlCommand command = new SqlCommand("store_proc_name", connection);

       command.CommandType = CommandType.StoredProcedure;

       command.Parameters.AddRange(sqlParams.ToArray());

       int results = command.ExecuteNonQuery();

       return (int) paramOutId.Value;
     }
     finally
     {
       CloseConnection(connection);
     }

   }

}
4

4 回答 4

26

不幸的是,您找不到将数据库置于已知状态并允许您针对数据库运行 CustomerRepository 以测试 CustomerRepository 的工具。但是,答案不是开始使用模拟来模拟所有的 ADO 调用。通过这样做,您最终会创建一个不真正测试任何逻辑的单元测试:它只是测试代码是否按照您认为应该编写的方式编写。

假设我最终编写了一个 SQL INSERT 作为在 SQL 数据库中创建客户的命令。现在假设我们正在进行更改,以便客户表具有不同的字段(这会破坏我们的 INSERT 命令),现在我们应该使用存储过程来创建客户。使用模拟的测试仍然可以通过,即使它正在测试的实现现在已经被破坏了。此外,如果您将实现修复为使用存储过程,您的单元测试现在将失败。如果单元测试在应该失败时继续通过,但在您修复系统时会失败,那么单元测试的意义何在?

有关一些可能的替代方案,请参阅此问题。看起来标记的答案实际上是最终使用 IKVM 在 C# 中使用 DBUnit。

因此,可能还有其他途径可以继续探索,但模拟 ADO 调用只会导致脆弱的测试,而这些测试并不能真正测试任何重要的东西。

于 2013-02-24T06:40:22.597 回答
3

这一层的工作是将代码连接到数据库。它必须封装有关数据库连接和语法的知识。通常将领域语言映射到数据库语言。我将单元测试的这一部分视为集成测试,因此我测试数据库模式是否与真实数据库或测试数据库等效。更多关于这里的主题。

于 2013-02-24T05:15:35.437 回答
1

要测试 DataAccess Layer,您将需要一个更复杂的结构。

DataAccess Layer 将从 Repository 对象调用引用。Repo 对象将通过 UnitOfWork 设计模式从 Entity Framework DbSets 调用引用。

数据访问层 (TOP)
|
工作单位
|
存储库模式类
|
英孚语境
|
实际数据库

设置结构后,您将模拟存储库类。例如,项目将被插入到数据库中,而不是进入模拟对象。稍后您将针对您的模拟对象断言以查看是否插入了项目。

请看一下实现存储库和工作单元模式

于 2017-06-06T14:31:32.943 回答
0

为了编写单元测试用例,您将不得不使用 DB 接口方法并模拟这些接口方法。我们不能模拟预定义的类,因此您将不得不使用接口。如果您认为您需要任何应该属于预定义类的命令或数据读取器实例,请编写一个接口方法,在其中对接口实例进行类型转换,将其更改为类类型,使用类属性并再次返回接口实例。例如,如果您需要 oracleCommand,请创建 IDBCommand 并将 IDBCommand 传递给您的接口并更改类型,使用它并返回它。

防爆代码,

 public interface IDbConnectionFactory
    {
        IDbConnection GetConnection();
        IDbCommand BindByName(IDbCommand dbCommand);
    }


public class DbConnectionFactory : IDbConnectionFactory
{

    public IDbConnection GetConnection()
    {
        try
        {
            var connection = new SQLConnection();

            var connection = connection.CreateConnection();
            if (connection != null)
            {
                connection.ConnectionString = _connectionString;
                return connection;
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        return null;
    }

    public IDbCommand BindByName(IDbCommand dbCommand)
    {
        var command = (SQLCommand)dbCommand;
        command.BindByName = true;
        return command;
    }
}

//现在写你的数据库测试用例

public void CustomerRepository_Create_Returns_Int()
{

    var customer = new Customer(); //Initialise values of customer here
    var _refCustomerRepository = new CustomerRepository();
    var _mockDBConnection = new Mock<IDbConnection>();

    var _mockDBCommand = new Mock<IDbCommand>();

    var _mockDBConnectionFactory = new Mock<IDbConnectionFactory>();

   

    _mockDBConnectionFactory.Setup(c =>
        c.GetConnection()).Returns(_mockDBConnection.Object);

    _mockDBConnection.Setup(m => 
        m.CreateCommand()).Returns(_mockDBCommand.Object);

    // for out value. Rename "@out_id" to "out_id". 
    _mockDBCommand.Setup(m => m.Parameters.Add(ParameterDirection.Output));
    // Rename ur paramter from "@name" to "name", it'll work
    _mockDBCommand.Setup(m => m.Parameters.Add("name"));

    _mockDBCommand.Setup(m => m.Parameters.AddRange(5));

    _mockDBCommand.Setup(m => m.ExecuteNonQuery()).Returns(1);

    var result = _refCustomerRepository.Create(saveCustomerSettings);

    //  Assert your result here

}

// 用于测试执行阅读器

var _mockDataReader = new Mock<IDataReader>();

    _mockDataReader.SetupSequence(_ => _.Read())
                      .Returns(true)
                      .Returns(false);

// Do this for all columns that a query/SP returns 
   _mockDataReader.Setup(x => x["COLUMN_NAME"]).Returns("RETURN_VALUE");
   _mockDataReader.Setup(x => x["COLUMN_NAME"]).Returns("RETURN_VALUE"); 
   _mockDataReader.Setup(x => x["COLUMN_NAME"]).Returns("RETURN_VALUE");

例如,如果返回具有 FIRST_NAME、LAST_NAME、EMAIL 等属性的客户

   _mockDataReader.Setup(x => 
            x["FIRST_NAME"]).Returns("giveAnyValueYouWantThisColumnToReturn");
   _mockDataReader.Setup(x => 
            x["LAST_NAME"]).Returns("giveAnyValueYouWantThisColumnToReturn"); 
   _mockDataReader.Setup(x => 
            x["EMAIL"]).Returns("giveAnyValueYouWantThisColumnToReturn");
于 2022-02-01T08:28:52.147 回答