Uncategorized

Effective Unit Testing


Introduction to SQL Server Reporting Services – Next Tuesday, August 25

Here’s your chance to sign up for the next virtual class, "Introduction to SQL Server Reporting Services". This "Made Simple" course, taught by Sherri McDonald, a Business intelligence expert from Pragmatic Works, is a great investment for your personal development. If you’re ready to up your game, come on in and join us next Tuesday.

Effective Unit Testing
At this point we have talked a lot about what unit tests are, different unit test lifecycles, reasonable unit test coverage, and the benefits of unit tests.

As many have responded, the value of unit tests is dependent on the kinds of unit tests that are written. For example, measuring the effectiveness of unit tests simply by how much of the code is covered by unit tests does not determine if you have good tests. You can write tests that exercise all of your code, but not really test for scenarios that are real life.

Unit tests should reflect the core requirements the software is supposed to fulfill. Often, the requirements are too complicated by multiple parameters. The requirements have to be de-composed into smaller sets of requirements. You can do that de-composition using unit testing rather than writing a detailed set of requirements. Sometimes the software itself is broken down in order to fulfill the requirements, and/or optimize testing.

For example, consider you have a parameterized driven model for rounding numbers based on user preferences. The user can decide to not round at all, Round Up, Round Down or Round to the Nearest Value. This requirement quickly breaks down into a Rounding Interface, four different classes implementing the Rounding Interface that round accordingly, and some sort of factory or method to implement the appropriate class based on user preferences.

Testing is now quite simple. First you write a rounding test specific to each rounding class (4). Second you write a test to validate that the appropriate class is instantiated based on the users preferences (1) exercising the factory or implementation method. You now have 5 discrete unit tests fulfilling a single business rule stating that rounding must be driven based on the users rounding preference.

With these tests in place, do you need to test rounding everywhere it is implemented? The short answer is, “It doesn’t hurt.” However, in reality, any additional tests are not needed. You have tested that the rounding classes round correctly. You have tested that you instantiate the correct rounding class. Any test proving that real data being exercised through your rounding classes is therefore redundant.

The example is somewhat simplistic. The process is complicated by simply by adding a requirement allowing the user to specify the rounding precision as well. Allowing the user to specify rounding based on 1,2 or 3 decimal places multiplies the number of test conditions required. You now have No Rounding, Round Up * 3, Round Down * 3 and Round Nearest * 3.

Negative testing is another practice that increases your test counts. For example, consider if you were rounding up, you would need a test for when the last value was <5 or >=5. This increases all rounding tests by 2. We started with 4 tests. Now we have 1 + (Up * 3 * 2) + (Down * 3 * 2) + (Neareset * 3 * 2) for a total of 23 rounding tests, and four instantiation tests (one for each rounding method). Imagine how painful this would have been if you had tried to integrate this testing with real scenarios. You now have all your edge cases covered, and great confidence that your sofware will function as required.

As you can see, each additional factor increases the number of possible test permutations. What would happen to the number of test cases if you wanted to allow the user to specify at what point to round up or round down? Instead of rounding based on the last digit compared to 5, the user could specify rounding comparison at the digit 6. Or perhaps they could choose to use banker rounding instead of mathamatical rounding (banker rounding is the default method in Dot Net, and Numerical Rounding is the default method in SQL Server).

This is the key reason for breaking your software down into smaller components so that you may fully exercise the code with the fewest number of cases, in a simply way to find, understand, and maintain.

This small example demonstrates why so many people consider unit testing to be "too much work" or of little value. If you simply measured the value of unit tests by code coverage, I could cover all the code, even using a good object oriented design, with 5 tests. However, fully exercising the code, we are over 20 tests and could easily expand them to many more with only one more parameter. If I don’t have any tests where this code is utilized, I still have a high degree of confidence that the software performs correctly because the edge cases are fully covered.

Ed Writes:
I just glanced over your unit testing article, and wanted to share this additional insight. Rather than have a developer write his or her own unit tests, have each developer write unit tests for some other developer’s code. It should be possible to write the test in parallel with the code development, if the design clearly specifies the intent of the unit under development. There is more motivation to write effective unit tests when the objective is to break someone else’s code; it becomes something of a game. And with tools like x-unit suites, these tests become part of a regression library that will catch many things that might otherwise pass through to QA.

Your insights and questions are always valued here at SSWUG. Please send either to btaylor@sswug.org.

Cheers,

Ben

$$SWYNK$$

Featured Article(s)
Myths and Realities of an Iterative Development Lifecycle (Part 3)
Laura Lee Rose, business coach and corporate exit strategist, questions and debunks some widely held myths pertaining to iterative development and iterative testing. She explains how iterative development principles can address these common misunderstandings and transition to a pragmatic software development methodology.

Featured Script
Identity reset (reseed)
SQL Server script ro reset (reseed) a table’s Identity column to start at 1. Replace
with the actual table name (… (read more)