6

I am a .Net developer coding in C#, and I have been assigned to a project that its manager wants it to be thoroughly unit tested, mostly emphasising isolation so that one logical bug ideally fails one test, and there are no dependencies between tests.

Today we are discussing testing patterns, and the following question has arised:

Lets say we have an object named MyHashTable, that implements:

void Add(string key, string value);

string GetValue(string key);

We want to test each of those methods independently. The main problem of course, logically we can't get what we never added, and we can't verify something was added without getting it. We have heard and read about stubbing/mocking and other techniques that might help overcome this issues, but can't decide which solution would be most readable and maintanable.

Therefore I ask for advices/ideas how to test those methods in isolation, and if you may, include pros and cons for your suggestion. Thank you!

graham.reeds
  • 16,230
  • 17
  • 74
  • 137
Eldar S
  • 579
  • 3
  • 17
  • Please clarify, is MyHashTable an object of a type that you implemented yourself and want to test the internals of, or just an instance of a class you're using "as is" and want to test the external interface of? – Joachim Isaksson Mar 02 '12 at 08:15

2 Answers2

2

If you want to achieve full independence and isolation, then you'll most likely have to expose some internals of MyHashTable. For example, underlying collection that Add adds elements to:

public class MyHashTable<TKey, TValue>
{
    internal Dictionary<TKey, TValue> Storage { get; set; }
    // ...
}

This isn't very pretty as you can see, but like I said, it's not possible to test those two methods in full isolation without exposing something (like you noticed, it's a method pair). You can naturally provide other ways to initialize your class, eg. with constructor taking collection, but it only delegates problem one step further - you'll need to verify that constructor worked as well, and you'll probably need Get method for that... which brings you back to original problem.

Edit:

Note that I'm not suggesting revealing internals/implementation details as the only path to follow. You can simply test those methods together (not as in single test, but one utilizing other), which might prove to be better solution. Sure - if your Add at some point fails, Get tests will fail too. But then again, you want to fix broken Add - once that's done everything is back to normal. Problem naturally lies in how do you distinguish whether it was Add or Get that got broken - but that's where proper assertion failure messages or guard assertions concept I linked in comment come in handy.

Truth is, sometimes full separation and isolation is way too difficult to achieve or simply would need you to introduce poor design. That's one of the reasons they should never be final goal.

k.m
  • 30,794
  • 10
  • 62
  • 86
  • So basically those methods have to be treated as a pair, and cannot be tested in isolation from each other. Have you, in your tests projects allowed a group of tests to depend on each other and not be fully isolated? There is no need to be fixed on the isolation concept, if it means to start adding wierd internals or other stuff that will just make the code under test less maintanable – Eldar S Mar 02 '12 at 08:24
  • @user1244563: The problem here is that some things just can't be tested in full isolation (your `Add` / `Get` are perfect example), unless you expose some extra information about their inner workings (in this case - collection they work on). I'd say it's fairly normal to assume one of them works at some point and rely on that later (I think you might be interested in checking [these](http://stackoverflow.com/q/9018142/343266) [two](http://stackoverflow.com/q/9385715/343266) questions). – k.m Mar 02 '12 at 08:31
  • Revealing the inner workings is something i strongly disagree with... not because I'm an encapsulation freak (as much as I'm not an isolation freak) but because one day I might want to use, lets say, MemCache as my inner storage. So now all my tests have to be refactored. I don't want tests relying on inner workings same way I don't want my API clients relaying on them (a test is really just another API client). I only do so when code is expected to call other code (interaction testing) because I want to ensure seperation of concerns, to ensure that an API is used when needed and nothing else. – Eldar S Mar 02 '12 at 08:51
  • @user1244563: I'm totally on with you about that. I didn't mean to suggest *you must expose internals* (note the edit). It's just one of ways (and frankly, not always the best one). Utiliznig your existing code in tests is just as fine, and often preferred way. – k.m Mar 02 '12 at 09:12
  • My conclusion is that when some methods cant logically be isolated, then they shouldn't be tested in isolation. One test named AddThenGet_XXX would do an Add and then a Get and assert the result of both invocations, because really the API clients would use the object the same way (in some point an item will be added, some point later will be retrieved). That way I dont have one test per potential bug, but one test for two potential bugs which is still good enough in my case, and reflects the nature of the API's actual usage. – Eldar S Mar 02 '12 at 10:57
0

Why? The purpose of a unit test is to make sure that the tested method works as expected. Nothing says that you can't use the other methods to make sure that the tested method works OK.

the following tests are fine by me (for a hashtable implementation)

[Fact]
public void AddNewItem()
{
    var hashTable = new MyHashTable();

    hashTable.Add("hello", "world");

    Assert.True(hashTable.Exists("hello"));
    Assert.Equal("world", hashTable["hello"]);
}

[Fact]
public void AddExistingItem()
{
   var hashTable = new MyHashTable();

   hashTable.Add("hello", "world");
   hashTable.Add("hello", "realItem");

   Assert.Equal("realItem", hashTable["hello"]);
}

You could of course break out the inner storage into an interface and inject it in a constructor:

public class MyHashTable
(
    public MyHashTable(IKeyValueStorage storage)
    {}

    // creates a default storage class
    public MyHashTable()
    {}
)

Which allows you to mock the storage and therefore test a single method at a time. But that just moves the problem one step further. how are you going to test the storage implementation?

What I'm saying is that a HashTable is a small isolated unit. It's fine to test it by using it's methods. You won't have that problem with domain models since you can mock their dependencies.

jgauffin
  • 99,844
  • 45
  • 235
  • 372