Testing FS interaction class

151 views
Skip to first unread message

Norbert Nemes

unread,
Jun 23, 2013, 11:49:58 PM6/23/13
to clean-code...@googlegroups.com
Hello
 
I have just written an application in C# and it has a file system interaction class that I have no ide how to test.
 
It has 4 public methods:
- One that takes a path to a text file and returns the contents of the file as a List<String>
- One that takes a List<String> and a path and writes out the list to a text file
- One method that takes a DownloadDescriptor (DTO that contains a URL to download from, a path to download to
and a flag showing whether the download was successful) and downloads whatever the URL points at to a file on
the disc using the .Net WebClient class.
- One that cancels the download
 
I grouped the WEB downloader with the other methods based on "it's essentially a disc operation", but maybe it
should have its own class....
 
Anyway, I would like your oppinions on testing strategies for such a class.
 
Thank you.

Robert Snyder

unread,
Jun 24, 2013, 9:07:44 AM6/24/13
to clean-code...@googlegroups.com
I'm sure others will say this as well, but it is worth repeating. You should have written your test first. Much like in Episode 19 how Uncle Bob writes his test first then pulls out the methods into a class would have been how I would have done it. I would have had a test for file creation, then folder creation, then file and folder creation. I would have had a test for if I wanted the File to be appended to or not. I would then have worked on the save portion, then the load portion and use the 2 to assert that I've read what I've written.

As for the download portion I'm not sure exactly how I would have started. Maybe small text files with a checksum of some sort. I would have worked on a async download and tested the cancel. I think you should honestly though get Uncle Bob's 2 or 3 video's on TDD then try it again.

Norbert Nemes

unread,
Jun 24, 2013, 10:07:03 AM6/24/13
to clean-code...@googlegroups.com
Hello Robert.
Thank you for your feedback. I know I should have written the tests first, but I am the type of person who when presented with requirements, I already see how everything should fit together in my head pretty much to the smallest detail. So right now, I'm way faster writing the code before the tests. Plus the client needed the code ASAP, and it was my first time with NUnit, so I decided to write the code first, and then try to figure out how to use the test framework later. My final goal is of course TDD (and I've seen all the episodes and read all the books) but I still have to figure out some patterns before I feel confident enough writing tests first.
Besides, as you pointed out, the class I presented poses the problem: I know how to write the code, but no idea how to write the tests. How do you do TDD under these conditions? Furthermore, the file creation tests (and the networking part) you suggested are problematic, because if someone changes the folder permissions, or the network is down, those tests would fail for reasons completely unrelated to the code being tested.
What I would really like to know is should a class like this be (unit) tested at all? If so, how? What are the patterns used in this situation?

Uncle Bob

unread,
Jun 24, 2013, 12:18:44 PM6/24/13
to clean-code...@googlegroups.com
Norbert,

Your first method can be tested by having your unit test create a file with some text in it.  Call your function and make sure the text is returned.  Make sure you delete the file in your teardown.  It helps if you create the file on a ramdisk.  It also helps if you can mock out the file system calls -- but it's probably too late for that.  Had you written the test first, that would have been a bit easier.

The second method can be tested by ensuring the file does not exist, calling your method, and then calling the first method to see if the data was read appropriately.  Again a ramdisk will help.  Again, make sure you delete the file in your teardown.  And, again, mocking out the filesystem calls would be better; but it's probably too late for that.

The third function can be tested by creating a couple of sockets with local ip addresses, having one spit bytes out, and the other accept bytes in.  Call your functions with urls that id those sockets and make sure the bytes you sent from one are received by the other. You'll have to use multiple threads, and coordinate their starting and stoping, which is a pain.  This actually works a lot better if you mock out the socket library; but it's probably too late to do that now.

The fourth function sounds a bit hairier.  You're going to have to start the download by calling the third function in a thread; then in another thread, after a short delay, you'll have to call the forth function.  Then the other threads will have to verify that the download was terminated appropriately.  This kind of test often requires a bit of tweaking and can be the kind that leads to intermittent test failures, so you really need to understand how all the threading works, and what all the delays are.  Again, this is easier if you've mocked the socket library because you have a lot more control over the timing.

I hope this helps.  I expect that this explanation is unsurprising.  The way you test things is just exactly the way you would use them.  You simply have to set up the input and output conditions and verify that the output is what you expected, given the inputs.

Norbert Nemes

unread,
Jun 25, 2013, 4:52:05 AM6/25/13
to clean-code...@googlegroups.com
Hello Uncle Bob
 
Thank you for your reply.
I've been trying to implement your suggestions, unfortunately I was only 50% successful.
For the first 2 functions I created a DataProvider class that returns StreamReader to the first function and a StreamWriter to the second. It also has the job of closing the streams.
This allowed me to mock the DataProvider class and have it write to a Hashtable(String fileName, byte[] contents) instead of the disk, using MemoryStreams. (I did not want to use ramdisks because from what I googled they require installation, and I cannot expect the client to do that in order to run the tests. Perhaps you know of a good library that does the job?)
As for the DataProvider class, since it is only creating and closing streams, I suppose the only tests needed are the ones to check if the methods are called.
This took care of testing the first half of the class.
 
As for the second part, I am still stumped. The last 2 methods look like this:
 
       public void DownloadLink(DownloadDescriptor descriptor) {
           
String address = descriptor.URL;
           
WebClient client = new WebClient();
           
try {
               
Uri uri = new Uri(address);
                client
.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadLinkCompleted);
               
CreateTargetOutputDirectory(descriptor.TargetFileName);
                client
.DownloadFileAsync(uri, descriptor.TargetFileName, descriptor);
                currentDownload
= client;
           
} catch (Exception ex) {
                descriptor
.DownloadComplete = false;
               
AsyncCompletedEventArgs args = new AsyncCompletedEventArgs(ex, false, descriptor);
               
DownloadLinkCompleted(client, args);
           
}
       
}

       
private void DownloadLinkCompleted(Object sender, AsyncCompletedEventArgs e) {
           
DownloadDescriptor downloadDescriptor = (DownloadDescriptor)e.UserState;
           
String errorMessage = "";
           
if (e.Error != null)
                errorMessage
= e.Error.Message;
            ioDelegate
.DownloadCompleted(downloadDescriptor, errorMessage);
       
}

       
public void CancelCurrentDownload() {
           
if (currentDownload != null)
                currentDownload
.CancelAsync();
       
}

 
currentDownload is a class variable that holds the well... the current download and ioDelegate is the caller.
 
As you can see, all the DownloadLink and CancelCurrentDownload do is set up a WebClient and just call methods on it. If I create sockets and start a download from one of them,  the WebClient will download to the actual disk, and not to my nice hashtable. So I see no way of mocking anything here. Wait...I just realized that puting the WebClient routines into the DataProvider class and just forwarding the download call to it might work. The DataProvider mock would just simulate the download to the hashtable, and the tests for these 2 methods would simply be to check if they are actually called. Is this correct?
 

Uncle Bob

unread,
Jun 25, 2013, 9:47:42 AM6/25/13
to clean-code...@googlegroups.com
This is called "Explaining it to the bear".  You get a little toy bear, and explain your problem to the bear.  By the time you are done explaining the problem, the solution is often in your head.

Did it work for you?

Norbert Nemes

unread,
Jun 26, 2013, 1:40:00 AM6/26/13
to clean-code...@googlegroups.com
It sure did :)
 
Thank you. 
Reply all
Reply to author
Forward
0 new messages