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?

\$\endgroup\$
  • \$\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\$ – Leonid May 24 '12 at 19:45
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.

\$\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.

\$\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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.