Testability of East Oriented Code

Introduction

By Urville DjasimJames Ladd wrote great post about East Oriented Code. In this post we’ll discuss the influence of east code on its testability.

For simplification, east code is mainly characterized by that all public methods return void. Usage of this “rule of void” makes the code focus on the “Tell, don’t ask” attitude.

What does it have to do with testability?

For making things simpler, let’s go over a simple example based on the MoviesLister used in James post. The goal of this code is to add stars to Steven Spielberg movies.

West code

public class WestMovieLister
{
    private readonly IWestFinder moviesWestFinder;
    public WestMovieLister(IWestFinder moviesWestFinder)
    {
        this.moviesWestFinder = moviesWestFinder;
    }

    public IWestMovie[] MoviesDirectedBy(string requestedDirector)
    {
        var allMovies = moviesWestFinder.FindAll();
        var directorMovies = allMovies.Where(movie => movie.Director == requestedDirector).ToArray();
        return directorMovies;
    }
}
public class WestStarsAdder
{
    public void AddStarsToMovies(IWestMovie[] moviesToAddStartTo)
    {
        foreach (var movie in moviesToAddStartTo)
        {
            movie.AddStar();
        }
    }
}

West tests

[TestMethod]

public void TestWestMovieLister()
{
    var fakeSpielbergMovie = Isolate.Fake.Instance<WestMovie>();
    Isolate.WhenCalled(() => fakeSpielbergMovie.Director).WillReturn("Steven Spielberg");

var fakeNonSpielbergMovie = Isolate.Fake.Instance<WestMovie>();
    Isolate.WhenCalled(() => fakeNonSpielbergMovie.Director).WillReturn("Not Steven Spielberg");
 
    var fakeFinder = Isolate.Fake.Instance<WestFinder>();
    Isolate.WhenCalled(() => fakeFinder.FindAll())
        .WillReturnCollectionValuesOf(new List<WestMovie> { fakeSpielbergMovie, fakeNonSpielbergMovie });
 
    var movieLister = new WestMovieLister(fakeFinder);
    var movies = movieLister.MoviesDirectedBy("Steven Spielberg");
    CollectionAssert.AreEquivalent(new[] { fakeSpielbergMovie }, movies);
}

[TestMethod]
public void TestWestStarsAdder()
{
    var fakeMovie = Isolate.Fake.Instance<WestMovie>(Members.CallOriginal, ConstructorWillBe.Called, "Director");

    var starsAdder = new WestStarsAdder();
    starsAdder.AddStarsToMovies(new[] {fakeMovie});
 
    Assert.AreEqual(1, fakeMovie.Stars);
}

Testing this code is very straight forward. All needed to be done is give basic inputs to the classes and easily check the state of the returned values.

East code

public class EastMovieLister
{
    private readonly IEastFinder moviesFinder;

    public EastMovieLister(IEastFinder moviesFinder)
    {
        this.moviesFinder = moviesFinder;
    }

    public void ApplyToMoviesDirectedBy(IMovieAction movieAction, string director)
    {
        moviesFinder.FindAllAndApply(new MovieDirectorFilterActionDecorator(movieAction, director));
    }

    class MovieDirectorFilterActionDecorator : IMovieAction
    {
        private readonly IMovieAction movieAction;
        private readonly string director;

        public MovieDirectorFilterActionDecorator(IMovieAction movieAction, string director)
        {
            this.movieAction = movieAction;
            this.director = director;
        }

        public void ApplyTo(IEastMovie movie)
        {
            movie.IfDirectedByDo(director, movieAction);
        }
    }
}
public class EastMovie : IEastMovie
{
    private readonly string director;

    public EastMovie(string director)
    {
        this.director = director;
    }

    public void AddStar()
    {
        stars++;
    }

    private int stars;

    public void IfDirectedByDo(string directorToFilter, IMovieAction movieAction)
    {
        if (director == directorToFilter)
        {
            movieAction.ApplyTo(this);
        }
    }
}

East tests

The moment we start thinking of a test we see that there is no state to verify against:

public void ApplyToMoviesDirectedBy(IMovieAction movieAction, string director)

This method has no return value, so there is nothing to check there. Unit testing this method forces us to test the interaction between the objects.

What are we going to check? We can check that EastMovie.IfDirectedByDo was called. We can also check that EastMovie.AddStar was called, we can check if the IMovieAction.ApplyTo was called and we can check if EastFinder.FindAllAndApply was called. What should we choose? Since we’re unit testing we’ll choose the closest object – EastFinder.

First attempt
[TestMethod]
public void TestEastMovieLister()
{
    var fakeFinder = Isolate.Fake.Instance<EastFinder>();
    var mockMovieAction = Isolate.Fake.Instance<IMovieAction>();
 
    var movieLister = new EastMovieLister(fakeFinder);
    movieLister.ApplyToMoviesDirectedBy(mockMovieAction, "Steven Spielberg");
 
    Isolate.Verify.WasCalledWithAnyArguments(() => fakeFinder.FindAllAndApply(null));
}


After taking a close look at this test – what it does is actually reversing the code logic. Does this test ensure that if we send an AddStarAction Spielberg’s movies will get another star? Not really, it doesn’t even check if the passed action is the AddStarAction. Also, we know we can’t verify it. It will be over specification (and in this case it won’t really work).

Second attempt
[TestMethod]
public void TestWestMovieLister()
{
    var fakeMovie = Isolate.Fake.Instance<EastMovie>(Members.CallOriginal,
                                                     ConstructorWillBe.Called,
                                                     "Steven Spielberg");
    var fakeFinder = Isolate.Fake.Instance<EastFinder>(Members.CallOriginal);
    fakeFinder.AddMovie(fakeMovie);
    
    var mockMovieAction = Isolate.Fake.Instance<IMovieAction>();

    var movieLister = new EastMovieLister(fakeFinder);
    movieLister.ApplyToMoviesDirectedBy(mockMovieAction, "Steven Spielberg");

    Isolate.Verify.WasCalledWithAnyArguments(() => mockMovieAction.ApplyTo(null));
}

What we have now? We’re sure our action was called. We could also verify it was called with our fake movie, but again, it’s over specification. Second attempt and our test is still away from being satisfying.

Third attempt
[TestMethod]
public void TestEastMovieLister()
{
    var mockMovie = Isolate.Fake.Instance<EastMovie>(Members.CallOriginal,
                                                     ConstructorWillBe.Called,
                                                     "Steven Spielberg");
    var fakeFinder = Isolate.Fake.Instance<EastFinder>(Members.CallOriginal);

    fakeFinder.AddMovie(mockMovie);

    var fakeMovieAction = Isolate.Fake.Instance<AddStarAction>(Members.CallOriginal);
 
    var movieLister = new EastMovieLister(fakeFinder);
    movieLister.ApplyToMoviesDirectedBy(fakeMovieAction, "Steven Spielberg");

    Isolate.Verify.WasCalledWithAnyArguments(() => mockMovie.AddStar());
}

This test takes us one step further. Now we have more confidence that our test actually tests what it’s intended to. This test is surly not enough. If we want to be sure this test is OK we have to check that our movie adds one star only. This make us count the calls to AddStar, we have no other way since EastMovie doesn’t have a getter that tells us how many stars it has.

What have we learnt so far?

As we can see from our last test, in order to get high confidence even simple unit tests looks like integration test. East code has better encapsulation of its state which makes it harder to test the state and forces us to check the interaction.

Conclusion

East code improves encapsulation but reduces out ability to write tests against objects states. Code with strong east orientation will have more interaction based tests than west oriented code.

If so, should we reduce our east orientation to improve its testability? In my opinion – No. Orienting east makes the code having more classes with focused responsibility. These kind of classes are less likely to change and of course smaller (by code and interface).

How’s the conclusion fits all of the above? East orientation is a compass, it helps us aim to better code:

“The structuring of code to an East orientation decreases coupling and the amount of code needed to be written, whilst increasing code clarity, cohesion and flexibility. It is easier to create a good design and structure by simply orienting it East.”

With these qualities, we have focused tests (yet, interaction based) on focused classes. Focused code is less likely to change, and focused classes are easier to test – this is the major advantage we gain by adjusting to the east oriented code.

  • Blessyahu

    Good article.  How easy it is to create tests with a certain technique can give you a broader perspective on if/when/where to use it.  Seems like it would be more involved to verify that only one star has been added, which would denote that verifying interactions are more valuable then verifying state.  If that is the case, that will take some time to wrap my mind around.  I keep thinking, “but how do I know that only one star has been added, if I don’t get a count?”. I would like to see more posts exploring testing with this technique.

TOP