对 TransactionScope 的使用进行单元测试

2024-04-17

序言:我设计了一个强接口且完全可模拟的数据层类,期望业务层创建一个TransactionScope http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx?ppud=4当多个调用应包含在单个事务中时。

问题:我想对我的业务层使用的进行单元测试TransactionScope当我期望它的时候就反对。

不幸的是,使用的标准模式TransactionScope是如下:

using(var scope = new TransactionScope())
{
    // transactional methods
    datalayer.InsertFoo();
    datalayer.InsertBar();
    scope.Complete();
}

虽然就程序员的可用性而言,这确实是一个很棒的模式,但测试它的完成情况对我来说似乎是不可能的。我无法检测到瞬态对象已被实例化,更不用说模拟它来确定对其调用了方法。然而,我的报道目标意味着我必须这么做。

问题:我怎样才能构建单元测试来确保TransactionScope是否按照标准模式正确使用?

最后的想法:我考虑过一种肯定能提供我需要的覆盖范围的解决方案,但由于过于复杂且不符合标准而拒绝了它TransactionScope图案。它涉及添加一个CreateTransactionScope我的数据层对象上的方法返回一个实例TransactionScope。但由于 TransactionScope 包含构造函数逻辑和非虚拟方法,因此即使不是不可能,也很难模拟,CreateTransactionScope将返回一个实例DataLayerTransactionScope这将是一个可嘲笑的外观TransactionScope.

虽然这可能可以完成这项工作,但它很复杂,我更喜欢使用标准模式。有没有更好的办法?


我现在正面临同样的问题,对我来说似乎有两种解决方案:

  1. 别解决问题。
  2. 为现有类创建遵循相同模式但可模拟/可存根的抽象。

Edit:我现在为此创建了一个 CodePlex 项目:http://legendtransactions.codeplex.com/ http://legendtransactions.codeplex.com/

我倾向于创建一组用于处理事务的接口和委托给 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);

}

这看起来工作量很大,但另一方面它是可重用的,并且使得一切都非常容易测试。

请注意,这并不是接口的完整定义,仅足以让您了解全局。

Edit:我只是做了一些快速而肮脏的实现作为概念证明,我认为这是我将采取的方向,这是我到目前为止所想出的。我想也许我应该为此创建一个 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();
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

对 TransactionScope 的使用进行单元测试 的相关文章

随机推荐