views:

81

answers:

2

A part of my project interacts with iTunes using COM. The goal of the test in question is to verify that my object asks the iTunes API to export all of the album artwork for a collection of tracks to a file.

I have successfully written a test which can prove my code is doing this, however to accomplish that I have had to stub out a chunk of the iTunes implementation, while thiis is to be expected in a unit test I am concerned at the ratio of stub setup code vs. code doing actual testing

My questions:

  1. Is the fact that there is more stub setup code then acting code indicitive of another underlying problem in my code>
  2. There is a lot of setup code and I don't believe repeating it per test is a good idea. What is the best way to refactor this code so that this setup code is seperate from, but available to other tests that need to utilise the stubs.

This seams like the kind of question that might have been asked before, so I applagise in advance if I have created a duplicate

For reference, here is the complete unit test that I am concerned about

[Fact]
    public void Add_AddTrackCollection_AsksiTunesToExportArtForEachTrackInCollectionToAFile()
    {
        var trackCollection = MockRepository.GenerateStub<IITTrackCollection>(null);
        var track = MockRepository.GenerateStub<IITTrack>(null);
        var artworkCollection = MockRepository.GenerateStub<IITArtworkCollection>(null);
        var artwork = MockRepository.GenerateMock<IITArtwork>(null);
        var artworkCache = new ArtworkCache();
        trackCollection.Stub<IITTrackCollection, int>(collection => {return collection.Count; }).Return(5);
        trackCollection.Stub<IITTrackCollection, IITTrack>(collection => { return trackCollection[0]; }).IgnoreArguments().Return(track);
        track.Stub<IITTrack, IITArtworkCollection>(stub => { return stub.Artwork; }).Return(artworkCollection);
        artworkCollection.Stub<IITArtworkCollection, int>(collection => { return collection.Count; }).Return(1);
        artworkCollection.Stub<IITArtworkCollection, IITArtwork>(collection => { return artworkCollection[0]; }).IgnoreArguments().Return(artwork);
        artwork.Expect<IITArtwork>(stub => { stub.SaveArtworkToFile(null); }).IgnoreArguments().Repeat.Times(trackCollection.Count-1);
        artwork.Replay();
        artworkCache.Add(trackCollection);
        artwork.VerifyAllExpectations();

        //refactor all the iTunes fake-out that isn't specific to this test into it's own method and call that from ctor.
+5  A: 

The need for a lot of setup code indicates that your code under test interacts deeply with a complex interface; that's not necessarily a problem (it's not, per se, a "code smell") though it may suggest that eventually you might put a facade in front of that complex interface and interact with that (at that time testing the facade may require some setup, but your "substantial" application code will be simpler AND simpler to test), at least if you can identify any repetitive patterns of interaction you may be using.

Widespread unit-test frameworks such as junit, unittest, &c, recognize the need for repetitive setup code and thereby define "test case" classes with a setUp method (and a corresponding tearDown one in case you need to undo something there). If you have otherwise disparate groups of tests that yet need SOME common setup code, but also some setup that's per-group but different from other groups, you can share the "common setup code" by inheriting from an appropriate abstract base class, calling a global function, or as otherwise appropriate in your favorite language.

Alex Martelli
+2  A: 

To address both of your questions:

  1. In both common unit test patterns (Arrange/Act/Assert, and Four-Phase Test), there will almost always be more Arrange code than Act code, since by definition, Act should only contain a single statement. However, it is still a good idea to attempt to minimize the Arrange code as much as possible.

  2. There are more than one way to refactor Setup code into reusable code. As Alex writes in his answer, many unit testing frameworks support setup methods. This is called Implicit Fixture Setup, and in my opinion something that should be avoided, since it does not communicate intent very well. Instead, I prefer explicit setup methods, usually encapsulated into a Fixture Object.

In general, the need for complex Setup code should always cause you to consider if you can model your API differently. This is not always the case, but when it is, you often end up with a better and more concise API than you started out with. That's one of the advantages of TDD.

For the cases where setup is just complex because input is complex, I will recommend AutoFixture, which is a general-purpose Test Data Builder.

Many of the patterns I have used in this answer are described in xUnit Test Patterns, which is an excellent book.

Mark Seemann
I was aware of explicit test setup methods and am not a fan of them for the reasons you stated. Having given it more thaught I think it might be best in this case to redesign the API to abstract the dependency on iTubes a bit
Crippledsmurf