Introduction
I want to demonstrate a nice time saving pattern for testing interfaces. It allows us to write the unit tests for an interface just once and use these tests to test any number of implementations of our interface.
The examples are written using MSTest in Visual Studio 2012.
Using the Code
String Searcher Interface
First, we need to define an interface for the example. Here is a simple interface to search string
s. The example code contains a few different implementations of the string
search algorithm. The implementations of these algorithms are from another CodeProject article by Carl Daniel.
public interface IStringSearcher
{
IEnumerable<int> SearchString(string stringToSearch, string pattern);
IEnumerable<int> SearchString(string stringToSearch, string pattern, int startingIndex);
}
Then, we have an abstract
implementation of the interface StringSearcherBase
which contains a few useful protected
methods that our string
search algorithms are going to need.
Finally, a few implementations of the algorithm. One that uses String.IndexOf
, and another that uses the Bayer-Moore Algorithm + a few others.
public class StringSearcherIndexOf : StringSearcherBase
{
public override IEnumerable<int> SearchString
(string stringToSearch, string pattern, int startingIndex)
{
int patternLength = pattern.Length;
int index = startingIndex;
do
{
index = stringToSearch.IndexOf(pattern, index,
StringComparison.InvariantCultureIgnoreCase);
if (index < 0)
yield break;
yield return index;
index += patternLength;
}
while (true);
}
}
public class StringSearcherBoyerMoore : StringSearcherBase
{
public override IEnumerable<int> SearchString(string stringToSearch, string pattern,
int startingIndex)
{
int[] badCharacterShift = BuildBadCharacterShift(pattern);
int[] suffixes = FindSuffixes(pattern);
int[] goodSuffixShift = BuildGoodSuffixShift(pattern, suffixes);
int patternLength = pattern.Length;
int textLength = stringToSearch.Length;
int index = startingIndex;
while (index <= textLength - patternLength)
{
int unmatched;
for (unmatched = patternLength - 1;
unmatched >= 0 &&
(pattern[unmatched] == stringToSearch[unmatched + index]);
--unmatched)
;
if (unmatched < 0)
{
yield return index;
index += goodSuffixShift[0];
}
else
index += Math.Max(goodSuffixShift[unmatched],
badCharacterShift[stringToSearch[unmatched + index]] -
patternLength + 1 + unmatched);
}
}
}
Writing the Unit Tests
Having to write a set of tests for each implementation of the IStringSearcher
interface would be a massive waste of time. What we want to do is write the tests once and reuse them to test all the implementations of the string
search algorithm.
To do this, we are going to write an abstract
base class where the tests will actually be written. Note the abstract
method GetStringSearcherInstance
.
[TestClass]
public abstract class StringSearcherTestBase
{
public abstract IStringSearcher GetStringSearcherInstance();
[TestMethod]
public void BasicTest()
{
IStringSearcher searcher = GetStringSearcherInstance();
List<int> indexes = searcher.SearchString(
"Hello. Welcome to unit testing interfaces",
"test").ToList();
Assert.AreEqual(1, indexes.Count);
Assert.AreEqual(23, indexes[0]);
}
[TestMethod]
public void NegativeTest()
{
IStringSearcher searcher = GetStringSearcherInstance();
var indexes = searcher.SearchString(
"Hello. Welcome to unit testing interfaces",
"uint").ToList();
Assert.AreEqual(0, indexes.Count);
}
Then, we simply implement this test class for each of the string
algorithms we want to test and override the GetStringSearcherInstance
method to return the IStringSearcher
class we want to test.
[TestClass]
public class StringSearcherBoyerMoore_Tests : StringSearcherTestBase
{
public override IStringSearcher GetStringSearcherInstance()
{
return new StringSearcherBoyerMoore();
}
}
Looking in the test explorer, we see each test available for the implementation we are testing. It's as simple as that.
Moreover, if for some subtle reason, I want to make a test different in one of the implementations, then I can mark it as virtual in the test base class and override it.
For example, if the startingIndex
is out of bounds, then all the string
search classes just return no matches, but StringSearcherIndexOf
throws an exception. So this test is overridden in StringSearcherIndexOf_Tests
to test for the exception.
[TestClass]
public class StringSearcherIndexOf_Tests : StringSearcherTestBase
{
public override IStringSearcher GetStringSearcherInstance()
{
return new StringSearcherIndexOf();
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public override void NegativeTest_StartingIndexOutOfBounds()
{
IStringSearcher searcher = GetStringSearcherInstance();
var indexes = searcher.SearchString(
"Hello. Welcome to unit testing interfaces",
"unit",
500).ToList();
}
}
Points of Interest
The above was done using MSTest. I haven't tried with any other testing frameworks. It would be interesting to see how other unit test frameworks handle it.