单元测试数据访问层的方法

本文关键字:方法 访问 测试数据 单元 | 更新日期: 2023-09-27 18:00:20

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

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

在我工作的系统中,数据访问层使用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);
     }
   }
}

单元测试数据访问层的方法

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

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

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

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

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

要测试DataAccess层,需要一个更复杂的结构。

DataAccess层将调用存储库对象中的引用。Repo对象将通过UnitOfWork设计模式调用实体框架数据库集中的引用。

数据访问层(TOP)
|
工作单位
|
存储库模式类
|
EF上下文
|
实际数据库

设置结构之后,将模拟存储库类。例如,项目将被插入DB,而不是进入mock对象。稍后,您将对mock对象进行断言,以查看是否插入了项。

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

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

Ex代码,

 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");

对于Ex,如果退货客户具有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");