4.6 Testing Protected Behavior


A topic of much discussion within the unit testing community is how to test protected or private methods. Since access to such methods is restricted, writing unit tests for them is not straightforward.

Some developers deal with this quandary by simply ignoring protected or private methods and testing only the public interfaces. It's argued that most of an object's behavior is reflected in its public methods. The behavior of the protected methods can be inferred by the exposed behavior.

There are some drawbacks to this approach. If there are private methods that contain complex functionality, they will not be tested directly. There is a tendency to make everything public so that it is testable. Some behaviors that should be private might be exposed.

It is possible to access and test protected and private methods, depending on the specifics of how a language defines and enforces object access permissions. In C++, making the test class a friend of the production class allows it to access protected interfaces:

 class Library { #ifdef TEST    friend class LibraryTest; #endif } 

This introduces a reference to the test code into the production code, which is not good. Preprocessor directives such as #ifdef TEST can omit such references when the production code is built.

In Java, a simple technique that allows test classes to access protected and private methods is to declare the methods as package scope and place the test classes in the same package as the production classes. The next section, "Test Code Organization," shows how to arrange Java code this way.

For Java developers who are not satisfied with the direct approach, the Java Reflection API is a tricky way to overcome access protection. The JUnit extension "JUnit-addons" includes a class named PrivateAccessor that uses this approach to access protected or private attributes and methods.

The truly hardcore can follow the examples given here to write their own code that subverts access protection. In Example 4-12, the values of all of Book 's fields are read, regardless of protection. This approach is an ugly hack. Don't read this code just after a meal.

Example 4-12. Example showing use of Reflection API to get private field values
 BookTest.java import java.lang.reflect.*;    public void  testGetFields( )  {       Book book = new Book( "test", "test" );       Field fields[] = book.getClass( ).  getDeclaredFields( )  ;       for ( int i = 0; i < fields.length; i++ ) {          fields[i].  setAccessible( true )  ;          try {             String value = (String)  fields[i].get( book )  ;             assertEquals( "test", value );          } catch (Exception e) {             fail( e.getMessage( ) );          }       }    } 

A Book with title and author "test" is created. The Reflection API method getDeclaredFields() returns an array of all of the Book 's fields, and the call to setAccessible( ) allows access to a field. The Reflection API method get( ) is used to obtain each field's value. The test asserts that the value of the field is test .

Similarly, in Example 4-13, all of Book 's get methods are called, ignoring access protection (although the get methods actually are public).

Example 4-13. Example using Reflection API to invoke methods
 BookTest.java    public void  testInvokeMethods( )  {       Book book = new Book( "test", "test" );       Method[] methods = book.getClass( ).  getDeclaredMethods( )  ;       for ( int i = 0; i < methods.length; i++ ) {          if ( methods[i].getName( ).startsWith("get") ) {             methods[i].  setAccessible( true )  ;             try {                String value = (String)  methods[i].invoke( book, null )  ;                assertEquals( "test", value );             } catch (Exception e) {                fail( e.getMessage( ) );             }          }       }    } 

Paralleling the previous example, the Reflection API method getDeclaredMethods() returns all of the Book 's methods, and the call to setAccessible( ) subverts the method's access protection. The test checks the method name and calls only those that have names starting with get to avoid calling Book 's constructor. The Reflection API method invoke() is used to call the methods. Both get methods should return the value test , so this condition is asserted.

Hacks aside, the recommended approach is to design objects so that their important behaviors are public and test those behaviors. Structure the code so that the tests have access to the protected behaviors as well, so that they can be accessed if necessary.

Unit Test Frameworks
Unit Test Frameworks
ISBN: 0596006896
EAN: 2147483647
Year: 2006
Pages: 146
Authors: Paul Hamill

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net