JUnit best practices (III)

上一篇 / 下一篇  2008-02-01 21:08:32

JUnit best practices

Techniques for building resilient, relocatable, multithreaded JUnit tests

Page 3 of 5

Do not load data from hard-coded locations on a filesystem(不要从一个程序很难定位的文件系统加载测试数据)

Tests often need to load data from some location in the filesystem. Consider the following:

public void setUp () {
   FileInputStream inp ("C:\\TestData\\dataSet1.dat");
   ...
}


The code above relies on the data set being in theC:\TestDatapath. That assumption is incorrect in two situations: (上面的一段程序依赖存放于C:\TestData下的数据。但这种假设从以下两点来讲是不正确的)

  • A tester does not have room to store the test data onC:and stores it on another disk
  • The tests run on another platform, such as Unix


One solution might be(解决方法之一):

public void setUp () {
   FileInputStream inp ("dataSet1.dat");
   ...
}


However, that solution depends on the test running from the same directory as the test data. If several different test cases assume this, it is difficult to integrate them into one test suite without continually changing the current directory.

To solve the problem, access the dataset using eitherClass.getResource()orClass.getResourceAsStream(). Using them, however, means that resources load from a location relative to the class's origin.

Test data should, if possible, be stored with the source code in a configuration management (CM) system. However, if you're using the aforementioned resource mechanism, you'll need to write a scrīpt that moves all the test data from the CM system into the classpath of the system under test. A less ungainly approach is to store the test data in the source tree along with the source files. With this approach, you need a location-independent mechanism to locate the test data within the source tree. One such mechanism is a class. If a class can be mapped to a specific source directory, you could write code like this:

InputStream inp = SourceResourceLoader.getResourceAsStream (this.getClass (), "dataSet1.dat");


Now you must only determine how to map from a class to the directory that contains the relevant source file. You can identify the root of the source tree (assuming it has a single root) by a system property. The class's package name can then identify the directory where the source file lies. The resource loads from that directory. For Unix and NT, the mapping is straightforward: replace every instance of '.' withFile.separatorChar.

Keep tests in the same location as the source code

If the test source is kept in the same location as the tested classes, both test and class will compile during a build. This forces you to keep the tests and classes synchronized during development. Indeed, unit tests not considered part of the normal build quickly become dated and useless.

Name tests properly

Name the test caseTestClassUnderTest. For example, the test case for the classMessageLogshould beTestMessageLog. That makes it simple to work out what class a test case tests. Test methods' names within the test case should describe what they test:

  • testLoggingEmptyMessage()
  • testLoggingNullMessage()
  • testLoggingWarningMessage()
  • testLoggingErrorMessage()


Proper naming helps code readers understand each test's purpose.

Ensure that tests are time-independent(保证测试用例不依赖于时间)

Where possible, avoid using data that may expire; such data should be either manually or programmatically refreshed. It is often simpler to instrument the class under test, with a mechanism for changing its notion of today. The test can then operate in a time-independent manner without having to refresh the data.

Consider locale when writing tests(考虑测试用例的本地化兼容问题)

Consider a test that uses dates. One approach to creating dates would be:

Date date = DateFormat.getInstance ().parse ("dd/mm/yyyy");


Unfortunately, that code doesn't work on a machine with a different locale. Therefore, it would be far better to write:

Calendar cal = Calendar.getInstance ();
Cal.set (yyyy, mm-1, dd);
Date date = Calendar.getTime ();


The second approach is far more resilient to locale changes.

Utilize JUnit's assert/fail methods and exception handling for clean test code(利用JUnit的assert/fail方法和异常处理机制,使测试代码更简洁)

Many JUnit novices make the mistake of generating elaborate try and catch blocks to catch unexpected exceptions and flag a test failure. Here is a trivial example of this:

public void exampleTest () {
   try {
      // do some test
   } catch (SomeApplicationException e) {
      fail ("Caught SomeApplicationException exception");
   }
}


JUnit automatically catches exceptions. It considers uncaught exceptions to be errors, which means the above example has redundant code in it.

Here's a far simpler way to achieve the same result:

public void exampleTest () throws SomeApplicationException {
   // do some test
}


In this example, the redundant code has been removed, making the test easier to read and maintain (since there is less code).

Use the wide variety of assert methods to express your intention in a simpler fashion. Instead of writing:

assert (creds == 3);


Write:

assertEquals ("The number of credentials should be 3", 3, creds);


The above example is much more useful to a code reader. And if the assertion fails, it provides the tester with more information. JUnit also supports floating point comparisons:

assertEquals ("some message", result, expected, delta);


When you compare floating point numbers, this useful function saves you from repeatedly writing code to compute the difference between the result and the expected value.

UseassertSame()to test for two references that point to the same object. UseassertEquals()to test for two objects that are equal.

Document tests in javadoc

Test plans documented in a word processor tend to be error-prone and tedious to create. Also, word-processor-based documentation must be kept synchronized with the unit tests, adding another layer of complexity to the process. If possible, a better solution would be to include the test plans in the tests'javadoc, ensuring that all test plan data reside in one place.

Avoid visual inspection

Testing servlets, user interfaces, and other systems that produce complex output is often left to visual inspection. Visual inspection -- a human inspecting output data for errors -- requires patience, the ability to process large quantities of information, and great attention to detail: attributes not often found in the average human being. Below are some basic techniques that will help reduce the visual inspection component of your test cycle.   (Continued)


TAG:

 

评分:0

我来说两句

Open Toolbar