Introduction
James 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
1 |
<span class="kwrd">public</span> <span class="kwrd">class</span> WestMovieLister |
1 |
{ |
1 |
<span class="kwrd">private</span> <span class="kwrd">readonly</span> IWestFinder moviesWestFinder; |
1 |
<span class="kwrd">public</span> WestMovieLister(IWestFinder moviesWestFinder) |
1 |
{ |
1 |
<span class="kwrd">this</span>.moviesWestFinder = moviesWestFinder; |
1 |
} |
1 |
<br /> |
1 |
<span class="kwrd">public</span> IWestMovie[] MoviesDirectedBy(<span class="kwrd">string</span> requestedDirector) |
1 |
{ |
1 |
var allMovies = moviesWestFinder.FindAll(); |
1 |
var directorMovies = allMovies.Where(movie => movie.Director == requestedDirector).ToArray(); |
1 |
<span class="kwrd">return</span> directorMovies; |
1 |
} |
1 |
} |
1 |
<span class="kwrd">public</span> <span class="kwrd">class</span> WestStarsAdder |
1 |
{ |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> AddStarsToMovies(IWestMovie[] moviesToAddStartTo) |
1 |
{ |
1 |
<span class="kwrd">foreach</span> (var movie <span class="kwrd">in</span> moviesToAddStartTo) |
1 |
{ |
1 |
movie.AddStar(); |
1 |
} |
1 |
} |
1 |
} |
West tests
[TestMethod]
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> TestWestMovieLister() |
1 |
{ |
1 |
var fakeSpielbergMovie = Isolate.Fake.Instance<WestMovie>(); |
1 |
Isolate.WhenCalled(() => fakeSpielbergMovie.Director).WillReturn(<span class="str">"Steven Spielberg"</span>); |
1 |
Isolate.WhenCalled(() => fakeNonSpielbergMovie.Director).WillReturn(<span class="str">"Not Steven Spielberg"</span>); |
1 |
1 |
var fakeFinder = Isolate.Fake.Instance<WestFinder>(); |
1 |
Isolate.WhenCalled(() => fakeFinder.FindAll()) |
1 |
.WillReturnCollectionValuesOf(<span class="kwrd">new</span> List<WestMovie> { fakeSpielbergMovie, fakeNonSpielbergMovie }); |
1 |
1 |
var movieLister = <span class="kwrd">new</span> WestMovieLister(fakeFinder); |
1 |
var movies = movieLister.MoviesDirectedBy(<span class="str">"Steven Spielberg"</span>); |
1 |
CollectionAssert.AreEquivalent(<span class="kwrd">new</span>[] { fakeSpielbergMovie }, movies); |
1 |
} |
1 |
<span class="Apple-style-span" style="font-family:Georgia, serif;font-size:130%;"><span class="Apple-style-span" style=" white-space: normal;font-size:16px;"><br /></span></span> |
1 |
[TestMethod] |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> TestWestStarsAdder() |
1 |
{ |
1 |
var fakeMovie = Isolate.Fake.Instance<WestMovie>(Members.CallOriginal, ConstructorWillBe.Called, <span class="str">"Director"</span>); |
1 |
<br /> |
1 |
var starsAdder = <span class="kwrd">new</span> WestStarsAdder(); |
1 |
starsAdder.AddStarsToMovies(<span class="kwrd">new</span>[] {fakeMovie}); |
1 |
1 |
Assert.AreEqual(1, fakeMovie.Stars); |
1 |
} |
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
1 |
<span class="kwrd">public</span> <span class="kwrd">class</span> EastMovieLister |
1 |
{ |
1 |
<span class="kwrd">private</span> <span class="kwrd">readonly</span> IEastFinder moviesFinder; |
1 |
<br /> |
1 |
<span class="kwrd">public</span> EastMovieLister(IEastFinder moviesFinder) |
1 |
{ |
1 |
<span class="kwrd">this</span>.moviesFinder = moviesFinder; |
1 |
} |
1 |
<br /> |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> ApplyToMoviesDirectedBy(IMovieAction movieAction, <span class="kwrd">string</span> director) |
1 |
{ |
1 |
moviesFinder.FindAllAndApply(<span class="kwrd">new</span> MovieDirectorFilterActionDecorator(movieAction, director)); |
1 |
} |
1 |
<br /> |
1 |
<span class="kwrd">class</span> MovieDirectorFilterActionDecorator : IMovieAction |
1 |
{ |
1 |
<span class="kwrd">private</span> <span class="kwrd">readonly</span> IMovieAction movieAction; |
1 |
<span class="kwrd">private</span> <span class="kwrd">readonly</span> <span class="kwrd">string</span> director; |
1 |
<span class="Apple-style-span" style="font-size:-webkit-xxx-large;"><br /></span> |
1 |
<span class="kwrd">public</span> MovieDirectorFilterActionDecorator(IMovieAction movieAction, <span class="kwrd">string</span> director) |
1 |
{ |
1 |
<span class="kwrd">this</span>.movieAction = movieAction; |
1 |
<span class="kwrd">this</span>.director = director; |
1 |
} |
1 |
<br /> |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> ApplyTo(IEastMovie movie) |
1 |
{ |
1 |
movie.IfDirectedByDo(director, movieAction); |
1 |
} |
1 |
} |
1 |
} |
1 |
<span class="kwrd">public</span> <span class="kwrd">class</span> EastMovie : IEastMovie |
1 |
{ |
1 |
<span class="kwrd">private</span> <span class="kwrd">readonly</span> <span class="kwrd">string</span> director; |
1 |
<br /><pre class="alt"> <span class="kwrd">public</span> EastMovie(<span class="kwrd">string</span> director) |
1 |
{<br /><pre class="alt"> <span class="kwrd">this</span>.director = director; |
1 |
}<br /><br /> |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> AddStar()<br /><pre class="alt"> { |
1 |
stars++;<br /><pre class="alt"> } |
1 |
<br /> |
1 |
<span class="kwrd">private</span> <span class="kwrd">int</span> stars; |
1 |
<br /><pre class="alt"> <span class="kwrd">public</span> <span class="kwrd">void</span> IfDirectedByDo(<span class="kwrd">string</span> directorToFilter, IMovieAction movieAction) |
1 |
{ |
1 |
<span class="kwrd">if</span> (director == directorToFilter) |
1 |
{ |
1 |
movieAction.ApplyTo(<span class="kwrd">this</span>); |
1 |
} |
1 |
} |
1 |
}<br /> |
East tests
The moment we start thinking of a test we see that there is no state to verify against:
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> ApplyToMoviesDirectedBy(IMovieAction movieAction, <span class="kwrd">string</span> 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
1 |
[TestMethod] |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> TestEastMovieLister()<br /><pre class="alt">{ |
1 |
var fakeFinder = Isolate.Fake.Instance<EastFinder>();<br /><pre class="alt"> var mockMovieAction = Isolate.Fake.Instance<IMovieAction>(); |
1 |
<br /><pre class="alt"> var movieLister = <span class="kwrd">new</span> EastMovieLister(fakeFinder); |
1 |
movieLister.ApplyToMoviesDirectedBy(mockMovieAction, <span class="str">"Steven Spielberg"</span>);<br /><pre class="alt"> |
1 |
Isolate.Verify.WasCalledWithAnyArguments(() => fakeFinder.FindAllAndApply(<span class="kwrd">null</span>));<br /><pre class="alt">} |
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
1 |
[TestMethod] |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> TestWestMovieLister()<br /><pre class="alt">{ |
1 |
var fakeMovie = Isolate.Fake.Instance<EastMovie>(Members.CallOriginal,<br /><pre class="alt"> ConstructorWillBe.Called, |
1 |
<span class="str">"Steven Spielberg"</span>);<br /><pre class="alt"> var fakeFinder = Isolate.Fake.Instance<EastFinder>(Members.CallOriginal); |
1 |
fakeFinder.AddMovie(fakeMovie);<br /><pre class="alt"> |
1 |
var mockMovieAction = Isolate.Fake.Instance<IMovieAction>();<br /><br /> |
1 |
var movieLister = <span class="kwrd">new</span> EastMovieLister(fakeFinder);<br /><pre class="alt"> movieLister.ApplyToMoviesDirectedBy(mockMovieAction, <span class="str">"Steven Spielberg"</span>); |
1 |
<br /><pre class="alt"> Isolate.Verify.WasCalledWithAnyArguments(() => mockMovieAction.ApplyTo(<span class="kwrd">null</span>)); |
1 |
}<style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode, .csharpcode pre<br />{<br /> font-size: small;<br /> color: black;<br /> font-family: consolas, "Courier New", courier, monospace;<br /> background-color: #ffffff;<br /> /*white-space: pre;*/<br />}<br />.csharpcode pre { margin: 0em; }<br />.csharpcode .rem { color: #008000; }<br />.csharpcode .kwrd { color: #0000ff; }<br />.csharpcode .str { color: #006080; }<br />.csharpcode .op { color: #0000c0; }<br />.csharpcode .preproc { color: #cc6633; }<br />.csharpcode .asp { background-color: #ffff00; }<br />.csharpcode .html { color: #800000; }<br />.csharpcode .attr { color: #ff0000; }<br />.csharpcode .alt <br />{<br /> background-color: #f4f4f4;<br /> width: 100%;<br /> margin: 0em;<br />}<br />.csharpcode .lnum { color: #606060; }</style><br /> |
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
1 |
[TestMethod] |
1 |
<span class="kwrd">public</span> <span class="kwrd">void</span> TestEastMovieLister()<br /><pre class="alt">{ |
1 |
var mockMovie = Isolate.Fake.Instance<EastMovie>(Members.CallOriginal,<br /><pre class="alt"> ConstructorWillBe.Called, |
1 |
<span class="str">"Steven Spielberg"</span>);<br /><pre class="alt"> var fakeFinder = Isolate.Fake.Instance<EastFinder>(Members.CallOriginal); |
1 |
fakeFinder.AddMovie(mockMovie);<br /><br /> |
1 |
var fakeMovieAction = Isolate.Fake.Instance<AddStarAction>(Members.CallOriginal);<br /><pre class="alt"> |
1 |
var movieLister = <span class="kwrd">new</span> EastMovieLister(fakeFinder);<br /><pre class="alt"> movieLister.ApplyToMoviesDirectedBy(fakeMovieAction, <span class="str">"Steven Spielberg"</span>); |
1 |
<br /><pre class="alt"> Isolate.Verify.WasCalledWithAnyArguments(() => mockMovie.AddStar()); |
1 |
}<style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode, .csharpcode pre<br />{<br /> font-size: small;<br /> color: black;<br /> font-family: consolas, "Courier New", courier, monospace;<br /> background-color: #ffffff;<br /> /*white-space: pre;*/<br />}<br />.csharpcode pre { margin: 0em; }<br />.csharpcode .rem { color: #008000; }<br />.csharpcode .kwrd { color: #0000ff; }<br />.csharpcode .str { color: #006080; }<br />.csharpcode .op { color: #0000c0; }<br />.csharpcode .preproc { color: #cc6633; }<br />.csharpcode .asp { background-color: #ffff00; }<br />.csharpcode .html { color: #800000; }<br />.csharpcode .attr { color: #ff0000; }<br />.csharpcode .alt <br />{<br /> background-color: #f4f4f4;<br /> width: 100%;<br /> margin: 0em;<br />}<br />.csharpcode .lnum { color: #606060; }</style><br /> |
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.