单元测试数据访问层的方法
本文关键字:方法 访问 测试数据 单元 | 更新日期: 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");