开发者

How do I unit test a method like this using IOC

开发者 https://www.devze.com 2023-03-22 07:23 出处:网络
I\'m trying to wtire unit tests for a function that looks something like this: public List<int> Process(int input)

I'm trying to wtire unit tests for a function that looks something like this:

public List<int> Process(int input)
{
    List<int> outputList = new List<int>();

    List<int> list = this.dependency1.GetSomeList(input);
    foreach(int element in list)
    {
        // ... Do some procssing to element

        //Do some more processing
        int processedInt = this.dependency2.DoSomeProcessing(element);

        // ... Do some processing to processedInt

        outputList.Add(processedInt);
   开发者_运维百科 }
    return outputList;
}

I was planning on mocking dependency1 and dependency2 in the test cases but I'm not sure how I should set them up. In order to setup dependency2.DoSomeProcessing I will need to know what the value of "element" will be each time it is called. To figure this out I would either need to:

  1. Copy paste some of the logic from my Process() method into the test case

  2. Calculate the values by hand (in the actual function this would involve hard coding some doubles with many decimal places into the test case)

  3. Bite the bullet and use the actual implementation of dependency2 instead of mocking it.

None of these solutions seem very good to me. I'm new to IOC and mocking so I'm hoping there's just something I'm completely not getting. If not which of these solutions seems the least bad?

Thanks

Edit: I'm using Moq to mock the dependencies.


What exactly are you testing?

The content of this method is that it calls one function to get a list of values, then calls another function to modify these values and returns the modified values.

You should not be testing the logic that gets the values, or the logic that converts it in this test, they are seperate tests.

So your mocking should be something like (hand compllied with Moq)

var dependency1 = new Mock<IDependency1>()
  .Setup(d => d.GetSomeList(It.IsAny<int>())
  .Returns(new List<int>(new [] { 1, 2, 3 });  
var dependency2 = new Mock<IDependency2>()
  .Setup(d => d.DoSomeProcessing(It.IsAny<int>())
  .Returns(x => x * 2); // You can get the input value and use it to determine an output value

Then just run your method and make sure the returned list is 2,4,6. This validates that some processing was performed on some list.

You then create a different test that makes sure GetSomeList() works. And another that makes sure DoSomeProcessing() works. Its the DoSomeProcessing() test that will require hand calculated values.


Is the variable "element" changed between the begin of foreach and DoSomeProcessing(element) method? If not, I would go with option number 2. Since you have the control over dependency1 and dependency2, you can create a stub for them so you return some elements in the list and you write code in dependency2 to handle each of the elements you got from dependency1 and return the correct integer.

My second option would be 3. I didn't like the first one.

By the way, look if Pex (http://research.microsoft.com/en-us/projects/pex/) can be useful for you in this case, since you have the actual code done.

[]'s


Okay, so first of all if you have access to this method and can change it then a much better approach would be the following:

public IEnumerable<int> Process(int input)
{
    foreach(var element in dependency1.GetSomeList(input))
    {
        yield return dependency2.DoSomeProcessing(element);
    }
}

So now on to the question at hand. You have 2 dependencies in the method that need to be mocked in order to test it properly. There are a few good ways of doing this but the simplest is to use Constructor Dependency Injection. This is where you inject the dependency into your class through the constructor. You will need your dependencies to have a common base type (interface or abstract class) to perform the injection too.

public class Processor
{
    public Processor(IDep1 dependency1, IDep2 dependency2)
    {
        _dependency1 = dependency1;
        _dependency2 = dependency2;
    }

    IDep1 _dependency1;
    IDep2 _dependency2;

    public IEnumerable<int> Process(int input)
    {
        foreach(var element in dependency1.GetSomeList(input))
        {
            yield return dependency2.DoSomeProcessing(element);
        }
    }
}

Now create a mock to inject into it for your tests.

public interface IDep1
{
    IEnumerable<int> GetSomeList(int);
}

public interface IDep2
{
    int DoSomeProcessing(int);
}

public class MockDepdency1 : IDep1
{
    public IEnumerable<int> GetSomeList(int val)
    {
        return new int[]{ 1, 2, 3 }.AsEnumerable();
    }
}

public class MockDepdency2 : IDep2
{
    public int DoSomeProcessing(int val)
    {
        return val + 1;
    }
}

...
main()
{
    IDep1 dep1 = new MockDependency1();
    IDep2 dep2 = new MockDependency2();

    var proc = new Processor(dep1, dep2);
    var actual = proc.Process(5);

    Assert.IsTrue(actual.Count() == 3);
}

I haven't written this code in the compiler - I just typed it in by hand but it should be close to working and represents a good example of how you could test that method.

An even better way is to use a utility like Unity and don't inject through the constructor. Instead you can just map your interface to the MockDependency1 and MockDependency2 inside your test and when Process(...) runs it will get the Mock versions instead. If you want to know how to do that then let me know and I'll add it below.


Mock both dependencies

The best solution is to Mock out both of your dependencies. Ideally your concrete class should have those dependencies injected upon construction.

You need to only test 3 things inside Process.

  1. Correct output of Process according to mocked inputs
  2. Behavior => (was dependency1.GetSomeList called as expected, etc)
  3. Corrcect output of Process along all logic paths

You should never use concrete dependencies, this will cause you to have fragile unit tests as you will be relying on data that is outside of your control. To test a method properly you need to provide invalid inputs (nulls, empty strings, extremely large numbers, negative numbers ) etc, anything in an attempt to break the current expected functinality. And naturally valid inputs as well.

You also do not need to replicate the actual functionality of your dependencies. The only thing you need to test is correct functionality of your code along all logic paths. If you need to change the output of dependency2 to test a 2nd code path. Then that's another test.

The advantage to separating your tests into a large number of finely grained tests cases is that if you change anything inside Process, and run your unit tests. You know exactly where the problem is because 3 / 50 tests now fail and you know where to fix your problems because they're all testing 1 specific thing.

Using Rhino Mocks would go something like this

private IDependency1 _dependency1;
private IDependency2 _dependency2;
private ClassToBeTested _classToBeTested;

[SetUp]
private override void SetUp()
{
  base.SetUp();
  _dependency1 = MockRepository.GenerateMock<IDependency1>();
  _dependency2 = MockRepository.GenerateMock<IDependency2>();

  _classToBeTested = new ClassToBeTested(_dependency1, _dependency2);

}

[Test]
public void TestCorrectFunctionOfProcess()
{

  int input = 10000;
  IList<int> returnList = new List<int>() {1,2,3,4};

  // Arrange   
  _dependency1.Expect(d1 => d1.GetSomeList(input)).Return(returnList);
  _dependency2.Expect(d2 => d2.DoSomeProcessing(0))
      .AtLeastOnce().IgnoreArguments().Return(1);

  // Act
  var outputList = _classToBeTested.Process(input);

  // Assert that output is correct for all mocked inputs
  Assert.IsNotNull(outputList, "Output list should not be null")

  // Assert correct behavior was _dependency1.GetSomeList(input) called?
  _dependency1.VerifyAllExpectations();
  _dependency2.VerifyAllExpectations();

}

Update

IElementProcessor _elementProcessor;

public List<int> Process(int input)
{
    List<int> outputList = new List<int>();

    List<int> list = this.dependency1.GetSomeList(input);
    foreach(int element in list)
    {
        // ... Do some procssing to element
        _elementProcessor.ProcessElement(element);

        //Do some more processing
        int processedInt = this.dependency2.DoSomeProcessing(element);

        // ... Do some processing to processedInt
        _elementProcessor.ProcessInt(processedInt);

        outputList.Add(processedInt);
    }
    return outputList;
}

So effectively what happens above is that both of your processing is now broken up into separate objects. Process is not almost completely abstract (which is perfect). You can now test each element individually to ensure correct functionality in separate unit tests.

The above test will then become an Integration test where you test that each of the dependencies are called correctly.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号