当前位置:  开发笔记 > 编程语言 > 正文

单元测试TransactionScope的使用

如何解决《单元测试TransactionScope的使用》经验,为你挑选了2个好方法。

前言: 我设计了一个强接口和完全可模拟的数据层类,它希望业务层TransactionScope在单个事务中包含多个调用时创建.

问题:我希望单元测试我的业务层TransactionScope在我预期的时候使用了一个对象.

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

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

虽然这对于程序员的可用性而言是一个非常好的模式,但测试它已经完成似乎......对我来说是不可能的.我无法检测到已经实例化了一个瞬态对象,更不用说模拟它以确定在其上调用了一个方法.然而,我的报道目标意味着我必须这样做.

问题:如何构建确保TransactionScope按照标准模式正确使用的单元测试?

最后的想法:我已经考虑过一个肯定会提供我需要的覆盖范围的解决方案,但是因为过于复杂并且不符合标准TransactionScope模式而拒绝了它.它涉及CreateTransactionScope在我的数据层对象上添加一个返回实例的方法TransactionScope.但是因为TransactionScope包含构造函数逻辑和非虚方法,因此很难(如果不是不可能)进行模拟,CreateTransactionScope那么返回的实例DataLayerTransactionScope将是一个可模拟的外观TransactionScope.

虽然这可能会起到作用,但它更复杂,我宁愿使用标准模式.有没有更好的办法?



1> Patrik Hägne..:

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

    不解决问题.

    为遵循相同模式但可模拟/可存根的现有类创建抽象.

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

我倾向于创建一组用于处理事务的接口和一个委托给System.Transaction实现的默认实现,例如:

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

}

这似乎是很多工作,但另一方面它是可重复使用的,它使得它很容易测试.

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

编辑: 我只是做了一些快速和肮脏的实现作为概念的证明,我认为这是我将采取的方向,这是我到目前为止所提出的.我想也许我应该为此创建一个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();
        }
    }
}



2> ShuggyCoUk..:

忽略这个测试是否是好事....

非常脏的黑客是检查Transaction.Current不是null.

这不是100%的测试,因为有人可能会使用除了TransactionScope之外的其他东西来实现这一点,但它应该防止显而易见的"没有费心去交易"的部分.

另一个选择是故意尝试创建一个新的TransactionScope,它具有与任何将要/应该使用的不兼容的隔离级别TransactionScopeOption.Required.如果这成功而不是抛出ArgumentException,则没有事务.这要求您知道特定的IsolationLevel未使用(像混沌这样的东西是潜在的选择)

这两个选项都不是特别令人愉快,后者非常脆弱,并且受到TransactionScope保持不变的语义的影响.我会测试前者而不是后者,因为它更健壮(并且读取/调试清晰).

推荐阅读
ar_wen2402851455
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有