我被指派帮助遗留系统;该系统在整个应用程序中引用了第三方库(超过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(...)
进行单元测试其他信息 -
我认为我不理解的是如何从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);
}
}
使用类似
的ITagProxypublic 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);
}
}
但它仍然取决于具体的标签和控制器。
如何让代码不依赖于具体标记和控制器?
答案 0 :(得分:3)
除了包装Controller
和Tag
类之外,您还需要一种方法来创建不直接暴露第三方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; }
}
}