创建一个围绕第三方dll的包装器来抽象dll并能够对我的代码进行单元测试

时间:2014-09-02 16:48:50

标签: c# .net unit-testing

我被指派帮助遗留系统;该系统在整个应用程序中引用了第三方库(超过2000次)。应用程序中没有单一的单元测试,这是一个关键任务系统。

我想要做的是重构代码,以便在整个应用程序中不引用第三方类。我还想介绍一下我可以控制的代码的单元测试。

使用第三方dll的代码的一个示例如下所示(第三方类是Controller和Tag):

public class Processor
{
    private Controller _clx;
    private Tag _tag;

    public bool Connect(string ip, string slot, int timeout, bool blockWrites, string tagName)
    {
        _clx = new Controller(ip, slot, timeout);
        if (_clx != null)
        {
            _clx.Connect();
            if (_clx.IsConnected)
            {
                if (tagName != null)
                {
                    _tag = new Tag(_clx, tagName, ATOMIC.DINT);
                    return ((_tag.ErrorCode == 0) && _tag.Controller.IsConnected);
                }

                _tag = new Tag {Controller = _clx, DataType = ATOMIC.DINT, Length = 1};
                return (_tag.Controller.IsConnected);

            }
        }
        return false;
    }
}

我已经能够创建一个包装类,帮助我删除第三方dll的所有引用,现在所有调用都通过我的包装器,我的包装器是调用第三方dll的唯一代码。

 public class ControllerWrapper 
{
    public ControllerWrapper(string ip, string slot, int timeout)
    {
        Controller = new Controller(ip,slot,timeout);
    }
    public Controller Controller { get; set; }
}

public class TagWrapper
{
    public Tag Tag { get; set; }
    public TagWrapper()
    {
    }
    public TagWrapper(ControllerWrapper clx, string tagName, ATOMIC datatype)
    {
        Tag = new Tag(clx.Controller, tagName,datatype);

    }
}

现在我的处理器类看起来像:

public class Processor
{
    private ControllerWrapper _clx;
    private TagWrapper _tag;
    public bool Connect(string ip, string slot, int timeout, bool blockWrites, string tagName)
    {
        _clx = new ControllerWrapper(ip, slot, timeout);
        if (_clx != null)
        {
            _clx.Controller.Connect();
            if (_clx.Controller.IsConnected)
            {
                if (tagName != null)
                {
                    _tag = new TagWrapper(_clx, tagName, ATOMIC.DINT);
                    return ((_tag.Tag.ErrorCode == 0) && _tag.Tag.Controller.IsConnected);
                }
                _tag = new TagWrapper {Tag = {Controller = _clx.Controller, DataType = ATOMIC.DINT, Length = 1}};
                return (_tag.Tag.Controller.IsConnected);
            }
        }
        return false;
    }
}

我的问题是我仍然无法对Processor.Connect(...)

进行单元测试

其他信息 -

  • 这是一个winforms应用程序.NET 2.0。
  • 没有使用任何DI或IOC工具。
  • 具体的Tag对象需要一个具体的Controller对象。
  • 第三方dll无法在测试环境中使用,因为它使用IP并尝试连接到控制器。

我认为我不理解的是如何从Connect方法中创建标签和控制器,这样我就可以在单元测试中使用假标签和假控制器,但是有真正的标签和控制器在生产代码中。

我现在已经玩了大约4天并实施了许多不同的版本,但仍然不知所措。

我尝试过如下构建器类:

public static class TagBuilder
{
    public static ITagProxy BuildTag()
    {
        return new TagProxy().CreateTag();
    }

    public static ITagProxy BuildTag(IControllerProxy clx, string tagName, ATOMIC datatype)
    {
        return new TagProxy().CreateTag(clx, tagName, datatype);
    }
}

使用类似

的ITagProxy
public interface ITagProxy
{
    Tag Tag { get; set; }

    ITagProxy CreateTag();
    ITagProxy CreateTag(IControllerProxy clx, string tagName, ATOMIC dataType);
}

和ControllerProxy一样:

public interface IControllerProxy
{
    Controller Controller { get; set; }
    IControllerProxy CreateController(string ip, string slot, int timeout );
}

现在处理器代码如下:

 public class Processor
{
    private IControllerProxy _clx;
    private ITagProxy _tag;

    public virtual bool Connect(string ip, string slot, int timeout, bool blockWrites, string tagName)
    {
        _clx = CreateController(ip, slot, timeout);
        if (_clx != null && _clx.Controller != null)
        {
            _clx.Controller.Connect();

            if (_clx.Controller.IsConnected)
            {
                // check this connection carefully, if it fails the controller is not in the slot configured
                if (tagName != null)
                {
                    // use supplied Tag Name to to a qualified connection
                    _tag = TagBuilder.BuildTag(_clx, tagName, ATOMIC.DINT);

                    return ((_tag.Tag.ErrorCode == 0) && _tag.Tag.Controller.IsConnected);
                }
                _tag = TagBuilder.BuildTag();
                _tag.Tag.Controller = _clx.Controller;
                _tag.Tag.Length = 1;

                    return (_tag.Tag.Controller.IsConnected);
            }
        }
        return false;
    }
    public virtual IControllerProxy CreateController(string ip, string slot, int timeout)
    {
        if (_clx == null)
            _clx = new ControllerProxy();
        return _clx.CreateController(ip, slot, timeout);
    }
}

但它仍然取决于具体的标签和控制器。

如何让代码不依赖于具体标记和控制器?

1 个答案:

答案 0 :(得分:3)

除了包装ControllerTag类之外,您还需要一种方法来创建不直接暴露第三方DLL的包装类的实例。这通常使用Abstract Factory pattern来完成,它允许您同时拥有一个具体的工厂类(用于创建第三方DLL对象和关联包装器)和mock / stub工厂对象(用于创建单元测试的包装器)。

由于您显然没有可用的DI / IOC工具,因此您需要一些其他方法来设置Processor的工厂对象以进行测试。一种方法是使工厂对象成为Processor类的公共成员;另一种方法是使其成为Processor受保护成员,并将Processor作为测试目的的子类。无论哪种方式,使用延迟初始化属性来访问工厂都可以确保代码默认使用“真实”代码。

public interface IControllerProxy
{
    public bool IsConnected { get; }
    public void Connect();
}
public interface ITagProxy
{
    public IControllerProxy Controller { get; }
    public int Length { get; set; }
    public int ErrorCode { get; }
}
public interface IProxyFactory
{
    IControllerProxy CreateControllerProxy(string ip, string slot, int timeout);
    ITagProxy CreateTagProxy(IControllerProxy clx, string tagName, WrappedClasses.ATOMIC dataType);
    ITagProxy CreateTagWrapper(IControllerProxy clx, WrappedClasses.ATOMIC dataType, int length);
}

private class ConcreteControllerProxy : IControllerProxy
{
    private WrappedClasses.Controller _clx;
    public ConcreteControllerProxy(string ip, string slot, int timeout)
    {
        _clx = new WrappedClasses.Controller(ip, slot, timeout);
    }
    public bool IsConnected
    {
        get { return _clx.IsConnected; }
    }
    public void Connect()
    {
        _clx.Connect();
    }
    public WrappedClasses.Controller Controller { get { return _clx; } }
}
private class ConcreteTagProxy : ITagProxy
{
    private WrappedClasses.Tag _tag;
    public ConcreteTagProxy(WrappedClasses.Tag tag)
    {
        _tag = tag;
    }
    public ConcreteTagProxy(WrappedClasses.Controller clx, string tagName, WrappedClasses.ATOMIC dataType)
    {
        _tag = new WrappedClasses.Tag(clx, tagName, dataType);
    }
}
public class ConcreteProxyFactory : IProxyFactory
{
    public IControllerProxy CreateControllerProxy(string ip, string slot, int timeout)
    {
        return new ConcreteControllerProxy(ip, slot, timeout);
    }
    public ITagProxy CreateTagProxy(IControllerProxy clx, string tagName, WrappedClasses.ATOMIC dataType)
    {
        ConcreteControllerProxy controllerWrapper = clx as ConcreteControllerProxy;
        return new ConcreteTagProxy(controllerWrapper.Controller, tagName, dataType);
    }
    public ITagProxy CreateTagWrapper(IControllerProxy clx, WrappedClasses.ATOMIC dataType, int length)
    {
        ConcreteControllerProxy controllerWrapper = clx as ConcreteControllerProxy;
        WrappedClasses.Tag tag = new WrappedClasses.Tag
        {
            Controller = controllerWrapper.Controller,
            DataType = dataType,
            Length = length
        };
        return new ConcreteTagProxy(tag);
    }
}

public class Processor
{
    protected IProxyFactory _factory;
    private IProxyFactory Factory
    {
        get
        {
            if (_factory == null )
            {
                _factory = new ConcreteProxyFactory();
            }
            return _factory;
        }
    }

    private IControllerProxy _clx;
    private ITagProxy _tag;
    public bool Connect(string ip, string slot, int timeout, bool blockWrites, string tagName)
    {
        _clx = Factory.CreateControllerProxy(ip, slot, timeout);
        if (_clx != null)
        {
            _clx.Connect();
            if (_clx.IsConnected)
            {
                if (tagName != null)
                {
                    _tag = Factory.CreateTagProxy(_clx, tagName, WrappedClasses.ATOMIC.DINT);
                    return ((_tag.ErrorCode == 0) && _tag.Controller.IsConnected);
                }
                _tag = Factory.CreateTagWrapper(_clx, WrappedClasses.ATOMIC.DINT, 1);
                return (_tag.Controller.IsConnected);
            }
        }
        return false;
    }
}

// This class would be in your test suite
public class TestableProcessor : Processor
{
    public IProxyFactory Factory
    {
        get { return this._factory; }
        set { this._factory = value; }
    }
}