Hiển thị các bài đăng có nhãn Unit Testing. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Unit Testing. Hiển thị tất cả bài đăng

Thứ Năm, 17 tháng 1, 2013

Hamcrest Containing Matchers

The Hamcrest 1.3 Javadoc documentation for the Matchers class adds more documentation for several of that class's methods than were available in Hamcrest 1.2. For example, the four overloaded contains methods have more descriptive Javadoc documentation as shown in the two comparison screen snapshots shown next.

Although one can figure out how the "contains" matchers work just by trying them out, the Javadoc in Hamcrest 1.3 makes it easier to read how they work. Most Java developers probably think of behavior like that of String.contains(CharSequence) or Collection.contains(Object) when they think of a contains() method. In other words, most Java developers probably think of "contains" as describing if the String/Collection contains the provided characters/objects among other possible characters/objects. However, for Hamcrest matchers, "contains" has a much more specific meaning. As the Hamcrest 1.3 documentation makes much clearer, the "contains" matchers are much more sensitive to number of items and order of items being passed to these methods.

My examples shown here use JUnit and Hamcrest. It is important to emphasize here that Hamcrest's JAR file must appear on the unit tests' classpath before JUnit's JAR file or else I must use the "special" JUnit JAR file built for use with the standalone Hamcrest JAR. Using either of these approaches avoids the NoSuchMethodError and other errors (suc as org.hamcrest.Matcher.describeMismatch error) resulting from mismatched versions of classes. I have written about this JUnit/Hamcrest nuance in the blog post Moving Beyond Core Hamcrest in JUnit.

The next two screen snapshots indicate the results (as shown in NetBeans 7.3) of the unit test code snippets I show later in the blog to demonstrate Hamcrest containing matchers. The tests are supposed to have some failures (7 tests passing and 4 tests failing) to make it obvious where Hamcrest matchers may not work as one expects without reading the Javadoc. The first image shows only 5 tests passing, 2 tests failing, and 4 tests causing errors. This is because I have JUnit listed before Hamcrest on the NetBeans project's "Test Libraries" classpath. The second image shows the expected results because the Hamcrest JAR occurs before the JUnit JAR in the project's "Test Libaries" classpath.

For purposes of this demonstration, I have a simple contrived class to be tested. The source code for that Main class is shown next.

Main.java

package dustin.examples;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* Main class to be unit tested.
*
* @author Dustin
*/
public class Main
{
/** Uses Java 7's diamond operator. */
private Set<String> strings = new HashSet<>();

public Main() {}

public boolean addString(final String newString)
{
return this.strings.add(newString);
}

public Set<String> getStrings()
{
return Collections.unmodifiableSet(this.strings);
}
}

With the class to be tested shown, it is now time to look at building some JUnit-based tests with Hamcrest matchers. Specifically, the tests are to ensure that Strings added via the class's addString(String) method are in its underlying Set and accessible via the getStrings() method. The unit test methods shown next demonstrate how to use Hamcrest matchers appropriately to determine if added Strings are contained in the class's underlying Set

Using Hamcrest contains() Matcher with Single String in Set Works

/**
* This test will pass because there is only a single String and so it will
* contain that single String and order will be correct by implication.
*/
@Test
public void testAddStringAndGetStringsWithContainsForSingleStringSoWorks()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final Set<String> strings = subject.getStrings();
assertThat(strings, contains("Java"));
}

The unit test shown above passes because the Set only has one String in it and so the order and number of Strings tested with the contains matcher matches.

Using Hamcrest Contains with Same Number of Elements Works if Order Matches

/**
* The "contains" matcher expects exact ordering, which really means it should
* not be used in conjunction with {@code Set}s. Typically, either this method
* will work and the method with same name and "2" on end will not work or
* vice versa.
*/
@Test
public void testAddStringAndGetStringsWithContainsForMultipleStringsNotWorks1()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, contains("Java", "Groovy"));
}

/**
* The "contains" matcher expects exact ordering, which really means it should
* not be used in conjunction with {@code Set}s. Typically, either this method
* will work and the method with same name and "1" on end will not work or
* vice versa.
*/
@Test
public void testAddStringAndGetStringsWithContainsForMultipleStringsNotWorks2()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, contains("Groovy", "Java"));
}

The two example unit tests shown above and their resultant output of running those test as shown in the previous screen snapshot show that as long as the number of arguments to the contains() matcher are the same as the number of Strings in the collection being tested, the match may work if the elements tested are in exactly the same order as the elements in the collection. With an unordered Set, this order cannot be relied upon, so contains() is not likely to be a good matcher to use with a unit test on a Set of more than one element.

Using Hamcrest Contains with Different Number of Elements Never Works

/**
* Demonstrate that contains will NOT pass when there is a different number
* of elements asked about contains than in the collection.
*/
@Test
public void testAddStringAndGetStringsWithContainsNotWorksDifferentNumberElements1()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, contains("Java"));
}

/**
* Demonstrate that contains will NOT pass when there is a different number
* of elements asked about contains than in the collection even when in
* different order.
*/
@Test
public void testAddStringAndGetStringsWithContainsNotWorksDifferentNumberElements2()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, contains("Groovy"));
}

As the JUnit test results indicate, these two unit tests never pass because the number of elements being tested for in the Set is fewer than the number of elements in the Set. In other words, this proves that the contains() matcher does not test simply for a given element being in a collection: it tests for all specified elements being present and in the specified order. This might be too limiting in some cases, so now I'll move onto some other matches Hamcrest provides for determining if an element is contained in a particular collection.

Using Hamcrest's containsInAnyOrder() Matcher

The containsInAnyOrder matcher is not as strict as the contains() matcher: it allows tested elements to be in any order within the containing collection to pass.


/**
* Test of addString and getStrings methods of class Main using Hamcrest
* matcher containsInAnyOrder.
*/
@Test
public void testAddStringAndGetStringsWithContainsInAnyOrder()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultCSharp = subject.addString("C#");
final boolean resultGroovy = subject.addString("Groovy");
final boolean resultScala = subject.addString("Scala");
final boolean resultClojure = subject.addString("Clojure");
final Set<String> strings = subject.getStrings();
assertThat(strings, containsInAnyOrder("Java", "C#", "Groovy", "Scala", "Clojure"));
}

/**
* Use containsInAnyOrder and show that order does not matter as long as
* all entries provided are in the collection in some order.
*/
@Test
public void testAddStringAndGetStringsWithContainsInAnyOrderAgain()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, containsInAnyOrder("Java", "Groovy"));
assertThat(strings, containsInAnyOrder("Groovy", "Java"));
}

The two unit tests shown immediately above both pass despite the Strings being tested being provided to the containsInAnyOrder() matcher in a different order than what they could exist in for both collections. However, the less strict containsInAnyOrder() matcher still requires all elements of the containing collection to be specified to pass. The following unit test does not pass because this condition is not met.


/**
* This will fail because containsInAnyOrder requires all items to be matched
* even if in different order. With only one element being tried and two
* elements in the collection, it will still fail. In other words, order
* does not matter with containsInAnyOrder, but all elements in the collection
* still need to be passed to the containsInAnyOrder matcher, just not in the
* exact same order.
*/
@Test
public void testAddStringAndGetStringsWithContainsInAnyOrderDiffNumberElements()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, containsInAnyOrder("Java"));
}
Hamcrest hasItem() and hasItems() Matchers Work As Sounds

As shown in the next two unit test methods (both of which pass), the Hamcrest hasItem() (for single item) and hasItems (for multiple items) successfully tests whether a collection has the one or more than one specified items respectively without regard for order or number of specified items. This really works more like most Java developers are used to "contains" working when working with Strings and collections.


/**
* Demonstrate hasItem() will also work for determining a collection contains
* a particular item.
*/
@Test
public void testAddStringAndGetStringsWithHasItem()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, hasItem("Groovy"));
assertThat(strings, hasItem("Java"));
}

/**
* Demonstrate that hasItems works for determining that a collection has one
* or more items and that the number of items and the order of the items
* is not significant in determining pass/failure.
*/
@Test
public void testAddStringAndGetStringsWithHasItems()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat(strings, hasItems("Groovy", "Java"));
assertThat(strings, hasItems("Java", "Groovy"));
assertThat(strings, hasItems("Groovy"));
assertThat(strings, hasItems("Java"));
}
Hamcrest isIn() Matcher Tests Containment from Other Direction

The just-discussed hasItem() and hasItems() matchers are less strict than contains() and even less strict than containsInAnyOrder() and are often what one wants when one wants to simply ensure that one or multiple items are somewhere in a collection without concern about the item's order in that collection or that other possible items are in that collection. One other way to use Hamcrest to determine the same relationship, but from the opposite perspective, is to use isIn matcher. The isIn matcher determines if an item is somewhere with the collection provided to the matcher without regard for that item's order in the collection or whether or not there are other items in that containing collection.


/**
* Use isIn matcher to test individual element is in provided collection.
*/
@Test
public void testAddStringAndGetStringsWithIsIn()
{
final Main subject = new Main();
final boolean resultJava = subject.addString("Java");
final boolean resultGroovy = subject.addString("Groovy");
final Set<String> strings = subject.getStrings();
assertThat("Groovy", isIn(strings));
assertThat("Java", isIn(strings));
}
Conclusion

Hamcrest provides a rich set of matchers that can be used to determine if specified elements reside within a specified collection. Here are important points to keep in mind when deciding to apply these and determining which to use:

  • Ensure that the Hamcrest JAR is on the test classpath before the JUnit JAR.
  • Use contains when you want to ensure that the collection contains all specified items and no other items and you want the collection to contain the items in the specified order.
    • Generally avoid using contains() matcher with Sets because they are unordered by nature.
  • Use containsInAnyOrder matcher when you still want to strictly test for presence of exactly same items in collection as specified in test, but don't care about the order (applicable for Sets).
  • Use hasItem() and hasItems() matchers to ask a collection if it contains, possibly among other unlisted items and in no particular order, the specified item or items.
  • Use isIn() matcher to ask if a particular item is in the specified collection with no regard for whether other items are in that collection or what order that item is in within the containing collection.

Thứ Hai, 18 tháng 6, 2012

Moving Beyond Core Hamcrest in JUnit

In the post Improving On assertEquals with JUnit and Hamcrest I introduced use of Hamcrest with JUnit. I then looked at JUnit's Built-in Hamcrest Core Matcher Support. In this post, I look at how to apply Hamcrest's non-core matchers with JUnit. These non-core matchers are NOT included with JUnit by default, but are available by including a Hamcrest JAR in the classpath.

Although JUnit's inclusion of Hamcrest core matchers makes them easier to use if one only wants to use the core matchers, this inclusion can make use of the non-core matchers more difficult and is a well-known issue.

Because the non-core Hamcrest matchers are not included with JUnit, the Hamcrest JAR needs to be downloaded. For my examples in this post, I am using hamcrest-all-1.2.jar.

The next screen snapshot indicates the problems with combining the hamcrest-all JAR with the normal JUnit library (JUnit 4.10 as provided by NetBeans 7.2 beta in my example). As the screen snapshot indicates, when the junit-4.10.jar is included in the NetBeans libraries BEFORE the hamcrest-all-1.2.jar, the previously working code (from my previous post) breaks. Both NetBeans and the command-line compiler show this breakage in this screen snapshot.

Switching the order of the test libraries so that the Hamcrest library is listed first and the JUnit JAR listed after it, makes the compiler break on the test code go away. This is shown in the next screen snapshot.

Although switching the order of the dependent libraries so that the Hamcrest JAR is included before the JUnit JAR does prevent the build problem, this is not typically a satisfactory approach. This approach is too fragile for long-term maintainability. Fortunately, there is a better approach that JUnit directly supports to deal with this issue.

A special Hamcrest-less JUnit JAR can be downloaded. The next screen snapshot shows the one I use in this example: junit-dep-4.10.jar. The -dep in the JAR name is the clue that it's Hamcrest-free. The notation next to the JAR on the download page (screen snapshot shown next) points this out as well ("Jar without hamcrest").

With the Hamcrest-free "dep" version of the JUnit JAR, I can include it in the test libraries at any point I like with relation to the Hamcrest JAR and will still be able to build the test code. This is a much more favorable approach than relying on a specific order of test libraries. The next image shows the screen snapshot of NetBeans and the command-line build being successful even with the JUnit JAR listed first.

With the appropriate libraries in use (JUnit-dep JAR and the Hamcrest "all" JAR), all of Hamcrest's matchers can be used with JUnit-based tests. Hamcrest provides numerous matchers beyond the core matches that are now bundled with JUnit. One way to get an idea of the additional matchers available is to look at the classes in the Hamcrest JAR. The following is output from running a jar tvf command against the Hamcrest JAR and removing many of the entries to leave some of the most interesting ones. The "core" matchers tend to be based on the classes in the "core" package and the non-core matchers tend to be based on the classes in all the other packages without "core" in their name.


4029 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/AllOf.java
3592 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/AnyOf.java
1774 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/CombinableMatcher.java
1754 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/DescribedAs.java
1104 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/Every.java
2088 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/Is.java
1094 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsAnything.java
2538 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsCollectionContaining.java
1862 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsEqual.java
2882 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsInstanceOf.java
1175 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsNot.java
1230 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsNull.java
960 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/IsSame.java
675 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/StringContains.java
667 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/StringEndsWith.java
678 Thu May 21 23:21:20 MDT 2009 org/hamcrest/core/StringStartsWith.java

2557 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsArray.java
1805 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsArrayContaining.java
1883 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsArrayContainingInAnyOrder.java
1765 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsArrayContainingInOrder.java
1388 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsArrayWithSize.java
1296 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsCollectionWithSize.java
812 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsEmptyCollection.java
866 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsEmptyIterable.java
1086 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsIn.java
3426 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsIterableContainingInAnyOrder.java
3479 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsIterableContainingInOrder.java
993 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsIterableWithSize.java
1899 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsMapContaining.java
1493 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsMapContainingKey.java
1421 Thu May 21 23:21:20 MDT 2009 org/hamcrest/collection/IsMapContainingValue.java

1380 Thu May 21 23:21:20 MDT 2009 org/hamcrest/number/IsCloseTo.java
2878 Thu May 21 23:21:20 MDT 2009 org/hamcrest/number/OrderingComparison.java

1082 Thu May 21 23:21:20 MDT 2009 org/hamcrest/object/HasToString.java
918 Thu May 21 23:21:20 MDT 2009 org/hamcrest/object/IsCompatibleType.java
2080 Thu May 21 23:21:20 MDT 2009 org/hamcrest/object/IsEventFrom.java

1164 Thu May 21 23:21:20 MDT 2009 org/hamcrest/text/IsEmptyString.java
1389 Thu May 21 23:21:20 MDT 2009 org/hamcrest/text/IsEqualIgnoringCase.java
2058 Thu May 21 23:21:20 MDT 2009 org/hamcrest/text/IsEqualIgnoringWhiteSpace.java
1300 Thu May 21 23:21:20 MDT 2009 org/hamcrest/text/StringContainsInOrder.java

4296 Thu May 21 23:21:20 MDT 2009 org/hamcrest/xml/HasXPath.java

JUnit's providing of a JAR without Hamcrest automatically built in (the "dep" JAR) allows developers to more carefully building up their classpaths if Hamcrest matchers above and beyond the "core" matchers are desired for use with JUnit.

Thứ Tư, 30 tháng 5, 2012

NetBeans 7.2 Introduces TestNG

One of the advantages of code generation is the ability to see how a specific language feature or framework is used. As I discussed in the post NetBeans 7.2 beta: Faster and More Helpful, NetBeans 7.2 beta provides TestNG integration. I did not elaborate further in that post other than a single reference to that feature because I wanted to devote this post to the subject. I use this post to demonstrate how NetBeans 7.2 can be used to help a developer new to TestNG start using this alternative (to JUnit) test framework.

NetBeans 7.2's New File wizard makes it easier to create an empty TestNG test case. This is demonstrated in the following screen snapshots that are kicked off by using New File | Unit Tests (note that "New File" is available under the "File" drop-down menu or by right-clicking in the Projects window).

Running the TestNG test case creation as shown above leads to the following generated test code.

TestNGDemo.java (Generated by NetBeans 7.2)

package dustin.examples;

import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.Assert;

/**
*
* @author Dustin
*/
public class TestNGDemo
{
public TestNGDemo()
{
}

@BeforeClass
public void setUpClass()
{
}

@AfterClass
public void tearDownClass()
{
}

@BeforeMethod
public void setUp()
{
}

@AfterMethod
public void tearDown()
{
}
// TODO add test methods here.
// The methods must be annotated with annotation @Test. For example:
//
// @Test
// public void hello() {}
}

The test generated by NetBeans 7.2 includes comments indicate how test methods are added and annotated (similar to modern versions of JUnit). The generated code also shows some annotations for overall test case set up and tear down and for per-test set up and tear down (annotations are similar to JUnit's). NetBeans identifies import statements that are not yet used at this point (import org.testng.annotations.Test; and import org.testng.Assert;), but are likely to be used and so have been included in the generated code.

I can add a test method easily to this generated test case. The following code snippet is a test method using TestNG.

testIntegerArithmeticMultiplyIntegers()

@Test
public void testIntegerArithmeticMultiplyIntegers()
{
final IntegerArithmetic instance = new IntegerArithmetic();
final int[] integers = {4, 5, 6};
final int expectedProduct = 2 * 3 * 4 * 5 * 6;
final int product = instance.multiplyIntegers(2, 3, integers);
assertEquals(product, expectedProduct);
}

This, of course, looks very similar to the JUnit equivalent I used against the same IntegerArithmetic class that I used for testing illustrations in the posts Improving On assertEquals with JUnit and Hamcrest and JUnit's Built-in Hamcrest Core Matcher Support. The following screen snapshot shows the output in NetBeans 7.2 beta from right-clicking on the test case class and selecting "Run File" (Shift+F6).

The text output of the TestNG run provided in the NetBeans 7.2 beta is reproduced next.


[TestNG] Running:
Command line suite

[VerboseTestNG] RUNNING: Suite: "Command line test" containing "1" Tests (config: null)
[VerboseTestNG] INVOKING CONFIGURATION: "Command line test" - @BeforeClass dustin.examples.TestNGDemo.setUpClass()
[VerboseTestNG] PASSED CONFIGURATION: "Command line test" - @BeforeClass dustin.examples.TestNGDemo.setUpClass() finished in 33 ms
[VerboseTestNG] INVOKING CONFIGURATION: "Command line test" - @BeforeMethod dustin.examples.TestNGDemo.setUp()
[VerboseTestNG] PASSED CONFIGURATION: "Command line test" - @BeforeMethod dustin.examples.TestNGDemo.setUp() finished in 2 ms
[VerboseTestNG] INVOKING: "Command line test" - dustin.examples.TestNGDemo.testIntegerArithmeticMultiplyIntegers()
[VerboseTestNG] PASSED: "Command line test" - dustin.examples.TestNGDemo.testIntegerArithmeticMultiplyIntegers() finished in 12 ms
[VerboseTestNG] INVOKING CONFIGURATION: "Command line test" - @AfterMethod dustin.examples.TestNGDemo.tearDown()
[VerboseTestNG] PASSED CONFIGURATION: "Command line test" - @AfterMethod dustin.examples.TestNGDemo.tearDown() finished in 1 ms
[VerboseTestNG] INVOKING CONFIGURATION: "Command line test" - @AfterClass dustin.examples.TestNGDemo.tearDownClass()
[VerboseTestNG] PASSED CONFIGURATION: "Command line test" - @AfterClass dustin.examples.TestNGDemo.tearDownClass() finished in 1 ms
[VerboseTestNG]
[VerboseTestNG] ===============================================
[VerboseTestNG] Command line test
[VerboseTestNG] Tests run: 1, Failures: 0, Skips: 0
[VerboseTestNG] ===============================================

===============================================
Command line suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

Deleting directory C:\Users\Dustin\AppData\Local\Temp\dustin.examples.TestNGDemo
test:
BUILD SUCCESSFUL (total time: 2 seconds)

The above example shows how easy it is to start using TestNG, especially if one is moving to TestNG from JUnit and is using NetBeans 7.2 beta. Of course, there is much more to TestNG than this, but learning a new framework is typically most difficult at the very beginning and NetBeans 7.2 gets one off to a fast start.

Thứ Ba, 29 tháng 5, 2012

JUnit's Built-in Hamcrest Core Matcher Support

In the post Improving On assertEquals with JUnit and Hamcrest, I briefly discussed Hamcrest "core" matchers being "baked in" with modern versions of JUnit. In that post, I focused particularly on use of JUnit's assertThat(T, Matcher) static method coupled with the Hamcrest core is() matcher that is automatically included in later versions of JUnit. In this post, I look at additional Hamcrest "core" matchers that are bundled with recent versions of JUnit.

Two of the advantages of JUnit including Hamcrest "core" matchers out-of-the-box is that there is no need to specifically download Hamcrest and there is no need to include it explicitly on the unit test classpaths. Before looking at more of the handy Hamcrest "core" matchers, it is important to point out here that I am intentionally and repeatedly referring to "core" Hamcrest matchers because recent versions of JUnit only provide "core" (and not all) Hamcrest matchers automatically. Any Hamcrest matchers outside of the core matchers would still need to be downloaded separately and specified explicitly on the unit test classpath. One way to get an idea of what is Hamcrest "core" (and thus what matchers are available by default in recent versions of JUnit) is to look at that package's Javadoc-based API documentation:

From this JUnit-provided documentation for the org.hamcrest.core package, we see that the following matchers (with their descriptions) are available:

ClassJavadoc Class DescriptionCovered Here?
AllOf<T>Calculates the logical conjunction of two matchers.Yes
AnyOf<T>Calculates the logical disjunction of two matchers.Yes
DescribedAs<T>Provides a custom description to another matcher.Yes
Is<T>Decorates another Matcher, retaining the behavior but allowing tests to be slightly more expressive.Again
IsAnything<T>A matcher that always returns true.No
IsEqual<T>Is the value equal to another value, as tested by the Object.equals(java.lang.Object) invokedMethod?Yes
IsInstanceOfTests whether the value is an instance of a class.Yes
IsNot<T>Calculates the logical negation of a matcher.Yes
IsNull<T>Is the value null?Yes
IsSame<T>Is the value the same object as another value?Yes

In my previous post demonstrating the Hamcrest is() matcher used in conjunction with JUnit's assertThat(), I used an IntegerArithmetic implementation as test fodder. I'll use that again here for demonstrating some of the other Hamcrest core matchers. For convenience, that class is reproduced below.

IntegerArithmetic.java

package dustin.examples;

/**
* Simple class supporting integer arithmetic.
*
* @author Dustin
*/
public class IntegerArithmetic
{
/**
* Provide the product of the provided integers.
*
* @param firstInteger First integer to be multiplied.
* @param secondInteger Second integer to be multiplied.
* @param integers Integers to be multiplied together for a product.
* @return Product of the provided integers.
* @throws ArithmeticException Thrown in my product is too small or too large
* to be properly represented by a Java integer.
*/
public int multiplyIntegers(
final int firstInteger, final int secondInteger, final int ... integers)
{
int returnInt = firstInteger * secondInteger;
for (final int integer : integers)
{
returnInt *= integer;
}
return returnInt;
}
}

In the Improving On assertEquals with JUnit and Hamcrest post, I relied largely on is() to compare expected results to actual results for the integer multiplication being tested. Another option would have been to use the equalTo matcher as shown in the next code listing.

Using Hamcrest equalTo()

/**
* Test of multiplyIntegers method, of class IntegerArithmetic, using core
* Hamcrest matcher equalTo.
*/
@Test
public void testWithJUnitHamcrestEqualTo()
{
final int[] integers = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final int expectedResult = 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 *13 * 14 * 15;
final int result = this.instance.multiplyIntegers(2, 3, integers);
assertThat(result, equalTo(expectedResult));
}

Although not necessary, some developers like to use is and equalTo together because it feels more fluent to them. This is the very reason for is's existence: to make use of other matchers more fluent. I often use is() by itself (implying equalTo()) as discussed in Improving On assertEquals with JUnit and Hamcrest. The next example demonstrates using is() matcher in conjunction with the equalTo matcher.

Using Hamcrest equalTo() with is()

/**
* Test of multiplyIntegers method, of class IntegerArithmetic, using core
* Hamcrest matcher equalTo with "is" Matcher..
*/
@Test
public void testWithJUnitHamcrestEqualToAndIsMatchers()
{
final int[] integers = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final int expectedResult = 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 *13 * 14 * 15;
final int result = this.instance.multiplyIntegers(2, 3, integers);
assertThat(result, is(equalTo(expectedResult)));
}

The equalTo Hamcrest matcher performs a comparison similar to calling Object.equals(Object). Indeed, its comparison functionality relies on use of the underlying object's equals(Object) implementation. This means that the last two examples will pass because the numbers being compared are logically equivalent. When one wants to ensure an even greater identity equality (actually the same objects and not just the same logical content), one can use the Hamcrest sameInstance matcher as shown in the next code listing. The not matcher is also applied because the assertion will be true and the test will pass only with the "not" in place because the expected and actual results happen to NOT be the same instances!

Using Hamcrest sameInstance() with not()

/**
* Test of multiplyIntegers method, of class IntegerArithmetic, using core
* Hamcrest matchers not and sameInstance.
*/
@Test
public void testWithJUnitHamcrestNotSameInstance()
{
final int[] integers = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final int expectedResult = 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 *13 * 14 * 15;
final int result = this.instance.multiplyIntegers(2, 3, integers);
assertThat(result, not(sameInstance(expectedResult)));
}

It is sometimes desirable to control the text that is output from an assertion of a failed unit test. JUnit includes the core Hamcrest matcher asDescribed() to support this. A code example of this is shown in the next listing and the output of that failed test (and corresponding assertion) is shown in the screen snapshot of the NetBeans IDE that follows the code listing.

Using Hamcrest asDescribed() with sameInstance()

/**
* Test of multiplyIntegers method, of class IntegerArithmetic, using core
* Hamcrest matchers sameInstance and asDescribed. This one will assert a
* failure so that the asDescribed can be demonstrated (don't do this with
* your unit tests as home)!
*/
@Test
public void testWithJUnitHamcrestSameInstanceDescribedAs()
{
final int[] integers = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final int expectedResult = 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 *13 * 14 * 15;
final int result = this.instance.multiplyIntegers(2, 3, integers);
assertThat(result,
describedAs(
"Not same object (different identity reference)",
sameInstance(expectedResult)));
}

Use of describedAs() allowed the reporting of a more meaningful message when the associated unit test assertion failed.

I am going to now use another contrived class to help illustrate additional core Hamcrest matchers available with recent versions of JUnit. This that "needs testing" is shown next.

SetFactory.java

package dustin.examples;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* A Factory that provides an implementation of a Set interface based on
* supplied SetType that indicates desired type of Set implementation.
*
* @author Dustin
*/
public class SetFactory<T extends Object>
{
public enum SetType
{
ENUM(EnumSet.class),
HASH(HashSet.class),
SORTED(SortedSet.class), // SortedSet is an interface, not implementation
TREE(TreeSet.class),
RANDOM(Set.class); // Set is an interface, not a concrete collection

private Class setTypeImpl = null;

SetType(final Class newSetType)
{
this.setTypeImpl = newSetType;
}

public Class getSetImplType()
{
return this.setTypeImpl;
}
}

private SetFactory() {}

public static SetFactory newInstance()
{
return new SetFactory();
}

/**
* Creates a Set using implementation corresponding to the provided Set Type
* that has a generic parameterized type of that specified.
*
* @param setType Type of Set implementation to be used.
* @param parameterizedType Generic parameterized type for the new set.
* @return Newly constructed Set of provided implementation type and using
* the specified generic parameterized type; null if either of the provided
* parameters is null.
* @throws ClassCastException Thrown if the provided SetType is SetType.ENUM,
* but the provided parameterizedType is not an Enum.
*/
public Set<T> createSet(
final SetType setType, final Class<T> parameterizedType)
{
if (setType == null || parameterizedType == null)
{
return null;
}

Set<T> newSet = null;
try
{
switch (setType)
{
case ENUM:
if (parameterizedType.isEnum())
{
newSet = EnumSet.noneOf((Class<Enum>)parameterizedType);
}
else
{
throw new ClassCastException(
"Provided SetType of ENUM being supplied with "
+ "parameterized type that is not an enum ["
+ parameterizedType.getName() + "].");
}
break;
case RANDOM:
newSet = LinkedHashSet.class.newInstance();
break;
case SORTED:
newSet = TreeSet.class.newInstance();
break;
default:
newSet = (Set<T>) setType.getSetImplType().getConstructor().newInstance();
break;
}
}
catch ( InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException ex)
{
Logger.getLogger(SetFactory.class.getName()).log(Level.SEVERE, null, ex);
}
return newSet;
}
}

The contrived class whose code was just shown provides opportunities to use additional Hamcrest "core" matchers. As described above, it's possible to use all of these matches with the is matcher to improve fluency of the statement. Two useful "core" matchers are nullValue() and notNullValue(), both of which are demonstrated in the next JUnit-based code listing (and is is used in conjunction in one case).

Using Hamcrest nullValue() and notNullValue()

/**
* Test of createSet method, of class SetFactory, with null SetType passed.
*/
@Test
public void testCreateSetNullSetType()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(null, String.class);
assertThat(strings, nullValue());
}

/**
* Test of createSet method, of class SetFactory, with null parameterized type
* passed.
*/
@Test
public void testCreateSetNullParameterizedType()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(SetType.TREE, null);
assertThat(strings, is(nullValue()));
}

@Test
public void testCreateTreeSetOfStringsNotNullIfValidParams()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(SetType.TREE, String.class);
assertThat(strings, notNullValue());
}

The Hamcrest matcher instanceOf is also useful and is demonstrated in the next code listing (one example using instanceOf by itself and one example using it in conjunction with is).

Using Hamcrest instanceOf()

@Test
public void testCreateTreeSetOfStringsIsTreeSet()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(SetType.TREE, String.class);
assertThat(strings, is(instanceOf(TreeSet.class)));
}

@Test
public void testCreateEnumSet()
{
final SetFactory factory = SetFactory.newInstance();
final Set<RoundingMode> roundingModes = factory.createSet(SetType.ENUM, RoundingMode.class);
roundingModes.add(RoundingMode.UP);
assertThat(roundingModes, instanceOf(EnumSet.class));
}

Many of the Hamcrest core matchers covered so far increase fluency and readability, but I like the next two for even more reasons. The Hamcrest hasItem() matcher checks for the existence of the prescribed item in the collection and the even more useful Hamcrest hasItems() matcher checks for the existence of multiple prescribed items in the collection. It is easier to see this in code and the following code demonstrates these in action.

Using Hamcrest hasItem() and hasItems()

@Test
public void testCreateTreeSetOfStringsHasOneOfAddedStrings()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(SetType.TREE, String.class);
strings.add("Tucson");
strings.add("Arizona");
assertThat(strings, hasItem("Tucson"));
}

@Test
public void testCreateTreeSetOfStringsHasAllOfAddedStrings()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(SetType.TREE, String.class);
strings.add("Tucson");
strings.add("Arizona");
assertThat(strings, hasItems("Tucson", "Arizona"));
}

It is sometimes desirable to test the result of a certain tested method to ensure that it meets a wide variety of expectations. This is where the Hamcrest allOf matcher comes in handy. This matcher ensures that all conditions (expressed themselves as matchers) are true. This is illustrated in the following code listing, which tests with a single assert that a generated Set is not null, has two specific Strings in it, and is an instance of TreeSet.

Using Hamcrest allOf()

@Test
public void testCreateSetAllKindsOfGoodness()
{
final SetFactory factory = SetFactory.newInstance();
final Set<String> strings = factory.createSet(SetType.TREE, String.class);
strings.add("Tucson");
strings.add("Arizona");
assertThat(
strings,
allOf(
notNullValue(), hasItems("Tucson", "Arizona"), instanceOf(TreeSet.class)));
}

To demonstrate the Hamcrest core "anyOf" matcher provided out-of-the-box with newer versions of JUnit, I am going to use yet another ridiculously contrived Java class that is in need of a unit test.

Today.java

package dustin.examples;

import java.util.Calendar;
import java.util.Locale;

/**
* Provide what day of the week today is.
*
* @author Dustin
*/
public class Today
{
/**
* Provide the day of the week of today's date.
*
* @return Integer representing today's day of the week, corresponding to
* static fields defined in Calendar class.
*/
public int getTodayDayOfWeek()
{
return Calendar.getInstance(Locale.US).get(Calendar.DAY_OF_WEEK);
}
}

Now I need to test that the sole method in the class above returns a valid integer representing a day of the week correctly. I'd like my test(s) to ensure that a valid integer representing a day Sunday through Saturday is returned, but the method being tested is such that it may not be the same day of the week returned on any given test run. The code listing below indicates how this can be tested with the JUnit-included Hamcrest "anyOf" matcher.

Using Hamcrest anyOf()

/**
* Test of getTodayDayOfWeek method, of class Today.
*/
@Test
public void testGetTodayDayOfWeek()
{
final Today instance = new Today();
final int todayDayOfWeek = instance.getTodayDayOfWeek();
assertThat(todayDayOfWeek,
describedAs(
"Day of week not in range.",
anyOf(is(Calendar.SUNDAY),
is(Calendar.MONDAY),
is(Calendar.TUESDAY),
is(Calendar.WEDNESDAY),
is(Calendar.THURSDAY),
is(Calendar.FRIDAY),
is(Calendar.SATURDAY))));
}

While Hamcrest's allOf requires all conditions to match for the assertion to be avoided, the existence of any one condition is sufficient to ensure that anyOf doesn't lead to an assertion of a failure.

My favorite way of determining which core Hamcrest matchers are available with JUnit is to use import completion in my Java IDE. When I statically import the org.hamcrest.CoreMatchers.* package contents, all of the available matchers are displayed. I can look in the IDE to see what the * represents to see what matchers are available to me.

It is nice to have Hamcrest "core" matchers included with JUnit and this post has attempted to demonstrate the majority of these. Hamcrest offers many useful matchers outside of the "core" that are useful as well. More details on these are available in the Hamcrest Tutorial.

Thứ Ba, 1 tháng 5, 2012

Recent Interesting Software Development Posts - 1 May 2012

This post references and very briefly summarizes several recent posts on subjects of interest to software developers such as NetBeans 7.1.2, progress on Java 8's Lambda Expressions, Guava Release 12, TestNG, Scala, and the expense of ineffective meetings.

NetBeans 7.1.2

The release of NetBeans 7.1.2 includes JDK 7 Update 4 (first version of Java SE 7 for Mac OS X).

State of the Lambda: Libraries Edition

After attending JavaOne 2011, I believe that lambda expressions are going to dramatically change how we write Java. Therefore, it is always interesting to read about progress related to Lambda expressions. In the April 2012 post State of the Lambda: Libraries Edition, Brian Goetz "describes the design approach taken in the rough prototype that has been implemented in the Lambda Project repository." He points out that this description is "intended as a working straw-man proposal" and that "the final version may look different." Another Goetz April 2012 post (Translation of Lambda Expressions) "outlines the strategy for translating lambda expressions and method references from Java source code into bytecode."

Guava Release 12

Guava Release 12 was released this week. According to the Guava Release 12 Release Notes, this is the first release of Guava that requires Java SE 6: "Guava 12.0 is the first release to require JDK6. Users requiring JDK5 compatibility may continue to use Guava 11.0.2"

TestNG Rather than JUnit

Tomek Kaczanowski has posted the slides he prepared "to persuade my colleagues to migrate from JUnit to TestNG" in his post Why TestNG and Not JUnit?

Scala or Java? Exploring Myths and Facts

The post Scala or Java? Exploring myths and facts addresses some of the alleged pros and cons of Scala articulated online. The author addresses questions about productivity, complexity, concurrency support, tooling, extensibility, interoperability, performance, and backwards compatibility.

The Expense of Ineffective Meetings

Jeffrey Scott Klubeck's post The Expense of Ineffective Meetings is not necessarily new, but it is new to me. This short post is well worth the couple of minutes to read and articulates well what many of us have felt after having a particularly useless meeting foisted upon us. I love the quote, "Patrick Lencioni, author of Death by Meeting, says that bad meetings not only exact a toll on the attendees as they suffer through them, but also cause real human anguish in the form of anger, lethargy, cynicism, and even in the form of lower self-esteem."

10 Hard Truths Developer Must Accept

In the post 10 hard truths developers must learn to accept, Peter Wayner offers ten things that are reminders that "programming offers an array of bitter pills to swallow."

SSH Tunneling Explained

Buddhika Chamith's post SSH Tunneling Explained is a detailed and illustrated overview of SSH tunneling.

Conclusion

This post has referenced a small set of other blog posts, articles, and announcements that I have found particularly interesting in recent weeks.

Thứ Hai, 23 tháng 4, 2012

Improving On assertEquals with JUnit and Hamcrest

In my blog post Are Static Imports Becoming Increasingly Accepted in Java?, I discussed the increasing use of static imports in Java to make code more fluent in certain contexts. Unit testing in Java has been particularly affected by the static import and in this blog post I provide one quick example of using static imports to make more fluent unit tests that use JUnit and Hamcrest.

The next code listing is a simple IntegerArithmetic class that has one method that needs to be unit tested.

IntegerArithmetic.java

package dustin.examples;

/**
* Simple class supporting integer arithmetic.
*
* @author Dustin
*/
public class IntegerArithmetic
{
/**
* Provide the product of the provided integers.
*
* @param integers Integers to be multiplied together for a product.
* @return Product of the provided integers.
* @throws ArithmeticException Thrown in my product is too small or too large
* to be properly represented by a Java integer.
*/
public int multiplyIntegers(final int ... integers)
{
int returnInt = 1;
for (final int integer : integers)
{
returnInt *= integer;
}
return returnInt;
}
}

A common approach for testing one aspect of the above method is shown next.


/**
* Test of multipleIntegers method, of class IntegerArithmetic, using standard
* JUnit assertEquals.
*/
@Test
public void testMultipleIntegersWithDefaultJUnitAssertEquals()
{
final int[] integers = {2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final int expectedResult = 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 *13 * 14 * 15;
final int result = this.instance.multiplyIntegers(integers);
assertEquals(expectedResult, result);
}

In the fairly typical unit test example shown above, JUnit's assertEquals is called in a fluent fashion because of the static import of org.junit.Assert.* (not shown). However, recent versions of JUnit (JUnit 4.4+) have begun including Hamcrest core matchers and this allows for an even more fluent test as depicted in the next code snippet.


/**
* Test of multipleIntegers method, of class IntegerArithmetic, using core
* Hamcrest matchers included with JUnit 4.x.
*/
@Test
public void testMultipleIntegersWithJUnitHamcrestIs()
{
final int[] integers = {2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
final int expectedResult = 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 *13 * 14 * 15;
final int result = this.instance.multiplyIntegers(integers);
assertThat(result, is(expectedResult));
}

In this example, JUnit's assertThat (also available as part of the static import of org.junit.Assert.* since JUnit 4.4) is used in conjunction with the included Hamcrest core matcher is(). It's certainly a matter of taste, but I prefer this second approach as more readable to me. Asserting that something (the result) is something else (the expected) seems more readable and more fluent than the older approach. It can sometimes be tricky to remember whether to list the expected or actual result first when using assertEquals and the combination of assertThat and is() makes for a little less work when I write and read tests. Even a little less work, especially when multiplied by numerous tests, is welcome.

Thứ Ba, 27 tháng 3, 2012

Unit Testing is a Means to an End

Most professional software developers these days understand the importance and value of writing and using unit tests. A nice summary of some of the oft-touted and oft-realized benefits of unit testing can be found in the StackOverflow.com thread Is Unit Testing worth the effort? [my only very minor criticism is the mixing of more specialized Test-Driven Development (TDD) with the more general unit test concept]. As with most good things, however, even unit testing enthusiasm can go too far. The benefits of unit testing can lead to overly enthusiastic unit testing developers forgetting that unit tests are not the end themselves, but rather are a means to the real end.

The "end" that most software developers are striving for is delivery of software solutions that make their users' lives easier and more productive. Unit tests can be extremely valuable in obtaining this end and certainly add to software quality, but the overly zealous unit tester must beware of allowing the unit tests themselves to displace this end goal. It's all too easy to allow oneself to get so bound up in writing exhaustive and "perfect" unit tests that one puts the true end goal at risk. In the remainder of this post, I look at ways in which developers can allow unit testing to move from helping achieve the desired end to unintentionally displacing the real end and putting it at risk.

Overreaching Unit Testing

Steven Sanderson has written "the benefit of unit testing is correlated with the non-obviousness of the code under test." I largely agree with this sentiment as a general guideline. I see little value in unit testing trivial "get" and "set" methods. Some methods are more readily evaluated via code review than via unit test.

The concept of code coverage can be a useful one as long as it's not taken too far. Code coverage appears to provide high return for the effort for a while, but there comes a point of diminishing returns when gaining additional code coverage comes at much greater cost and may not be worth that cost. It's also important to recognize that even the often highly expensive 100% code coverage typically means only all lines of code were executed and does not check all possible paths through the code.

All Code's Unit Testability is Not Equal

The post Selective Unit Testing – Costs and Benefits clearly articulates well the differences in difficulty (cost) and advantages (benefits) of unit testing of different types of code. In cases where the advantages/benefits of a unit test are high and the cost/effort is low, the value of unit testing is obvious. On the opposite extreme, there are types of code that receive little benefit from unit testing.

There Are Other Effective Types of Testing

I have seen multiple disparate groups of developers build and unit test their respective code bases flawlessly and then suffer through the pain of trying to integrate their thoroughly unit tested code with the other groups' thoroughly unit tested code. This happens when the involved groups have written comprehensive unit tests based on their own assumptions and without regard for functional and integration tests. A certain minimum number of unit tests are always necessary, but there may be cases where less important unit tests should not displace better or more comprehensive integration tests. When looking at code coverage, I prefer to look at code coverage from all types of tests rather than simply code coverage provided via unit tests.

In his insightful column Breaking Away From The Unit Test Group Think, Cedric Beust (created TestNG and wrote Next Generation Java Testing) wrote:

I also question all the attention that unit tests are receiving at the expense of the other kinds of tests (functional, integration, etc.). The truth is that functional tests serve your users, while unit tests serve you — the developer. A unit test is just a convenience that allows you to track down bugs faster. At the end of the day, the reason you write tests is to make sure that your users will be able to be productive with your application, not to make sure that you can debug faster.

I also like how Igal Tabachnik puts it in the post Where unit testing fails: "The road to successful unit testing begins with understanding what unit testing is, but most importantly – what it isn’t."

Unit Testing is Not the Sole Means to the End

Not only are there other valuable types of testing, there are also other valuable software development tactics and methodologies for producing high-quality software the meets or exceeds customer expectations. Unit testing is a valuable part of this overall approach, but should not diminish or overshadow other approaches such as design and code inspections, appropriate collaboration, code analyzers, and so forth.

Sacrificing Other -ilities for Testability

As valuable as I believe unit testing is, the thing I probably find most frustrating about unit testing in Java is having to compromise a desired language design feature in production code for the sake of unit testing. To be sure, there are many cases where unit testing encourages better practices in the production code. For example, unit testing encourages smaller methods, a feature that is generally considered a strength in code maintainability and readability. However, there are times when I want to make my class final or a method final or private and this can be orthogonal to unit testability.

It galls me to have to give up long-term design considerations and benefits to make testing possible. I want the software I deliver to be of high quality and enjoy readability and maintainability. It is difficult to have to give some of this up in some cases in the name of testability. Fortunately, unit testing often forces better design. But in the cases when I must choose between elegant design and production code with hacked test code to test that elegant design or hacked design and production code to accommodate elegant test code, remembering that unit testing is not the end itself provides the appropriate frame of reference for making that call.

The good news is that modern unit testing frameworks are steadily reducing the types of desirable traits in production code that must be compromised to support unit testing. Unit test frameworks such as PowerMock use bytecode manipulation to overcome most of these issues.

Group Think

Whether we call it the Lemming Effect or peer pressure or group think, there's no question that we in the software development community tend to chase fads and shiny things. Unit testing has been part of software development for many years, but the increasing emphasis on it over the last decade has benefited software development. Unit testing has a long history in software development and has long since proven itself to not be a fad. However, I sometimes feel that some developers (particularly those who don't realize how long unit testing has been around) think it's something new that the rest of us don't understand. In their zeal to promote it as a "new thing," they actually make the act of unit testing more important than the goal of unit testing.

It is often the case in software development that evangelists and defenders of a particular product, language, framework, or technology cannot allow for any hint of a weakness or disadvantage to be discussed in relation to their favorite item. Any suggestion that their preferred product might not be the best thing since sliced bread is met with derision and scoffing. This ridiculous and unrealistic stance reduces constructive discourse about the advantages, disadvantages, opportunities, risks, and costs associated with a given approach. In the case of unit testing, this might be compounded by the fact that this practice was met with some skepticism by a large number of developers for numerous years and strong evangelism to change that has been largely successful but doesn't know now how to contain itself.

Conclusion

This post has assumed constrained resources and schedules. For those who have the luxury of not facing constrained resources and short timetables, there may be sufficient time to write and maintain all of the tests for all types of code no matter how trivial and still deliver a final product in time. However, many of us are in circumstances where costs and benefits of different aspects of software development must be evaluated. In such cases, it is important to keep focus on the true long-term goal and use effective unit testing to achieve this goal while not allowing unit testing itself to become the end goal. As with most things in software development, the level and type of unit testing should based on experienced judgment of the value gained versus the cost expended. Unit testing is a means to an end; it is not the end itself.

Referenced and Other Related Resources