Sometimes there are pieces of code that you want to test but you just don’t quite see how you can unit test it easily. I have illustrated one such example below where I am interacting with the file system.
Take the following snippet for example:
1: public class FileRenamer : IFileRenamer
2: {
3: public void RenameFile(IList<DomainFile> files)
4: {
5: if (files == null)
6: return false;
7:
8: foreach (var file in files)
9: {
10: var newName = string.Concat(file.FullFileName, file.Client, file.Site, file.DesagilationCount, ".file");
11: File.Copy(file.FullFileName, newName);
12: }
13: }
14: }
I want to “unit test” that the correct file is created, but I really can’t. The “File.Copy()” is going to hang me up a bit because in order to verify the method worked I am going to have to go visually verify that a file was copied to a certain location. I could of course automate this but in either case I’m touching the file system, which is not a quality of a good unit test.
In reality, the only thing I really care about here is that the new file is named properly. However, as it sits, the new name is really coupled to the copy process and the hard disk. Let’s change that…
1: public class FileRenamer : IFileRenamer
2: {
3: public void RenameFile(IList<DomainFile> files)
4: {
5: if (files == null)
6: return false;
7:
8: foreach (var file in files)
9: {
10: File.Copy(file.FullFileName, BuildNewFilename(file));
11: }
12: }
13:
14: public string BuildNewFilename(DomainFile file)
15: {
16: return string.Concat(file.FullFileName,file.Client,file.Site,file.DesagilationCount,".file");
17: }
18: }
What I’ve done here is move the logic of the new name creation to a new method. By extracting that single line to its own method, we’ve made the “untestable”, testable.
So at this point you may be asking yourself why this strategy is called “Hiding Out In the Open”? I call it that (is there another more formal name? possibly an xUnit test pattern name?) because we haven’t added the method to the interface (the “hiding” part) but we have made the method public so it’s visible to consumers (the “out in the open”).
Hiding
When I added the public method to the FileRenamer class, I *did not* add it to the interface IFileRenamer. That means anywhere where this particular class is referenced as an IFileRenamer that the new method (BuildNewFilename) won’t be visible (see below). Hence the usage of the term “hiding”:
Out In the Open
The whole point of this exercise was to gain some testability aspects for this particular class. Since the method is public, in our test method when we create an instance of our FileRenamer class, we now have the ability to test (see below):

Some Caveats
Some will argue that the renaming of the file is a separate concern from the copy. The separate concern would then be a separate class that is possibly used by the FileRenamer, in which case the new class would be testable. This is a fair argument and at times it’s a good strategy. I think it’s good to know the strategy so there are more tools in the bag when a curious situation arises.
Some will argue that you’re increasing the surface area of your API when you make the method public. Again, a fair argument however, in most cases when dealing with the abstraction (the interface) this doesn’t rear it’s head. It’s a small insignificant side-effect. If you don’t like it don’t use it. Disclaimer: I strongly discourage this course of action if you’re building a public API that others will consume. In my case, 99% of software I write is used in house. If making something “public” really bothers you, then make it internal and use the “InternalsVisibleTo” assembly attribute to give your test assembly visibility.
Conclusion:
The original snippet of code above had some pieces that were hard to properly unit test. What we’ve done here employed a strategy I call “hiding out in the open” to the class under test to achieve a bit more testability. I think this is just another testing strategy to have in your back pocket. There should be few times when you need this particular strategy, but when you need it it does come in handy.
Posted
12-16-2009 9:38 AM
by
Tim Barcz