8.7. Testing

 <  Day Day Up  >  

The automated acceptance tests are in the com.samscdrental.tests package:

 CheckinCheckoutTests.java     TestOnlyOperations.java 

The CheckInCheckoutTests class contains tests derived from the use cases and misuse cases. The tests run in the JUnit framework. [*] They are executed using collection instances (e.g., CDDiscDataAccess , CDReleaseDataAccess , etc.) containing a small amount of data. The data is initialized using the file import operation, which also serves in migrating data to the new system.

[*] Tests for the individual classes are not shown in the appendix. They duplicate much of the logic in the overall tests.

How many automated tests should you create? Let us differentiate between testing (determining if a system is functionally correct) and debugging (finding the source of failures). If the external interfaces to a system have a set of tests that check all functionality and the tests are successful, the system is functionally correct. If the tests are unsuccessful , the system does not work and the developer has to determine why.

It is possible to debug a system by tracing through the program driven by the external tests. For a complex system, the program tracing can involve numerous method calls and many classes. If there are no internal tests on a system's classes and modules, step-by-step debugging can be required to determine the cause of the failure.

The more tests performed on a system's internal classes and modules, the more confidence the developer can have that those items are not the source of failure. This permits skipping over the tested items during debugging. Having a full complement of tests on the internal items does not absolutely eliminate the need for step-by-step debugging. Failures can still occur due to the interaction between classes and modules.

8.7.1. Test Versus Production

The tests in CheckinCheckoutTests do not depend on the implementation of RentalOperations . However, to run the tests, it is necessary to perform operations that are not executed during normal operations. For example, the tests need to reinitialize the collections. Certainly, the ability for a regular user to remove all items from a collection is not something that should be permitted, even with multiple confirmations on the part of the user:

"You are about to destroy your data. Do you wish to continue? Yes/No."

"Are you sure? Yes/No."

"Last chance to stop this foolish act. Yes/No."

"OK, we warned you."

Such a drastic action should be part of an entirely separate interface to the system. If a language permits conditional compilation, this separate interface should not be compiled in production code. Tim and I created the TestOnlyOperations interface for those operations that should exist only during testing. The collectionsClear( ) method is in that interface.

The TestOnlyOperations interface also contains the setStartTimeForRentalBackSomeDays method for setting the start_time of a Rental back a number of days. Without such a method, we could not easily test for late rentals. This method should not be part of the RentalOperations class, unless there is a justifiable use for it. We realized that for testing purposes, we needed to see if a CDDisc was rented or not. We added that method ( isCDDiscRented( ) ) to the RentalOperation class, since it seemed that it would be useful for other use cases in the big picture.

Individual classes might also have a separate test interface. [*] A test implementation of the class would support the test interface. A production implementation would not. For example, the collections classes have a removeAll_TestingOnly( ) method that is identified by name that suggests it should be used only during testing. An Extreme Splitter would place this in a test interface.

[*] Eric M. Burke, a reviewer, notes, "If code is hard to test and you find yourself adding extra methods just to make it testable, this is often a 'smell' indicating something is wrong with the design. Well-designed code tends to be easier to test. Back when I first learned JUnit a few years ago, I would write a lot of package-scope methods just so that my tests could call them. Over time, I've found less and less need to do this."

TEST OR PRODUCTION: THAT IS THE QUESTION

Place all test-only methods in a test interface .


8.7.2. Testing Flexibility

Flexibility can aid in testing. [ ] The ability to choose different implementations of interfaces allows you to substitute test implementations for production implementations . For example, this system is tested using collections implemented with Java collection classes. The tests can run without the overhead of a database process. The production collections implementation can use any data persistence mechanism that supports the methods in the interface. Implementing this kind of flexibility requires, and so helps ensure, clean separation of the data persistence layer.

[ ] Flexibility is one manner of making something testable. See the testing reference mentioned in Chapter 4 for other testability ideas. Scott Ambler notes that testing flexibility is a core practice in Agile Modeling (http://www.ambysoft.com/agileModeling.html). Eric M. Burke notes, "Classes that are hard to test might be poorly designed."

The StoreDataAccess class is where this flexibility has been concentrated. The methods in other classes do not refer to the individual collections directly. Instead, the methods refer to an appropriate member of StoreDataAccess . The constructor for StoreDataAccess can create a collection of the appropriate type (test or production) for each of its members .

BUILD FLEXIBILITY FOR TESTING

Plan for flexibility in your design to allow for ease of testing .


 <  Day Day Up  >  


Prefactoring
Prefactoring: Extreme Abstraction, Extreme Separation, Extreme Readability
ISBN: 0596008740
EAN: 2147483647
Year: 2005
Pages: 175
Authors: Ken Pugh

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