6

After reading a blog post mentioning how it seems wrong to expose a public getter just to facilitate testing, I couldn't find any concrete examples of better practices.

Suppose we have this simple example:

public class ProductNameList
{
    private IList<string> _products;
    public void AddProductName(string productName)
    {
        _products.Add(productName);
    }
}

Let's say for object-oriented design reasons, I have no need to publicly expose the list of products. How then can I test whether AddProductName() did its job? (Maybe it added the product twice, or not at all.)

One solution is to expose a read-only version of _products where I can test whether it has only one product name -- the one I passed to AddProductName().

The blog post I mentioned earlier says it's more about interaction (i.e., did the product name get added) rather than state. However, state is exactly what I'm checking. I already know AddProductName() has been called -- I want to test whether the object's state is correct once that method has done its work.

Disclaimer: Although this question is similar to Balancing Design Principles: Unit Testing, (1) the language is different (C# instead of Java), (2) this question has sample code, and (3) I don't feel the question was adequately answered (i.e., code would have helped demonstrate the concept).

Community
  • 1
  • 1
geoffmazeroff
  • 171
  • 1
  • 7
  • I think something that's making this trickier is that the _behavior_ of `AddProductName()` is that it _changes the state_ of `ProductNameList`. – geoffmazeroff Jul 19 '11 at 20:48

4 Answers4

7

Unit tests should test the public API.

If you have "no need to publicly expose the list of products", then why would you care whether AddProductName did its job? What possible difference would it make if the list is entirely private and never, ever affected anything else?

Find out what affect AddProductName has on the state that can be detected using the API, and test that.

Very similar question here: Domain Model (Exposing Public Properties)

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • So if I have `Add`, and I also have `Frob` that acts upon the things I added, if I add *bar* but it is not frobbed, which is broken? `Frob` or `Add`? – Anthony Pegram Jul 19 '11 at 20:12
  • I'll take a look at the domain model question -- thanks for the link. The private list could be used by other methods/properties in the ProductNameList class. The consumer of the class doesn't care how the names are stored (queue, stack, list, dictionary) -- they just need to be stored. AddProductName() is a layer between the consumer and the actual data structure storing the names. – geoffmazeroff Jul 19 '11 at 20:29
  • Anthony: That's not something unit tests will determine for you, as Add and Frob are are actions that only have meaning in respect to eachother. – Vladislav Jul 19 '11 at 20:53
  • OTOH, if you had a Frob, a Bar, a Foo, that all acted on Added objects, and a code change would break all three tests, then you can be pretty certain the problem is in Add. – Vladislav Jul 19 '11 at 21:01
1

You could make your accessors protected so you can mock or you could use internal so that you can access the property in a test but that IMO would be wrong as you have suggested.

I think sometimes we get so caught up in wanting to make sure that every little thing in our code is tested. Sometime we need to take a step back and ask why are we storing this value, and what is its purpose? Then instead of testing that the value gets set we can then start testing that the behaviour of the component is correct.

EDIT

One thing you can do in your scenario is to have a bastard constructor where you inject an IList and then you test that you have added a product:

public class ProductNameList
{
    private IList<string> _products;

    internal ProductNameList(IList<string> products)
    {
        _products = products;
    }
    ...
}

You would then test it like this:

[Test]
public void FooTest()
{
    var productList = new List<string>();

    var productNameList = new ProductNameList(productList);

    productNameList.AddProductName("Foo");

    Assert.IsTrue(productList[0] == "Foo");
}

You will need to remember to make internals visable to your test assembly.

Bronumski
  • 14,009
  • 6
  • 49
  • 77
  • I agree that we focus on testing maybe more than we should. What I aim to test is whether the behavior "product name was added" occurred. How would I verify that behavior? – geoffmazeroff Jul 19 '11 at 20:37
  • @geoffmazeroff Okay so what is the purpose of adding a product name, how is that used? Is there more to the class that we cannot see? The other option you have is to use a bastard constructor (see my edit), which requires having a constructor specifically for testing but because it is internal nothing gets leaked. – Bronumski Jul 19 '11 at 20:45
  • I hadn't really thought about what other methods there are, but suppose I had a method `public IList GetProductNames`. If I unit test that, I have a **circular dependency**: I can't test Add with Get, and I can't test Get without Add. (I'm a big fan of testing only one thing at a time.) I think your internal constructor trick may be the solution I'm looking for! – geoffmazeroff Jul 19 '11 at 20:58
  • 1
    That's the thing - you can't test Add without Get. Add has no meaning without Get. Get has no meaning without Add (Except to return an empty list.) Testing that Add modifies the internals of a class in the way you expect is the wrong way to go about testing Add. The internals of a class should be entirely independent from its API. The whole point of unit testing is in verifying that the class adheres to it's API after you change the internals. Your tests should not fail if you change the internals, as long as the API functions correctly. And the way you store your names is not part of the API. – Vladislav Jul 19 '11 at 22:19
  • @geoffmazeroff As Vladislav said you cannot test one without the other unless you design your class to suite your test which is wrong. You should try not to write tests that enforce how a class should act. Someone calling AddProductName doesn't care how the product name is stored just that it is stored and that it can be retrieved. Maybe you should look at BDD testing using something like StoryQ. BDD is all about behaviour not implementation. – Bronumski Jul 20 '11 at 00:08
0

Make _products protected instead of private. In your mock, you can add an accessor.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
0

To test if AddProductName() did it's job, instead of using a public getter for _ProductNames, make a call to GetProductNames() - or the equivalent that's defined in your API. Such a function may not necessarily be in the same class.

Now, if your API does not expose any way to get information about product names, then AddProductName() has no observable side effects (In which case, it is a meaningless function).

If AddProductName() does have side effects, but they are indirect - say, a method in ProductList that writes the list of product names to a file, then ProductList should be split into two classes - one that manages the list, and the other that calls it's Add and Get API, and performs side effects.

Vladislav
  • 4,806
  • 1
  • 18
  • 15
  • AddProductName() does have a side effect -- it modifies a private field that other methods/properties could end up using. – geoffmazeroff Jul 19 '11 at 20:40
  • Then your unit test should first call AddProductName(), and then call those other methods/properties, that rely on the list of product names, and verify that they behave consistently. – Vladislav Jul 19 '11 at 21:44