Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a wrapper around a third party dll to abstract the dll and to be able to unit test my code

I have been assigned to help out on a legacy system; this system has a reference to a third party library throughout the application(more than 2000 times). There isn’t a single unit test within the application and this is a mission critical system.

What I would like to do is refactor the code so that the third party classes are not referenced throughout all the application. I would also like to introduce unit test around the code I can control.

one example of the code that uses the 3rd party dll looks like the following (3rd party classes are Controller and 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;
    }
}

I have been able to create a wrapper class that helped me to remove all references of the 3rd party dll, now all calls go through my wrapper and my wrapper is the only code that calls the 3rd party 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);

    }
}

And now my Processor class looks like:

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

My problem is that I still am unable to unit test the Processor.Connect(...)

Additional info --

  • This is a winforms app .NET 2.0.
  • There isn't any DI or IOC tools used.
  • The concrete Tag object requires a concrete Controller object.
  • The 3rd party dll cannot be used in the test environment because it uses the IP and tries to connect to the controller.

I think what I am not understanding is how to get the creation of the tag and the controller out of the Connect method so that I can have a Fake Tag and Fake Controller in a unit test, but have the real Tag and Controller in the production code.

I have been playing with this for about 4 days now and implemented many different versions, but still am at a loss.

I have tried a builder class like the following:

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

With a ITagProxy something like

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

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

and ControllerProxy like:

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

Now the processor code looks like:

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

But it is still dependent on the concrete Tag and Controller.

How can I get the code to not be dependent on the Concrete Tag and Controller?

like image 449
LWarthen Avatar asked Sep 02 '14 16:09

LWarthen


People also ask

What is a third party DLL?

Third party DLLs are libraries created by other organisation outside of yours. You can use these third party DLLs by putting them into a folder in your solution and then creating a reference to it (Project-> Right Click-> Add Reference). Once you have the DLL, that DLL will have a namespace.

What is unit test cases in C#?

In the software development process Unit Tests basically test individual parts ( also called as Unit ) of code (mostly methods) and make it work as expected by programmer. A Unit Test is a code written by any programmer which test small pieces of functionality of big programs.


1 Answers

In addition to wrapping the Controller and Tag classes, you'll need a way to create instances of the wrapped classes that doesn't directly expose the third-party DLL. This is commonly done using the Abstract Factory pattern, which allows you to have both a concrete factory class (to create the third-party DLL objects and associate wrappers) and mock/stub factory objects (to create wrappers for unit testing).

Since you apparently don't have DI/IOC tooling available, you'll need some other way to set the Processor's factory object for testing purposes. One way to do that is to make the factory object a public member of the Processor class; another way is to make it a protected member of Processor and subclass the Processor for testing purposes. Either way, using a lazy-initialized property to access the factory ensures that the code uses the "real" code by default.

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; }
    }
}
like image 116
Edmund Schweppe Avatar answered Sep 17 '22 21:09

Edmund Schweppe