3

This is a dilemma. Say we have two classes

Class A
{
    public int memberValue;
}

interface IB
{
    int fun();
}
Class B : IB
{
    public int fun()
    {
        var a = new A();
        switch(a.memberValue)
        {
            case 1:
                //do something
            break;
            case 2:
                //do something
            break;
        }        
    }
}

Now this presents two tightly coupled classes. For testing B.fun(), we need to mock Class A and provide multiple values for A.memberValue.

As the A object is not required anywhere else beyond the scope of B.fun(), I dont see why should we inject it through B's constructor. How can we unit test this method fun() ?

4

2 回答 2

3

Firstly you should probably create an interface for A as well, but if this is just a simple POCO data class, it might be better to just make its properties virtual instead to allow for mocking. You have 3 options I think:

  1. Inject A to the constructor of B if it is a class that will be used often (e.g. a logging class or something). Then you can create a mock version in your test to check how A is used (remember, mocking is for testing behaviour with dependencies).

    public class A : IA { ... }
    
    public class B : IB
    {
        private readonly A a;
    
        public B(IA a)
        {
            this.a = a;
        }
    
        public void Func()
        {
            //... use this.a ...
        }
    }
    
    [Test]
    public void Func_AHasValue1_DoesAction1()
    {
        Mock<IA> mock = new Mock<IA>();
        mock.Setup(a => a.somevalue).Returns("something");
    
        B sut = new B(mock.Object);
        sut.Func();
    
        mock.Verify(m => m.SomethingHappenedToMe());
    }
    
  2. Pass A to the method if it is something that B needs to work with (as it seems here). You can still create a mock version for use in your tests. This is the same code as the above, but mock is passed to the method instead of the constructor. This is the better method if A is some data class generated at runtime instead of a class with behaviour.
  3. Create a factory class for A and inject that into the constructor. Change the method to get A instances from the factory and inject a mock factory in your tests to verify the behaviour.

    public class AFactory : IAFactory
    {
        public IA Create() { ... }
    }
    
    public class B : IB
    {
        private readonly IAfactory factory;
    
        public B(IAFactory factory)
        {
            this.factory = factory;
        }
    
        public void Func()
        {
            IA a = factory.Create();
            //... use this.a ...
        }
    }
    
    [Test]
    public void Func_AHasValue1_DoesAction1()
    {
        Mock<IAFactory> mockFactory = new Mock<IAFactory>();
        Mock<IA> mock = new Mock<IA>();
        mockFactory.Setup(f => f.Create()).Returns(mock.Object);
        mock.Setup(a => a.somevalue).Returns("something");
    
        B sut = new B(mockFactory.Object);
        sut.Func();
    
        mock.Verify(m => m.SomethingHappenedToMe());
    }
    
    • Option 1 is the standard approach for classes that can be built without any runtime information (e.g. logging classes).
    • Option 2 is better for when the input is only a data class that is generated at runtime (e.g. a user fills in a form and you have a POCO data class representing the form input).
    • Option 3 is better when A is something that does have behaviour but can't be created without something generated at runtime.

You'll have to look at your application to see which is most applicable.

于 2013-05-31T16:36:24.297 回答
1

Well, you can do it in few ways. Easiest will probably be to create factory method and in tests create class that inherits B and overrides factory method. I think that's something Feathers or Gojko Adzic (I don't really remember in whose book I've read it, i suppose it was Feathers' Working Effectively with Legacy Code) propose in such situations. Sample implementation would be something like:

class B : IB
{
    public int fun()
    {
        A a = this.CreateA();
        ...
    }
    protected A CreateA() { return new A(); }
}

and in unit test:

class BTest : B
{
    protected override A CreateA() { return mockA(); }
}

This is pretty simple and straightforward solution that does not limit coupling at class level but at least moves different functionalities to different methods so method that does something does not care about object creation.

But you need to carefully think whether it is really what you want. For small, short project it may be fine. For something bigger or long term it may be useful to still refactor code and remove dependency on A from B to make classes less coupled. Also, the pattern you've shown starts to look like Strategy design pattern. Maybe you should not inject A but strategy object that will know how to handle your current situation? That's always thing that comes to my mind when I see if ladder or switch statement.

But it all depends on context and size of your project.

于 2013-05-31T16:32:41.993 回答