在为调用这些数据层操作的方法编写单元测试时,我偶然发现了一些问题.
对于单元测试,我需要在事务处理中进行这些操作,因为大多数操作都是根据其性质改变数据,因此在事务处理之外执行它们是有问题的,因为这会改变整个基础数据.因此,我需要围绕这些交易进行交易(最终没有提交发起).
现在我有两个不同的变体,这些BL方法如何工作.
有一些交易本身在他们之内,而其他人根本没有交易.这两种变体都会导致问题.
>分层事务:在这里,我收到错误,DTC取消分布式事务由于超时(尽管超时设置为15分钟,运行只有2分钟).
>只有1个事务:当我在被调用的方法中来到“新的sqlCommand”行时,我会收到有关事务状态的错误.
我的问题是,我可以做些什么来纠正这个问题,并通过手动正常和分层的事务进行单元测试?
单元测试方法示例:
using (sqlConnection connection = new sqlConnection(Properties.Settings.Default.ConnectionString)) { connection.Open(); using (sqlTransaction transaction = connection.BeginTransaction()) { MyBLMethod(); } }
使用方法的交易示例(非常简化)
using (sqlConnection connection = new sqlConnection(Properties.Settings.Default.ConnectionString)) { connection.Open(); using (sqlTransaction transaction = connection.BeginTransaction()) { sqlCommand command = new sqlCommand(); command.Connection = connection; command.Transaction = transaction; command.CommandTimeout = 900; // Wait 15 minutes before a timeout command.CommandText = "INSERT ......"; command.ExecuteNonQuery(); // Following commands .... Transaction.Commit(); } }
非事务使用方法的示例
using (sqlConnection connection = new sqlConnection(Properties.Settings.Default.ConnectionString)) { connection.Open(); sqlCommand command = new sqlCommand(); command.Connection = connection; command.CommandTimeout = 900; // Wait 15 minutes before a timeout command.CommandText = "INSERT ......"; command.ExecuteNonQuery(); }
解决方法
目前,您正在有效地编写集成测试.如果数据库不可用,那么您的测试将失败.这意味着测试可能很慢,但是如果他们通过,那么你的确很有信心,你的代码可以正确地打到数据库.
如果您不介意点击数据库,那么更改代码/支出金额的最小影响将是允许事务完成并在数据库中进行验证.您可以通过采取数据库快照和重置每个测试运行的数据库,或者通过拥有一个专用的测试数据库和编写测试,以便他们可以一次又一次地安全地击中数据库,然后进行验证.因此,例如,您可以使用增加的ID插入记录,更新记录,然后验证是否可以读取记录.如果有错误,您可能会有更多的放松要做,但是如果您不修改数据访问代码或数据库结构,那么这不应该是太多的问题.
如果您能够花费一些钱,并且想要将测试转化为单元测试,这样就不会打到数据库,那么您应该考虑使用TypeMock.这是一个非常强大的嘲笑框架,可以做一些非常可怕的东西.我相信它使用剖析API拦截调用,而不是使用像Moq这样的框架使用的方法.有一个使用Typemock来模拟sqlConnection here的例子.
如果您没有资金花费/您可以更改代码,并且不介意继续依赖数据库,那么您需要查看某种方式在您的测试代码和数据访问方法之间共享数据库连接.要注意的两个方法是将连接信息注入到类中,或者通过注入一个访问连接信息的工厂来使其可用(在这种情况下,您可以在测试期间注入返回连接的工厂模拟你要).
如果您使用上述方法,而不是直接注入sqlConnection,请考虑注入也负责该事务的包装类.就像是:
public class MysqLWrapper : IDisposable { public sqlConnection Connection { get; set; } public sqlTransaction Transaction { get; set; } int _transactionCount = 0; public void BeginTransaction() { _transactionCount++; if (_transactionCount == 1) { Transaction = Connection.BeginTransaction(); } } public void CommitTransaction() { _transactionCount--; if (_transactionCount == 0) { Transaction.Commit(); Transaction = null; } if (_transactionCount < 0) { throw new InvalidOperationException("Commit without Begin"); } } public void Rollback() { _transactionCount = 0; Transaction.Rollback(); Transaction = null; } public void Dispose() { if (null != Transaction) { Transaction.Dispose(); Transaction = null; } Connection.Dispose(); } }
这将阻止嵌套事务被创建提交.
如果您更愿意重新构建代码,那么您可能希望以更可笑的方式将数据访问代码打包.因此,您可以将核心数据库访问功能推送到另一个类中.根据你在做什么,你需要扩展它,但是你可能会遇到这样的事情:
public interface IMyQuery { string GetCommand(); } public class MyInsert : IMyQuery{ public string GetCommand() { return "INSERT ..."; } } class DBNonQueryRunner { public void RunQuery(IMyQuery query) { using (sqlConnection connection = new sqlConnection(Properties.Settings.Default.ConnectionString)) { connection.Open(); using (sqlTransaction transaction = connection.BeginTransaction()) { sqlCommand command = new sqlCommand(); command.Connection = connection; command.Transaction = transaction; command.CommandTimeout = 900; // Wait 15 minutes before a timeout command.CommandText = query.GetCommand(); command.ExecuteNonQuery(); transaction.Commit(); } } } }
这允许您单独测试您的逻辑,如命令生成代码,而无需真正担心击中数据库,您可以针对数据库一次测试核心数据访问代码(Runner),而不是针对所需的每个命令对数据库运行.我仍然会为所有数据访问代码编写集成测试,但是我只会在实际使用该部分代码(以确保列名称等)被正确指定的情况下运行它们.