序言:
我设计了一个高度接口(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/

    10-13 06:55