序言:
我设计了一个高度接口(interface)且完全可模拟的数据层类,当在单个事务中应包含多个调用时,该类期望业务层创建 TransactionScope
。
问题:我想对我的业务层在期望时使用TransactionScope
对象进行单元测试。
不幸的是,使用TransactionScope
的标准模式如下:
using(var scope = new TransactionScope())
{
// transactional methods
datalayer.InsertFoo();
datalayer.InsertBar();
scope.Complete();
}
就程序员的可用性而言,这是一个非常不错的模式,但是对它的完成进行测试似乎……对我而言是不可能的。我无法检测到已实例化了一个 transient 对象,更不用说模拟它来确定调用了一个方法。但是我的报道目标意味着我必须这样做。
问题:我该如何构建单元测试,以确保根据标准模式正确使用
TransactionScope
?最终想法:我考虑过一种解决方案,该解决方案当然可以提供所需的覆盖范围,但是由于过于复杂并且不符合标准
TransactionScope
模式而拒绝了它。它涉及在我的数据层对象上添加CreateTransactionScope
方法,该方法返回TransactionScope
的实例。但是,由于TransactionScope包含构造函数逻辑和非虚拟方法,因此很难(即使不是不可能进行模拟),CreateTransactionScope
会将DataLayerTransactionScope
的实例返回到TransactionScope
中,该实例是可模拟的外观。尽管这可能会完成工作,但它很复杂,我希望使用标准模式。有没有更好的办法?
最佳答案
我现在正面临着同样的问题,对我来说似乎有两种解决方案:
编辑:
我现在为此创建了一个CodePlex项目:http://legendtransactions.codeplex.com/
我倾向于创建一组用于处理事务的接口(interface)和一个委派给System.Transaction-implementations的默认实现,例如:
public interface ITransactionManager
{
ITransaction CurrentTransaction { get; }
ITransactionScope CreateScope(TransactionScopeOption options);
}
public interface ITransactionScope : IDisposable
{
void Complete();
}
public interface ITransaction
{
void EnlistVolatile(IEnlistmentNotification enlistmentNotification);
}
public interface IEnlistment
{
void Done();
}
public interface IPreparingEnlistment
{
void Prepared();
}
public interface IEnlistable // The same as IEnlistmentNotification but it has
// to be redefined since the Enlistment-class
// has no public constructor so it's not mockable.
{
void Commit(IEnlistment enlistment);
void Rollback(IEnlistment enlistment);
void Prepare(IPreparingEnlistment enlistment);
void InDoubt(IEnlistment enlistment);
}
这似乎是一项艰巨的工作,但另一方面,它是可重用的,并且使所有测试都非常容易。
请注意,这还不足以完整地定义接口(interface)。
编辑:
我只是做了一些快速而肮脏的实现作为概念验证,我认为这是我将要采取的方向,这是我到目前为止提出的。我在想,也许我应该为此创建一个CodePlex项目,以便可以一劳永逸地解决问题。这不是我第一次遇到这种情况。
public interface ITransactionManager
{
ITransaction CurrentTransaction { get; }
ITransactionScope CreateScope(TransactionScopeOption options);
}
public class TransactionManager : ITransactionManager
{
public ITransaction CurrentTransaction
{
get { return new DefaultTransaction(Transaction.Current); }
}
public ITransactionScope CreateScope(TransactionScopeOption options)
{
return new DefaultTransactionScope(new TransactionScope());
}
}
public interface ITransactionScope : IDisposable
{
void Complete();
}
public class DefaultTransactionScope : ITransactionScope
{
private TransactionScope scope;
public DefaultTransactionScope(TransactionScope scope)
{
this.scope = scope;
}
public void Complete()
{
this.scope.Complete();
}
public void Dispose()
{
this.scope.Dispose();
}
}
public interface ITransaction
{
void EnlistVolatile(Enlistable enlistmentNotification, EnlistmentOptions enlistmentOptions);
}
public class DefaultTransaction : ITransaction
{
private Transaction transaction;
public DefaultTransaction(Transaction transaction)
{
this.transaction = transaction;
}
public void EnlistVolatile(Enlistable enlistmentNotification, EnlistmentOptions enlistmentOptions)
{
this.transaction.EnlistVolatile(enlistmentNotification, enlistmentOptions);
}
}
public interface IEnlistment
{
void Done();
}
public interface IPreparingEnlistment
{
void Prepared();
}
public abstract class Enlistable : IEnlistmentNotification
{
public abstract void Commit(IEnlistment enlistment);
public abstract void Rollback(IEnlistment enlistment);
public abstract void Prepare(IPreparingEnlistment enlistment);
public abstract void InDoubt(IEnlistment enlistment);
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
this.Commit(new DefaultEnlistment(enlistment));
}
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
this.InDoubt(new DefaultEnlistment(enlistment));
}
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
this.Prepare(new DefaultPreparingEnlistment(preparingEnlistment));
}
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
this.Rollback(new DefaultEnlistment(enlistment));
}
private class DefaultEnlistment : IEnlistment
{
private Enlistment enlistment;
public DefaultEnlistment(Enlistment enlistment)
{
this.enlistment = enlistment;
}
public void Done()
{
this.enlistment.Done();
}
}
private class DefaultPreparingEnlistment : DefaultEnlistment, IPreparingEnlistment
{
private PreparingEnlistment enlistment;
public DefaultPreparingEnlistment(PreparingEnlistment enlistment) : base(enlistment)
{
this.enlistment = enlistment;
}
public void Prepared()
{
this.enlistment.Prepared();
}
}
}
这是一个依赖于ITransactionManager来处理其事务性工作的类的示例:
public class Foo
{
private ITransactionManager transactionManager;
public Foo(ITransactionManager transactionManager)
{
this.transactionManager = transactionManager;
}
public void DoSomethingTransactional()
{
var command = new TransactionalCommand();
using (var scope = this.transactionManager.CreateScope(TransactionScopeOption.Required))
{
this.transactionManager.CurrentTransaction.EnlistVolatile(command, EnlistmentOptions.None);
command.Execute();
scope.Complete();
}
}
private class TransactionalCommand : Enlistable
{
public void Execute()
{
// Do some work here...
}
public override void Commit(IEnlistment enlistment)
{
enlistment.Done();
}
public override void Rollback(IEnlistment enlistment)
{
// Do rollback work...
enlistment.Done();
}
public override void Prepare(IPreparingEnlistment enlistment)
{
enlistment.Prepared();
}
public override void InDoubt(IEnlistment enlistment)
{
enlistment.Done();
}
}
}
关于c# - 单元测试TransactionScope的使用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/626802/