Showing posts with label Unit Testing. Show all posts
Showing posts with label Unit Testing. Show all posts

Thursday, September 20, 2007

Easy Testing in F#

I've found a personally acceptable answer to the debate about whether or not to test private members. This is a technique that you can use in F#.

Here are some of the issues that you might face in testing F# code:
Perhaps you see my quandary. I want my test code to be in a separate assembly from my production code, so the code-under-test needs to be exposed. The more CLS-friendly I make my assembly, the further away from the code I get. And finally, I want to test all of those little functions that I'll inevitably end up writing except I don't want to expose all of those little functions in my API.

My answer? It's simple, I just compile my SUT without including my interface (.fsi) file! When you do this, every value declared in your source (.fs) file is exposed to the world for testing. Then, when I compile for release, I include the interface file and all of that stuff is hidden.

Thus far, I've found no issues with NUnit working in this manner and it's given me the Best of Both Worlds as far as test coverage and CLS-friendly API's go!

Friday, February 2, 2007

Mocking ArcObjects

I'm an avid test-driven developer but I'm also an ArcObjects programmer. Do you think this is an oxymoron? Read on!

Yes, ArcObjects are very expensive to create. Yes, they are slow and consume a lot of infrastructure. But none of this matters. ESRI did test-driven developers a humongous favor when they took an interface-oriented approach to ArcObjects. By doing this, they enabled us to easily unit test our code.

Suppose you are about to write a simple C# method like the following. How do we set up a test for it?
public static void EditFeature(IFeature feature, string fieldName, object value)
{
feature.set_Value(feature.Fields.FindField(fieldName), value);
}

Simple enough, right? Wrong! To even get a IFeature, we have to create am IFeatureWorkspace and to get one of those we'll have to create an IWorkspaceFactory. If your setup is anything like mine, that'll take tens of seconds and even minutes. Local geodatabases would ease that pain but either way our test will not be a unit test.

Do not despair! There's a way to test our method without relying on anything external to that one line of code. There's a wonderful testing tool called mock objects and with them we can test that our ArcObjects code does exactly what we want it to do.

You can go here if you want to learn more about mock objects. Today I'm just here to introduce you to a simple example of using mock objects to test ArcObjects code.

My favorite mocking framework for .NET is Rhino Mocks. When we want to create mock objects using Rhino Mocks, our first step is usually to create a MockRepository:
MockRepository mocks = new MockRepository();

The next thing that we need to is create our mock objects:
IFeature mockFeature = mocks.CreateMock();
IFields mockFields = mocks.CreateMock();

I'll explain why we need to create a mock IFields object in a second. Once you have your mock objects, you can start setting up your expectations. In Rhink Mocks, this works a bit like the record button on your...well, what has a record button these days? Take another look at the method that we're testing. We set the Value of an IFeature by giving it the index of the field that we want to change. This expectation is expressed like so:
Expect.Call(mockFields.FindField("field")).Return(1);

This line tells our MockRepository to expect our code under test to call the FindField() method on the mockFields object. It also demands that the value passed in to the method has to bed "field". Finally, we return the value 1 when that method is called.

Our next expectation is that the value returned by FindField() is used to set the value of the field. This expectation states that we expect the field at index 1 to be set to the value, "value":
mockFeature.set_Value(1, "value");

The last thing that we need to do is to tell our mockFeature to return our mockFields whenever its Fields property is called:
SetupResult.For(mockFeature.Fields).Return(mockFields);

That's it! Let's tell our Mock Repository that we're done setting up our expectations:
mocks.ReplayAll();

We really just have one line of test code:
EditFeature(mockFeature, "field", "value");

If we did all of this correctly, our test will pass and we have just written an ArcObject method test-first.