单元测试使用的TransactionScope的 [英] Unit Testing the Use of TransactionScope

查看:99
本文介绍了单元测试使用的TransactionScope的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

序言:
我设计了预计业务层创建的 的TransactionScope 当多个呼叫应包含在一个单一的交易。

The preamble: I have designed a strongly interfaced and fully mockable data layer class that expects the business layer to create a TransactionScope when multiple calls should be included in a single transaction.

问题:我想单元测试,我的业务层利用的的TransactionScope 时,我期望它的对象。

The problem: I would like to unit test that my business layer makes use of a TransactionScope object when I expect it to.

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

Unfortunately, the standard pattern for using TransactionScope is a follows:

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



虽然这是在可用性方面一个真正伟大的方式为程序员,测试,它的工作似乎... unpossible给我。我无法检测到一个短暂的对象已经被实例化,更遑论模拟它来确定一个方法调用就可以了。然而,我的目标覆盖意味着我必须。

While this is a really great pattern in terms of usability for the programmer, testing that it's done seems... unpossible to me. I cannot detect that a transient object has been instantiated, let alone mock it to determine that a method was called on it. Yet my goal for coverage implies that I must.

问题:如何着手建立单元测试,以确保的TransactionScope 是?按照标准模式使用得当

The Question: How can I go about building unit tests that ensure TransactionScope is used appropriately according to the standard pattern?

最后的思考:我认为一个解决方案,肯定会提供我所需要的覆盖范围,但拒绝它作为过于复杂,不符合标准的的TransactionScope 模式。它涉及将我的数据层对象上的 CreateTransactionScope 方法,返回的TransactionScope 的一个实例。但由于的TransactionScope包含构造逻辑和非虚方法,因此难以甚至无法模拟, CreateTransactionScope 将返回 DataLayerTransactionScope 这将是一个mockable门面到的TransactionScope

Final Thoughts: I've considered a solution that would certainly provide the coverage I need, but have rejected it as overly complex and not conforming to the standard TransactionScope pattern. It involves adding a CreateTransactionScope method on my data layer object that returns an instance of TransactionScope. But because TransactionScope contains constructor logic and non-virtual methods and is therefore difficult if not impossible to mock, CreateTransactionScope would return an instance of DataLayerTransactionScope which would be a mockable facade into TransactionScope.

虽然这可能做复杂的工作,我宁愿使用标准模式。有没有更好的办法?

While this might do the job it's complex and I would prefer to use the standard pattern. Is there a better way?

推荐答案

我刚才坐在同样的问题,我似乎有两种解决方案

I'm just now sitting with the same problem and to me there seems to be two solutions:


  1. 请不要解决问题

  2. 创建为现有类抽象后面的。同样的模式,但mockable / stubable

编辑:
我已经创建了一个CodePlex上-Project这个现在: http://legendtransactions.codeplex.com/

我倾向于创造与交易工作的一组接口和一个默认的实现,委托给System.Transaction的实现,是这样的:

I'm leaning towards creating a set of interfaces for working with transactions and a default implementation that delegates to the System.Transaction-implementations, something like:

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

}

这似乎是一个大量的工作,但是,从另一方面它的可重复使用的,这使得它很容易测试。

This seems like a lot of work but on the other hand it's reusable and it makes it all very easily testable.

请注意,这不是接口足以让你大画面的完整定义。

Note that this is not the complete definition of the interfaces just enough to give you the big picture.

编辑:
我只是做了一些快速和肮脏的实现作为一个概念证明,我想这是我将采取的方向,这里是我想出了这么远。我在想,也许我应该为此创建一个CodePlex项目,所以这个问题可以一劳永逸解决。这不是我第一次碰到这一点。

I just did some quick and dirty implementation as a proof of concept, I think this is the direction I will take, here's what I've come up with so far. I'm thinking that maybe I should create a CodePlex project for this so the problem can be solved once and for all. This is not the first time I've run into this.

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来处理它的事务性工作:

Here's an example of a class that depends on the ITransactionManager to handle it's transactional work:

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

这篇关于单元测试使用的TransactionScope的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆