QL-5) Working with Untestable Code
Consider this small but innocuous piece of code
using System;
using NodaTime;
namespace why_spy
{
public class DataClerk
{
public void ProcessData()
{
DateTime now = DateTime.Now;
DateTime stopTime = new DateTime(now.Year, now.Month, now.Day, 20, 0, 0, 0);
if( now < stopTime )
{
Console.WriteLine("Ready to process the data");
FileLog fl = new FileLog();
fl.ClearTheLog();
}
}
}
class FileLog
{
public void ClearTheLog()
{
// Simulated method that would do something to files in the log
}
}
}
There are a number of things that should cause the alarm bells to ring
Line 13 constrains the testing of this method. We can only test all paths if the now variable holds a time that is less than 2000 hours
Line 16, the dependent class could cause an issue if it performs IO, Network, and DB calls.
Line 16 can be resolved by injecting the FileLog in through the constructor or ProcessData method. You would need to refactor the code and write a test to prove the CUT is not broken
Line 13 can be resolved by refactoring the code so that Lines 10-11 are in a method of their own, and calling that method in Line 13 if( getTime() < stopTime )
Activities
If you are stuck at any time, use the code in the why-spy.zip
and use git tag
and git checkout <tag>
to see how the code evolves
Refactoring the code and writing a new test to support the refactored code - read Activity 1 Observations
Refactor the code so that the FileLog is injected into the DataClerk
Decide how you can test it to verify that the code is still working.
Give attention to whether you should be injecting the class or an interface
Use the NSubstitute
When... Do...
orReceived()
(or a library you are familiar with that has the same capabilities) feature to set up an expectation that can be tested in an Assert.
Line 11 is an issue. The timestamp is hard coded. Not a problem you say. We can remove the magic number so it’s declared as a constant field in the class. This doesn’t really help us because we can only test alternative paths after 8pm. We can use a Spy mechanism to solve this. This can be done by refactoring the code so that the current time is returned from a private method in the DataClerk class.
Turn line 10 into a
virtual public
functionUse the private function to get the time
In the test use NSubstitute
Substitute.ForPartsOf<Class>();
to override the returned time so it is greater than 2000 hours. - see NSubstitue API Guide
An alternative design for activity 2 would be to use a lambda expression in line 13
Think about how this might work, and what would the test look like?