views:

85

answers:

6

I'm a controls developer and a relative newbie to unit testing. Almost daily, I fight the attitude that you cannot test controls because of the UI interaction. I'm producing a demonstration control to show that it's possible to dramatically reduce manual testing if the control is designed to be testable. Currently I've got 50% logic coverage, but I think I could bump that up to 75% or higher if I could find a way to test some of the more complicated parts.

For example, I have a class with properties that describe the control's state and a method that generates a WPF PathGeometry object made of several segments. The implementation looks something like this:

internal PathGeometry CreateOuterGeometry()
{
    double arcRadius = OuterCoordinates.Radius;
    double sweepAngle = OuterCoordinates.SweepAngle;
    ArcSegment outerArc = new ArcSegment(...);

    LineSegment arcEndToCenter = new LineSegment(...);

    PathFigure fig = new PathFigure();
    // configure figure and add segments...

    PathGeometry outerGeometry = new PathGeometry();
    outerGeometry.Figures.Add(fig);
    return outerGeometry;
}

I've got a few other methods like this that account for a few hundred blocks of uncovered code, an extra 25% coverage. I originally planned to test these methods, but rejected the notion. I'm still a unit testing newbie, and the only way I could think of to test the code would be several methods like this:

void CreateOuterGeometry_AngleIsSmall_ArcSegmentIsCorrect()
{
    ClassUnderTest classUnderTest = new ClassUnderTest();
    // configure the class under test...
    ArcSegment expectedArc = // generate expected Arc...

    PathGeometry geometry = classUnderTest.CreateOuterGeometry()
    ArcSegment arc = geometry.Figures.Segments[0];

    Assert.AreEqual(expectedArc, arc)
}

The test itself looks fine; I'd write one for each expected segment. But I had some problems:

  • Do I need tests to verify "Is the first segment an ArcSegment?" In theory the test tests this, but shouldn't each test only test one thing? This sounds like two things.
  • The control has at least six cases for calculation and four edge cases; this means for each method I need at least ten tests.
  • During development I changed how the various geometries were generated several times. This would cause me to have to rewrite all of the tests.

The first problem gave me pause because it seemed like it might inflate the number of tests. I thought I might have to test things like "Were there x segments?" and "Is segment n the right type?", but now that I've thought more I see that there's no branching logic in the method so I only need to do those tests once. The second problem made me more confident that there would be much effort associated with the test. It seems unavoidable. The third problem compounds the first two. Every time I changed the way the geometry was calculated, I'd have to edit an estimated 40 tests to make them respect the new logic. This would also include adding or removing tests if segments were added or removed.

Because of these three problems, I opted to write an application and manual test plan that puts the control in all of the interesting states and asks the user to verify it looks a particular way. Was this wrong? Am I overestimating the effort involved with writing the unit tests? Is there an alternative way to test this that might be easier? (I'm currently studying mocks and stubs; it seems like it'd require some refactoring of the design and end up being approximately as much effort.)

+2  A: 

Use dependency injection and mocks.

Create interfaces for ArcSegmentFactory, LineSegmentFactory, etc., and pass a mock factory to your class. This way, you'll isolate the logic that is specific to this object (this should make testing easier), and won't be depending on the logic of your other objects.

About what to test: you should test what's important. You probably have a timeline in which you want to have things done, and you probably won't be able to test every single thing. Prioritize stuff you need to test, and test in order of priority (considering how much time it will take to test). Also, when you've already made some tests, it gets much easier to create new tests for other stuff, and I don't really see a problem in creating multiple tests for the same class...

About the changes, that's what tests are for: allowing you to change and don't really fear your change will bring chaos to the world.

Samuel Carrijo
A: 

The textbook way to do this would be to move all the business logic to libraries or controllers which are called by a 1 line method in the GUI. That way you can unit test the controller or library without dealing with the GUI.

tster
That's what I'm already doing. This question is about testing a specific part of the extracted logic.
OwenP
I see, the question was very unclear in that regard.As for how to do testing on complicated things. Random generation is one way. They other way is to study the problem and write good test cases with specific edge-cases. Testing is hard, that's why there are huge QA groups in the industry.
tster
A: 

You might try writing a control generation tool that generates random control graphs, and test those. This might yield some data points that you might not have thought of.

Larry Watanabe
A: 

In our project, we use JUnit to perform tests which are not, strictly speaking, unit tests. We find, for example, that it's helpful to hook up a blank database and compare an automatic schema generated by Hibernate (an Object-Relational Mapping tool) to the actual schema for our test database; this helps us catch a lot of issues with wrong database mappings. But in general... you should only be testing one method, on one class, in a given test method. That doesn't mean you can't do multiple assertions against it to examine various properties of the object.

RMorrisey
A: 

My approach is to convert the graph into a string (one segment per line) and compare this string to an expected result.

If you change something in your code, tests will start to fail but all you need to do is to check that the failures are in the right places. Your IDE should offer a side-by-side diff for this.

When you're confident that the new output is correct, just copy it over the old expected result. This will make sure that a mistake won't go unnoticed (at least not for long), the tests will still be simple and they are quick to fix.

Next, if you have common path parts, then you can put them into individual strings and build the expected result of a test from those parts. This allows you to avoid repeating yourself (and if the common part changes, you just have to update a single place for all tests).

Aaron Digulla
A: 

If I understand your example correctly, you were trying to find a way to test whether a whole bunch of draw operations produce a given result.

Instead of human eyes, you could have produced a set of expected images (a snapshot of verified "good" images), and created unit tests which use the draw operations to create the same set of images and compare the result with an image comparison. This would allow you to automate the testing of the graphic operations, which is what I understand your problem to be.

Galghamon
This is pretty much what some I talk to do already. It has problems with resolution, and occasionally small details escape image analysis. We'll still do manual verification, but I think it would be a big help to test that I'm passing the right numbers to the code that draws things. If that part is messed up, it's out of my control.
OwenP