Tuesday, May 3, 2011

TDD and DI: dependency injections becoming cumbersome

C#, nUnit, and Rhino Mocks, if that turns out to be applicable.

My quest with TDD continues as I attempt to wrap tests around a complicated function. Let's say I'm coding a form that, when saved, has to also save dependent objects within the form...answers to form questions, attachments if available, and "log" entries (such as "blahblah updated the form." or "blahblah attached a file."). This save function also fires off emails to various people depending on how the state of the form changed during the save function.

This means in order to fully test out the form's save function with all of its dependencies, I have to inject five or six data providers to test out this one function and make sure everything fired off in the right way and order. This is cumbersome when writing the multiple chained constructors for the form object to insert the mocked providers. I think I'm missing something, either in the way of refactoring or simply a better way to set the mocked data providers.

Should I further study refactoring methods to see how this function can be simplified? How's the observer pattern sound, so that the dependent objects detect when the parent form is saved and handle themselves? I know that people say to split out the function so it can be tested...meaning I test out the individual save functions of each dependent object, but not the save function of the form itself, which dictates how each should save themselves in the first place?

From stackoverflow
  • You're right that it can be cumbersome.

    Proponent of mocking methodology would point out that the code is written improperly to being with. That is, you shouldn't be constructing dependent objects inside this method. Rather, the injection API's should have functions that create the appropriate objects.

    As for mocking up 6 different objects, that's true. However, if you also were unit-testing those systems, those objects should already have mocking infrastructure you can use.

    Finally, use a mocking framework that does some of the work for you.

  • Constructor DI isn't the only way to do DI. Since you're using C#, if your constructor does no significant work you could use Property DI. That simplifies things greatly in terms of your object's constructors at the expense of complexity in your function. Your function must check for the nullity of any dependent properties and throw InvalidOperation if they're null, before it begins work.

    eglasius : disagree, making it property based doesn't simplifies, just hides the complexity.
    Randolpho : Well, by simplifies, I mean that it allows you to transfer the complexity from one location to another. That may actually simplify things in terms of testing or other aspects of the system, allowing you to deal with simpler portions in smaller chunks.
  • When it is hard to test something, it is usually symptom of the code quality, that the code is not testable (mentioned in this podcast, IIRC). The recommendation is to refactor the code so that the code will be easy to test. Some heuristics for deciding how to split the code into classes are the SRP and OCP. For more specific instructions, it would be necessary to see the code in question.

  • Use an AutoMocking container. There is one written for RhinoMocks.

    Imagine you have a class with a lot of dependencies injected via constructor injection. Here's what it looks like to set it up with RhinoMocks, no AutoMocking container:

    private MockRepository _mocks;
    private BroadcastListViewPresenter _presenter;
    private IBroadcastListView _view;
    private IAddNewBroadcastEventBroker _addNewBroadcastEventBroker;
    private IBroadcastService _broadcastService;
    private IChannelService _channelService;
    private IDeviceService _deviceService;
    private IDialogFactory _dialogFactory;
    private IMessageBoxService _messageBoxService;
    private ITouchScreenService _touchScreenService;
    private IDeviceBroadcastFactory _deviceBroadcastFactory;
    private IFileBroadcastFactory _fileBroadcastFactory;
    private IBroadcastServiceCallback _broadcastServiceCallback;
    private IChannelServiceCallback _channelServiceCallback;
    
    [SetUp]
    public void SetUp()
    {
        _mocks = new MockRepository();
        _view = _mocks.DynamicMock<IBroadcastListView>();
    
        _addNewBroadcastEventBroker = _mocks.DynamicMock<IAddNewBroadcastEventBroker>();
    
        _broadcastService = _mocks.DynamicMock<IBroadcastService>();
        _channelService = _mocks.DynamicMock<IChannelService>();
        _deviceService = _mocks.DynamicMock<IDeviceService>();
        _dialogFactory = _mocks.DynamicMock<IDialogFactory>();
        _messageBoxService = _mocks.DynamicMock<IMessageBoxService>();
        _touchScreenService = _mocks.DynamicMock<ITouchScreenService>();
        _deviceBroadcastFactory = _mocks.DynamicMock<IDeviceBroadcastFactory>();
        _fileBroadcastFactory = _mocks.DynamicMock<IFileBroadcastFactory>();
        _broadcastServiceCallback = _mocks.DynamicMock<IBroadcastServiceCallback>();
        _channelServiceCallback = _mocks.DynamicMock<IChannelServiceCallback>();
    
    
        _presenter = new BroadcastListViewPresenter(
            _addNewBroadcastEventBroker,
            _broadcastService,
            _channelService,
            _deviceService,
            _dialogFactory,
            _messageBoxService,
            _touchScreenService,
            _deviceBroadcastFactory,
            _fileBroadcastFactory,
            _broadcastServiceCallback,
            _channelServiceCallback);
    
        _presenter.View = _view;
    }
    

    Now, here's the same thing with an AutoMocking container:

    private MockRepository _mocks;
    private AutoMockingContainer _container;
    private BroadcastListViewPresenter _presenter;
    private IBroadcastListView _view;
    
    [SetUp]
    public void SetUp()
    {
    
        _mocks = new MockRepository();
        _container = new AutoMockingContainer(_mocks);
        _container.Initialize();
    
        _view = _mocks.DynamicMock<IBroadcastListView>();
        _presenter = _container.Create<BroadcastListViewPresenter>();
        _presenter.View = _view;
    
    }
    

    Easier, yes?

    The AutoMocking container automatically creates mocks for every dependency in the constructor, and you can access them for testing like so:

    using (_mocks.Record())
        {
          _container.Get<IChannelService>().Expect(cs => cs.ChannelIsBroadcasting(channel)).Return(false);
          _container.Get<IBroadcastService>().Expect(bs => bs.Start(8));
        }
    

    Hope that helps. I know my testing life has been made a whole lot easier with the advent of the AutoMocking container.

    Igor Brejc : This approach only hides the complexity, it doesn't relieve you of it. The root problem here is in the code that's being tested, not the test code itself.
    Frank Schwieterman : This is a reasonable thing to do. Just watch out for tests that are setting expectations on multiple services at once. Each test should typically set expectations against only one service at a time. Its nice to have a tool to put in expectation-less stubs for the rest.
  • First, if you are following TDD, then you don't wrap tests around a complicated function. You wrap the function around your tests. Actually, even that's not right. You interweave your tests and functions, writing both at almost exactly the same time, with the tests just a little ahead of the functions. See The Three Laws of TDD.

    When you follow these three laws, and are diligent about refactoring, then you never wind up with "a complicated function". Rather you wind up with many, tested, simple functions.

    Now, on to your point. If you already have "a complicated function" and you want to wrap tests around it then you should:

    1. Add your mocks explicitly, instead of through DI. (e.g. something horrible like a 'test' flag and an 'if' statement that selects the mocks instead of the real objects).
    2. Write a few tests in order to cover the basic operation of the component.
    3. Refactor mercilessly, breaking up the complicated function into many little simple functions, while running your cobbled together tests as often as possible.
    4. Push the 'test' flag as high as possible. As you refactor, pass your data sources down to the small simple functions. Don't let the 'test' flag infect any but the topmost function.
    5. Rewrite tests. As you refactor, rewrite as many tests as possible to call the simple little functions instead of the big top-level function. You can pass your mocks into the simple functions from your tests.
    6. Get rid of the 'test' flag and determine how much DI you really need. Since you have tests written at the lower levels that can insert mocks through areguments, you probably don't need to mock out many data sources at the top level anymore.

    If, after all this, the DI is still cumbersome, then think about injecting a single object that holds references to all your data sources. It's always easier to inject one thing rather than many.

    vijaysylvester : @ uncle Bob . you have mentioned exactly what i have been doing, in the intial lines.
  • I don't have your code, but my first reaction is that your test is trying to tell you that your object has too many collaborators. In cases like this, I always find that there's a missing construct in there that should be packaged up into a higher level structure. Using an automocking container is just muzzling the feedback you're getting from your tests. See http://www.mockobjects.com/2007/04/test-smell-bloated-constructor.html for a longer discussion.

  • In this context, I usually find statements along the lines of "this indicates that your object has too many dependencies" or "your object has too many collaborators" to be a fairly specious claim. Of course a MVC controller or a form is going to be calling lots of different services and objects to fulfill its duties; it is, after all, sitting at the top layer of the application. You can smoosh some of these dependencies together into higher-level objects (say, a ShippingMethodRepository and a TransitTimeCalculator get combined into a ShippingRateFinder), but this only goes so far, especially for these top-level, presentation-oriented objects. That's one less object to mock, but you've just obfuscated the actual dependencies via one layer of indirection, not actually removed them.

    One blasphemous piece of advice is to say that if you are dependency injecting an object and creating an interface for it that is quite unlikely to ever change (Are you really going to drop in a new MessageBoxService while changing your code? Really?), then don't bother. That dependency is part of the expected behavior of the object and you should just test them together since the integration test is where the real business value lies.

    The other blasphemous piece of advice is that I usually see little utility in unit testing MVC controllers or Windows Forms. Everytime I see someone mocking the HttpContext and testing to see if a cookie was set, I want to scream. Who cares if the AccountController set a cookie? I don't. The cookie has nothing to do with treating the controller as a black box; an integration test is what is needed to test its functionality (hmm, a call to PrivilegedArea() failed after Login() in the integration test). This way, you avoid invalidating a million useless unit tests if the format of the login cookie ever changes.

    Save the unit tests for the object model, save the integration tests for the presentation layer, and avoid mock objects when possible. If mocking a particular dependency is hard, it's time to be pragmatic: just don't do the unit test and write an integration test instead and stop wasting your time.

  • The simple answer is that code that you are trying to test is doing too much. I think sticking to the Single Responsibility Principle might help.

    The Save button method should only contain a top-level calls to delegate things to other objects. These objects can then be abstracted through interfaces. Then when you test the Save button method, you only test the interaction with mocked objects.

    The next step is to write tests to these lower-level classes, but thing should get easier since you only test these in isolation. If you need a complex test setup code, this is a good indicator of a bad design (or a bad testing approach).

    Recommended reading:

    1. Clean Code: A Handbook of Agile Software Craftsmanship
    2. Google's guide to writing testable code

0 comments:

Post a Comment