Place test classes in the same package as production code

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 105 - 108)

Problem

You either do not wish to, or cannot, place test classes in a separate package from the production code under test.

Background

Many Test-Driven Development practitioners prefer to place their test classes in a separate package from their production code, as this practice tends to improve the production system’s design over time. We like this practice, and this is our default mode of operation. This is easy enough to do when writing tests for code not yet written, but less so when writing tests for existing code.

We are not often fortunate enough to work on greenfield projects where we are building new components or systems from the ground up. For the most part, we are called in to either add features to or fix systems already in production, and in spite of JUnit’s increasing popularity, it remains less common to join a project that uses JUnit than it is to join a project not using JUnit.2 If you have inherited code with inadequate tests and your job is to add those tests, then you may not be able to place your test classes where you like.

The majority of systems in production are not designed to be easy to test. We don’t mean that the programmers intentionally made testing difficult—although in some cases that wouldn’t surprise us—but that most programmers are not aware of the need to design testable systems. There are a number of reasons why this is so, and contrary to what some might believe, programmer incompetence is low on the list.3 A programmer can easily execute “tests” by hand using breakpoints, the debugger, and her eyes. If you are reading this book, then you obviously want to go beyond this primitive form of testing and are beginning to write tests for your inherited code using JUnit. Pretty soon, if you haven’t done so already, you will attempt to write an assertion and realize that you have no way to talk to the object that knows whether your assertion passes or fails. Something has to give.

2 When the day comes that this statement is no longer true, we hope someone lets us know, in case we miss it. We may well have lost some of our mental faculties by then, but we hold out hope.

3 Although we prefer not to offend anyone, we tactfully point out that lack of focus on testing is the num- ber one reason why programmers build difficult-to-test systems. That focus generally needs to come

“from above.”

75 Place test classes in the same

package as production code

Recipe

When you write a test class, simply place it in the same package as the production code you plan to test. Your test will have access to all but the private parts of the production class’s interface, allowing you to write tests for behavior that would otherwise remain hidden from you.

Discussion

We prefer not to place test classes in the same package as the code they test, because the resulting tests tend to be brittle. That is, small changes in the produc- tion code affect an unexpectedly large number of tests. In particular, purely struc- tural changes to the production code can lead to changes in the tests, even though the simplest tests to write are the ones that depend only on the observable behavior of the code under test! Purely structural changes include the following:

■ Renaming a method that client classes use indirectly by calling other meth- ods (the method being renamed has protected or package-level visibility)

■ Changing the method signature (parameters, return type) of such an indi- rectly called method

■ Extracting a number of indirectly called methods into a new “helper” class In many cases, we choose to perform these refactorings to improve the code.

Renaming the method may better reveal its intent or, at a minimum, replace a nonsensical or abbreviated name with a name programmers can more easily understand. Changing the method signature may remove unnecessary parameters or replace difficult-to-understand parameters—such as boolean flags4—with more easily understood parameters (such as symbolic constants). Extracting a number of methods into a new class generally simplifies any design by reducing the num- ber of responsibilities per class, a technique we use to simplify naming certain tests, as you will see in recipe 3.4. Tests that impede refactoring increase costs in a number of ways:

■ You waste time performing the current refactoring.

■ Your annoyance at the current refactoring discourages you from performing future refactorings.

■ “Ugly” code leads to “ugly” tests.

4 In Java, C, or Ruby, where parameters are matched by their position in the parameter list, it is impossible to understand from the call site what getTreeCellRendererComponent(myJTree, aValue, true, false, true, 0, false) means!

76 CHAPTER 3

Organizing and building JUnit tests

Although the first problem is evident, the second is subtler and potentially more damaging. The goal of refactoring is to improve the system’s design incrementally over time to reduce future costs. Refactoring does this by adding features, fixing defects, and training others to navigate the code. Refactoring the design incre- mentally on an ongoing basis tends to go against human nature. This slow, methodical refactoring requires a certain level of discipline that not all of us have (or, at the very least, not all of us can maintain over time), so you need to avoid any situation that encourages you to abandon your discipline. Brittle, overly depen- dent tests are an excellent way to discourage ongoing refactoring, so any practice that naturally leads to brittle tests is to be used with caution. This is why we strongly recommend placing tests in a separate package from the production code (see recipe 3.3 for details). If you decide to place your test code in the same package as your production code, then consider whether to move the test source code into a separate directory structure, as this provides some of the benefits of a separate test package. We cover this technique in recipe 3.2.

The last problem is perhaps the worst, as it is a positive feedback loop of nega- tive feelings. Poorly factored code is difficult to test. The tests we do write for such code are usually poorly factored themselves. Both the production code and the test code are brittle, difficult to read, and costly to maintain. If you just keep adding production code and test code without refactoring any of it, then both become progressively worse, taking more and more time and effort to develop. Eventually, usually sooner than you realize, the most cost-effective strategy is to stop trying, rip it all out, and start over. It is better to deal with the problem now, while it is fresh in your mind, than later when you are under more pressure and have forgotten what to do. Any small technique that can help you avoid this situation is worth trying.

Although we recommend against it, we recognize the reasons why you may need to place tests in the same package as the production code. One particular situation involves wanting to test a protected method before making it public. In this case, we recommend that you use this as a starting point from which you slowly refactor the production code to allow all your tests to compile using only the production code’s collective public interface. If you undertake this task, get some coffee, grab your copy of Refactoring, and, above all, take your time. It’s not easy, but it’s usually worth it, even if you only refactor a little at a time.

Related

■ 3.2—Create a separate source tree for test code

■ 3.3—Separate test packages from production code packages

■ 3.4—Factor out a test fixture

77 Create a separate

source tree for test code

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 105 - 108)

Tải bản đầy đủ (PDF)

(753 trang)