2
\$\begingroup\$

I have this method inside a class that is responsible for setting up and executing a model:

public FlowJob PrepareAndScheduleFlowModelJob(string coordinates)
{
 GeometryCoordinateString coordinateString = 
 new GeometryCoordinateString(coordinates);
 DataSet intersectedWatershedsDataSet = 
 FindIntersectingWatersheds(coordinateString);
 List<string> xCoords = new List<string>();
 List<string> yCoords = new List<string>();
 List<string> ascFiles = new List<string>();
 string submitJobID = "fromServa" + new Random().Next().ToString();
 ProcessWatershedResults(intersectedWatershedsDataSet, xCoords, yCoords, ascFiles);
 ScheduleFlowJob(xCoords, yCoords, ascFiles, submitJobID);
 return new FlowJob(submitJobID);
}

It is a long running process so the client caller uses the FlowJob to poll status and get results. The client itself doesn't care about any of the intermediate data and only wants to specify the input location for the model to run.

Every method that is called inside this method have their own unit tests already to ensure those work properly.

I know I can write an integration test ensuring that the entire thing in fact works.

I don't see anything here to unit test except that various methods are called. Any suggestions?

palacsint
30.3k9 gold badges82 silver badges157 bronze badges
asked May 24, 2012 at 16:54
\$\endgroup\$
1
  • \$\begingroup\$ This method seems small and clean. If you can write an integration test and it is easy to do, then do it. Otherwise, I would not over-test. You could be doing other productive things instead. If you discover a bug, then it would make sense to code a regression test against it. Otherwise, just assume that this will work until it breaks and move on. \$\endgroup\$ Commented May 24, 2012 at 19:45

3 Answers 3

3
\$\begingroup\$

As far as testing the method itself:
In most testing frameworks, you would create parameterized tests that provide the inputs and expected outputs.

NUnit example below (omitting setup code to build ObjectUnderTest):

[TestCaseSource ("GetPrepTestData")]
public void TestPrepareAndScheduleFlowJob (string inputCoords, FlowJob expectedJob)
{
 var outputJob = ObjectUnderTest.PrepareAndScheduleFlowModelJob (inputCoords);
 Assert.That (outputJob, Is.EqualTo (expectedJob));
}
private static IEnumerable<TestCaseData> GetPrepTestData ()
{
 yield return new TestCaseData ("some coord string", new FlowJob (expectedId)).SetName ("first test");
 yield break;
}

The private method calls can largely be ignored unless they have external dependencies, in which case you should add some injection and mocking with your parent object. This will allow you to ensure your private method calls inside PrepareAndScheduleFlowJob execute quickly and return expected results.

edit:
The above solution requires that you override Object.Equals. You could change the assert to a set of asserts for testing equality, but Object.Equals overrides are preferable, as it will cover FlowJob and any future/current subclasses in addition to providing equality check capability to anyone using the class. Additionally, in a framework like NUnit, overriding Object.ToString is highly recommended, as many test frameworks will call ToString when printing assert failures, and having something more than just the type name is useful when assessing why your tests may have failed.

answered May 25, 2012 at 18:53
\$\endgroup\$
1
\$\begingroup\$

What is supposed to happen when the coordinates parameter is null? And when it is an empty string? And when it does not properly represent a coordinate? You can create unit tests that test for these kind of situations, for example.

answered May 25, 2012 at 6:42
\$\endgroup\$
1
\$\begingroup\$

The tricky thing is that function has a lot of external dependencies, which makes testing that one very specific function very difficult. The test has to rely on GeometryCoordinateString, FindIntersectingWatersheds, ProcessWatershedResults, ScheduleFlowJob, and depending on your business logic, FlowJob working correctly. Changing the underlying logic in any of those objects/methods will cause the test for PrepareAndScheduleFlowModelJob to fail. Obviously, I am not familiar with your business rules, or what the methods/objects do, but here is my best stab at making the method more testable.

public interface IGeometryCoordinateString
{
 GeometryCoordinateString GetCoordinateString(string coordinates);
}
public interface IIntersectingWatersheds
{
 DataSet FindIntersectingWatersheds(GeometryCoordinateString coordinateString); 
}
public interface IProcessWatershedResults
{
 void ProcessWaterShedResults(DataSet intersectedWatershedDataSet, IList<string> xCoords, IList<string> yCoords, IList<string> ascFiles);
}
public interface IScheduleFlowJob
{
 void ScheduleFlowJob(IList<string> xCoords, IList<string> yCoords, IList<string> ascFiles, string submitJobId);
}
public Class PrepareFlowJob
{
 private readonly IGeometryCoordinateString m_CoordinateString;
 private readonly IIntersectingWatersheds m_IntersectingWatersheds;
 private readonly IProcessWatershedResults m_ProcessWatershedResults;
 private readonly IScheduleFlowJob m_ScheduleFlowJob;
 Public PrepareFlowJob(
 IGeometryCoordinateString coordinateString, 
 IIntersectingWatersheds intersectingWaterSheds,
 IProcessWatershedResults processWatershedResults,
 IScheduleFlowJob scheduleFlowJob)
 {
 m_CoordinateString = coordinateString;
 m_IntersectingWatersheds = intersectingWaterSheds;
 m_ProcessWatershedResults = processWatershedResults;
 m_ScheduleFlowJob = scheduleFlowJob;
 }
 public FlowJob PrepareAndScheduleFlowModelJob(string coordinates)
 {
 GeometryCoordinateString coordinateString = m_CoordinateString.GetCoordinateString(coordinates); 
 DataSet intersectedWatershedsDataSet = 
 m_IntersectingWatersheds.FindIntersectingWatersheds(coordinateString);
 List<string> xCoords = new List<string>();
 List<string> yCoords = new List<string>();
 List<string> ascFiles = new List<string>();
 string submitJobID = "fromServa" + new Random().Next().ToString();
 m_ProcessWatershedResults.ProcessWatershedResults(intersectedWatershedsDataSet, xCoords, yCoords, ascFiles);
 m_ProcessWatershedResults.ScheduleFlowJob(xCoords, yCoords, ascFiles, submitJobID);
 return new FlowJob(submitJobID);
 }
}

Then using a mocking framework like Moq you can some stubs.

 [TestMethod]
 public void Test_PrepareAndScheduleFlowModelJob()
 {
 var mockCoordinateString = new Mock<IGeometryCoordinateString>();
 var mockIntesection = new Mock<IIntersectingWatersheds>();
 var mockWatershedResults = new Mock<IProcessWatershedResults>();
 var mockSchedule = new Mock<IScheduleFlowJob>();
 var itemUnderTest = new PrepareFlowJob(mockCoordinateString.Object, mockIntersection.Object, mockWatershedResults.Object, mockSchedule.Object);
 var expected = new FlowJob(string.empty);
 FlowJob actual = PrepareAndScheduleFlowModelJob("something");
 Assert.AreEqual(expected, actual);
}

In your production code you could use a factory to implement the necessary interfaces for the PrepareFlowJob object. The idea is to separate out everything into what it is responsible for, this way the sample test should never change, now no matter how you change logic for something like FindIntersectingWatersheds. Because all your passing in is the interface. The only reason the test should change is when the business logic for the method PrepareAndScheduleFlowModelJob changes.

answered Jun 14, 2012 at 1:28
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.