【问题标题】:Create a wrapper around a third party dll to abstract the dll and to be able to unit test my code围绕第三方 dll 创建一个包装器以抽象 dll 并能够对我的代码进行单元测试
【发布时间】:2014-09-02 16:48:50
【问题描述】:

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

我想做的是重构代码,这样第三方类就不会在整个应用程序中被引用。我还想围绕我可以控制的代码介绍单元测试。

使用第 3 方 dll 的代码示例如下所示(第 3 方类是 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;
    }
}

我已经能够创建一个包装类来帮助我删除第 3 方 dll 的所有引用,现在所有调用都通过我的包装,我的包装是唯一调用第 3 方 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 对象。
  • 第 3 方 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);
    }
}

但还是依赖于具体的Tag和Controller。

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

【问题讨论】:

  • 您仍在包装器中公开Tag。如果您想要真正的隔离,那么您需要将代理类中的每个属性/方法完全包装起来,这样第三方Tag 对象的存在只是该代理的一个实现细节。一旦你达到了这一点,那么你可以看看使用 DI 正确地创建对象。
  • 感谢您的意见。它让我看到了我错过了什么。

标签: c# .net unit-testing


【解决方案1】:

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

由于您显然没有可用的 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; }
    }
}

【讨论】:

  • 谢谢,我真的很喜欢你在这里所做的。我想我可以将它应用到应用程序中,然后开始围绕一些代码进行一些单元测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-11
  • 1970-01-01
相关资源
最近更新 更多