This is an extract from the Unit Testing Succinctly eBook, by Marc Clifton, kindly provided by Syncfusion.
Unit testing is also valuable for other purposes.
As Examples of Usage
One of the side benefits of unit testing is that it creates a large code base exemplifying how to use the code. For example, the code we saw earlier:
[Test] public void FilenameParsingTest() { Dictionary<string, string> options = CommandLineParser.Parse("-f foobar"); Assert.That(options.Count == 1, "Count expected to be 1"); Assert.That(options.ContainsKey("-f"), "Expected option '-f'"); Assert.That(options["-f"] == "foobar"); }
documents an expected valid use case for the command line parser. Also consider writing unit tests—not only for your own code, but also to provide examples for third-party libraries. (See subsequent examples and the interesting things revealed about the Rectangle struct.)
Black Box Testing
Black box testing assumes that you do not know anything about the internals of the class or service and are verifying its behavior strictly from the publicly exposed interfaces. This is frequently necessary when you don’t have the code available. For example, when working with a record management company, we were required to use a web service provided by a government agency to update records. By writing unit tests for the web service, we were able to prove that the documentation provided to us did not result in the expected behavior of the web service.
You might use this technique as well when working with code provided by different departments. For example, the database group might have its own white box unit testing; however, you should also verify that from a black-box perspective, the triggers and constraints have been programmed correctly by inspecting the result of transactions from the functionality that is exposed to you.
Test Your Assumptions
Unit testing can be a simple way to put together some tests regarding our assumptions about an API. Let’s take the System.Drawing.Rectangle
structure and test some seemingly reasonable assumptions about the implementation.
Test Constructor Assumptions
There are two Rectangle
constructors: one having Point
and Size
parameters, the other having x, y, width, and height parameters. The documentation makes no indication of whether the size (the width or height) must be positive, so let’s write a test to verify that we can construct a rectangle with a negative width or height:
[TestMethod] public void RectangleNegativeSizeConstructorTest() { Rectangle r = new Rectangle(0, 0, -4, -6); }
All we are doing here in this test is verifying that no exceptions are thrown when we construct the rectangle, and indeed, this is the case:
Test Assumptions Regarding Property Values
Now let’s test our assumptions about certain properties. The properties Top
, Left
, Bottom
, and Right
are described as (see
http://msdn.microsoft.com/en-us/library/system.drawing.rectangle.aspx):
Top: Gets the y-coordinate of the top edge of this Rectangle structure.
Left: Gets the x-coordinate of the left edge of this Rectangle structure.
Bottom: Gets the y-coordinate that is the sum of the Y and Height property values of this Rectangle structure.
Right: Gets the x-coordinate that is the sum of X and Width property values of this Rectangle structure.
So, with the preceding rectangle, with a negative width and height, and therefore having coordinates [(-4, -6), (0, 0)], we would make the following assumptions:
[TestMethod] public void TestLeft() { Rectangle r = new Rectangle(0, 0, -4, -6); Assert.IsTrue(r.Left == -4, "Expected Left == -4 but was " + r.Left); } [TestMethod] public void TestTop() { Rectangle r = new Rectangle(0, 0, -4, -6); Assert.IsTrue(r.Top == 0, "Expected Top == 0 but was " + r.Top); } [TestMethod] public void TestRight() { Rectangle r = new Rectangle(0, 0, -4, -6); Assert.IsTrue(r.Right == 0, "Expected Right == 0 but was " + r.Right); } [TestMethod] public void TestBottom() { Rectangle r = new Rectangle(0, 0, -4, -6); Assert.IsTrue(r.Bottom == -6, "Expected Bottom == -6 but was " + r.Bottom); }
However, this is not the case:
In fact, the determination of top and bottom appears totally arbitrary as well, as I have run tests on exactly the same rectangle dimensions and observed different results in the Top
and Bottom
property values.
Test Assumptions about Method Results
The MSDN documentation states that the Rectangle.Intersect
method:
- Returns a third Rectangle structure that represents the intersection of two other Rectangle structures.
- If there is no intersection, an empty Rectangle is returned.
Therefore, we can construct a simple test:
[TestMethod] public void TestIntersection() { Rectangle r1 = new Rectangle(0, 0, 10, 10); Rectangle r2 = new Rectangle(10, 10, 5, 5); Assert.IsFalse(r1.IntersectsWith(r2), "Expected R1 and R2 not to intersect."); Assert.IsTrue(Rectangle.Intersect(r1, r2) == Rectangle.Empty, "Expected an empty intersection rectangle."); }
with the result:
This informs us that our expectation, based on the documentation, is incorrect.
In Conclusion
Unit testing is an important tool in the testing process. While integration and usability testing are often more customer-centric (reporting, milestones, verifying high-level requirements), unit testing is the first line of defense for a programmer, his or her team, and the team managers. If used judiciously (remember, you are not aiming to create thousands of pretty green lights), it can be a cost-effective way of verifying the computational correctness of the code and for re-creating bugs and verifying that they have been fixed.
However, good unit testing practices require a disciplined approach, a commitment to the time and effort required to implement and maintain the tests, and, from a coder’s perspective, it also requires good programming practices and often enforces architectural decisions. The latter may fly in the face of “just get it done” constraints (which may be quite legitimate), and may potentially impact performance. On the upside, the programming practices and architectures that unit testing forces one to use are often of great benefit to the entire application development process, thus reducing costs and improving maintainability, not because the code is unit tested, but because the code is written better so that it can be unit tested.
Comments