Why AAA style mocking is better than Record / Playback

by Rasmus Kromann-Larsen May 24, 2009 21:06

If you've followed me on twitter for more than a couple of days, you will most probably have heard my grumbling each time I run into issues using record / playback mocking - so I thought I'd write a short post on my experiences with both and why I think that I keep bumping into issues with record / playback.

Phases of a test

If you take a look at the normal flow of a test method without mocking, it will usually perform some kind of setup, then perform some action that invokes the code under test - and then at the end, make some assertions about the state of some component that you want verified. While some tests continue after this point, this is where you should stop if you're following the "one assert per test" rule - this is sort of the single responsibility principle for tests. This flow is also the inspiration for the AAA name - first you Arrange your test setup, then you Act upon the class under test - and then you Assert something about the state. Here's a simple example without mocking:

[Test]
public void CanRemoveCategories()
{
    // Arrange
    var collection = new CategoryCollection("test");

    // Act
    collection.RemoveCategory("test");

    // Assert
    Assert.That(collection.Count, Is.EqualTo(0));
}

This test is chronologically sound, it makes sense and it is easy to read - but then again, this is a state-based test, I said I was going to talk about behavioral tests - mock tests.

Phase confusion

Many people find that mocking is rather difficult to understand and that it often very hard to read and understand. Since our tests act as an API-description for our code - and since we want to be able to figure out how to fix our failed tests - readability is important. Now, let's look at one of my tests from a Record / Playback point of view. I'm using Rhino Mocks as my mocking framework in this test:

[Test]
public void ShouldIgnoreRespondentsThatDoesNotExistRecordPlayback()
{
    var guid = Guid.NewGuid();
    IEventRaiser executeRaiser;

    using(_mocks.Record())
    {
        Expect.Call(_view.Respondents).Return(new[] {guid.ToString()});
        Expect.Call(_repository.GetById(guid)).Return(null);

        _view.ExecuteOperation += null;
        executeRaiser = LastCall.IgnoreArguments()
            .Repeat.Any()
            .GetEventRaiser();

        Expect.Call(_view.OperationErrors = null)
            .IgnoreArguments()
            .Constraints(List.IsIn("Non-existant respondent: " + guid));
    }

    using(_mocks.Playback())
    {
        new BulkRespondentPresenter(_view, _repository);
        executeRaiser.Raise(null, EventArgs.Empty);
    }
}

Now, at a glance, can you tell me what this test is really doing? There's something with a view and a repository and we can probably deduce quite a bit from the test name. But it's rather hard to separate the different phases I talked about before Arrange, Act and Assert. Below, I've tried to annotate the test with the phases:

[Test]
 public void ShouldIgnoreRespondentsThatDoesNotExistRecordPlayback()
 {
     // Arrange
     var guid = Guid.NewGuid();
     // Part of Act
     IEventRaiser executeRaiser;

     using(_mocks.Record())
     {
         // Arrange (or Assert?)
         Expect.Call(_view.Respondents).Return(new[] {guid.ToString()});
         Expect.Call(_repository.GetById(guid)).Return(null);

         // Part of Act
         _view.ExecuteOperation += null;
         executeRaiser = LastCall.IgnoreArguments()
             .Repeat.Any()
             .GetEventRaiser();

         // Assert
         Expect.Call(_view.OperationErrors = null)
             .IgnoreArguments()
             .Constraints(List.IsIn("Non-existant respondent: " + guid));
     }

     using(_mocks.Playback())
     {
         // Arrange
         new BulkRespondentPresenter(_view, _repository);
         // Act
         executeRaiser.Raise(null, EventArgs.Empty);
     }
 }

No wonder it's hard to read and understand. The phases are mixed all over - and the Asserts are in the middle of the test - this is nothing like the natural flow of the previous test without mocking. I usually like to have the phases separated in my test with comments as well, but it's just not possible in this test. I wrote this test up rather quickly, so there might be a better way of doing it that I am missing - if there is, please yell at me :-)

Sorting out the confusion

AAA mocking, as the name suggests, is all about clearing out the confusion in that last test - it's about maintaining the original test flow. It just so happens also to have some other benefits, that I will get into later in the post. I've written the same test as above in an AAA style, this time with Moq, since I'm trying it out at the moment, but Rhino Mocks has similar syntax. Moq is pretty heavy on lambda expressions, but even if you haven't worked with those yet, I'm sure you will grasp the idea. If you want a general introduction to mocking with Moq, Justin Etheredge has a small series about it.

[Test]
public void ShouldIgnoreRespondentsThatDoesNotExist()
{
    // Arrange
    var guid = Guid.NewGuid();
    _viewMock.Setup(x => x.Respondents).Returns(new[] { guid.ToString() });
    _repositoryMock.Setup(x => x.GetById(guid)).Returns(() => null);

    // Act
    _viewMock.Raise(x => x.ExecuteOperation += null, EventArgs.Empty);

    // Assert
    _viewMock.VerifySet(x => x.OperationErrors =
        It.Is<IList<string>>(l => l.Contains("Non-existant respondent: "+guid)));
}

Those comments are actually in my original test as well - and in my test live template I generate all my tests with. If you compare this test to the one above, you will see that it has more or less the same components, but this time, they're arranged in a way that makes sense for the next reader of the test. The fact that the test is shorter is also slightly unfair, since my first test used an event raiser, which involves "many" lines of code. Also the separation of the phases allowed me to move the actual construction of the presenter our of the actual test and into shared setup code.

So what techniques did AAA mocking introduce to help alleviate the pains? First of all, the mocks no longer has states - that's what record and playback really refer to: A mock in record state will record calls made on it and then expect them to be called again during the playback state. Furthermore, it cleanly separates mock setup from mock expectations.

What is gained?

So what did we gain with AAA style mocking over the traditional record / playback style?

  • The main selling point for me is readability and test simplicity - it is much easier for me to explain mocking to someone else with AAA.
  • If you have done any fairly advanced record / playback mocking, you will find that when the mocks have states, it will often result in subtle test failures.
  • Clean separation of test phases.
  • Greatly improved ability to move shared code out of tests. Since you have the first part of your test handling setup, extending this part to start before the actual test (in a setup method) is no problem. With record / playback mocking, you will often run into state failures if you attempt it.

kick it on DotNetKicks.com

Tags:

Development | Testing

Be Mindful Of Your Dependencies

by Rasmus Kromann-Larsen May 11, 2009 20:35

Dependencies are everywhere in your code, you cannot avoid them, without dependencies, your software would make no sense, it is the interaction and collaboration between the software entities that create the program. Now this statement doesn't mean that we should not worry about our dependencies - we just can't remove them completely. But as with almost any other aspect of good software development, we should be mindful of our dependencies.

Coupling

When a software entity depends on another, they are said to be coupled. Coupling ranges in degrees from totally decoupled over loosely coupled to tightly coupled. The tighter two software entities are coupled, the bigger risk that when one changes, the other will be forced to change as well. Why is this important? Because coupling is transitive (if A depends on B and B depends on C, A indirectly depends on C) and we would like to be able to change our program in the future. If you have a program where all components depend on each other and the coupling is high, a small change will often ripple through the system - either a) forcing you to change a bigger part of the system (potentially creating more ripples) or b) breaking the system in unexpected places. If not treated properly, these ripples can cause developers to loose confidence in their changes (what will I break this time?) and slow development down - maybe even to a halt.

Cohesion

Cohesion describes how related the responsibilities within a component are, how focused it is. This relates to the functionality within the component - a static utility class will often have rather low cohesion. But it also relates to the level of abstraction used within the component - again, a class that does both very high level operations and very low level operations will often have very low cohesion. Code with low cohesion will often be harder to understand, since it doing many different things. It will often be easier to reuse a component with high cohesion, since it will be more focused on doing a single task.

Managing Dependencies

So how do we manage dependencies? As mentioned, we can't get rid of them - but we can choose to decouple our software components from each other - and we can work to increase the cohesion of our components. Looking into the Gang of Four book, some basic advice is available:

  • Program to an interface, not an implementation.
  • Favor composition over inheritance.

Interfaces

Decoupling often involves programming to an interface instead of an implementation. Interfaces can help us break our dependency chains - looking at the example from before: If A depends on B and B depends on C - we can break the dependency chain by making an interface IB of B. Now A depends on IB instead. Interfaces, when used properly, can be seen as concepts and are thus more "stable" than an actual implementation. It is a contract that describes a set of properties for some object to fulfill. If you were to build a tall tower, would you prefer to have the core of the tower made of stable building blocks or instable ones?

Having an interface also allows us to tweak other things, such as the granularity of access - the size of the surface on which A is dependant. If we reduce the size of the interface and thus the dependency, A's usage of B will be easier to control - and new concrete classes based on IB will probably be easier to implement and have higher cohesion too.

Composition

Another factor that can help us reduce coupling is to favor composition over inheritance. I like the analogy from the Gang of Four book where they talk about inheritance as white-box reuse, while composition is black-box reuse. What this means is that a sub-class will often be tightly coupled to the internals of the parent. In languages like C# and Java, single inheritance limits your options even further, once you start inheriting - and languages with multiple inheritance often suffer from other problems.

Composition on the other hand, is all about creating small units of focused behavior and then "weaving" this behavior into the desired functionality. With composition, different components are free to vary independent of each other - whereas this might cause combinatorial explosions in inheritance trees.

This is not to say that inheritance is not useful, but it is often overused - composition is often a little harder to grasp at first (I know it took me a while), but it is a really powerful technique.

Conclusion

In this post I have discussed some of the basics about managing dependencies. It is on a reasonably low level, but will be the basis for some of my next posts on design principles and design patterns - since many of them build upon exactly this.

kick it on DotNetKicks.com

Tags:

Design

Powered by BlogEngine.NET 1.6.1.0
Theme by Mads Kristensen | Modified by Mooglegiant | Adjusted by Rasmus Kromann-Larsen

About Me

I am a danish .NET developer blogging about the technical side of my life, mostly .NET stuff, but also fundamental topics like design patterns, principles and productivity boosters.

In addition, I am a core group member of Aarhus .NET User Group.