Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 95 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
95
Dung lượng
812,83 KB
Nội dung
413 public void testStatus_initial() { // in-line setup Airport departureAirport = new Airport("Calgary", "YYC"); Airport destinationAirport = new Airport("Toronto", "YYZ"); Flight flight = new Flight(flightNumber, departureAirport, destinationAirport); // exercise SUT and verify outcome assertEquals(FlightState.PROPOSED, flight.getStatus()); // teardown // garbage-collected } public void testStatus_cancelled() { // in-line setup Airport departureAirport = new Airport("Calgary", "YYC"); Airport destinationAirport = new Airport("Toronto", "YYZ"); Flight flight = new Flight( flightNumber, departureAirport, destinationAirport); flight.cancel(); // still part of setup // Exercise SUT and verify outcome assertEquals(FlightState.CANCELLED, flight.getStatus()); // teardown // garbage-collected } These tests contain a fair amount of Test Code Duplication. Refactoring Notes We can refactor the fi xture setup logic by using an Extract Method refactoring to remove any frequently repeated code sequences into utility methods with Intent-Revealing Names. We leave the calls to the methods in the test, however, so that the reader can see what is being done. The method calls that remain within the test will convey the “big picture” to the test reader. The utility meth- od bodies contain the irrelevant mechanics of carrying out the intent. If we need to share the Delegated Setups with another Testcase Class, we can use either a Pull Up Method [Fowler] refactoring to move them to a Testcase Superclass or a Move Method [Fowler] refactoring to move them to a Test Helper class. Example: Delegated Setup In this version of the test, we use a method that hides the fact that we need two airports instead of creating the two airports needed by the fl ight within each Test Method. We could produce this version of the tests either through refactoring or by writing the test in this intent-revealing style right off the bat. Delegated Setup Delegated Setup Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 414 public void testGetStatus_initial() { // setup Flight flight = createAnonymousFlight(); // exercise SUT and verify outcome assertEquals(FlightState.PROPOSED, flight.getStatus()); // teardown // garbage-collected } public void testGetStatus_cancelled2() { // setup Flight flight = createAnonymousCancelledFlight(); // exercise SUT and verify outcome assertEquals(FlightState.CANCELLED, flight.getStatus()); // teardown // garbage-collected } The simplicity of these tests was made possible by the following Creation Methods, which hide the “necessary but irrelevant” steps from the test reader: private int uniqueFlightNumber = 2000; public Flight createAnonymousFlight(){ Airport departureAirport = new Airport("Calgary", "YYC"); Airport destinationAirport = new Airport("Toronto", "YYZ"); Flight flight = new Flight( new BigDecimal(uniqueFlightNumber++), departureAirport, destinationAirport); return flight; } public Flight createAnonymousCancelledFlight(){ Flight flight = createAnonymousFlight(); flight.cancel(); return flight; } Delegated Setup Chapter 20 Fixture Setup Patterns Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 415 Creation Method How do we construct the Fresh Fixture? We set up the test fi xture by calling methods that hide the mechanics of building ready-to-use objects behind Intent-Revealing Names. Fixture setup usually involves the creation of a number of objects. In many cases, the details of those objects (i.e., the attribute values) are unimportant but must be specifi ed to satisfy each object’s constructor method. Including all of this unnecessary complexity within the fi xture setup part of the test can lead to Obscure Tests (page 186) and certainly doesn’t help us achieve Tests as Docu- mentation (see page 23)! How can a properly initialized object be created without having to clutter the test with In-line Setup (page 408)? The answer, of course, is to encapsulate this complexity. Delegated Setup (page 411) moves the mechanics of the fi xture setup into other methods but leaves overall control and coordination within the test itself. But what to delegate to? A Creation Method is one way we can encapsulate the mechanics of object creation so that irrelevant details do not distract the reader. Fixture Setup Exercise Verify Teardown SUT Creation Method Fixture Setup Exercise Verify Teardown SUT Creation Method Creation Method Creation Method Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 416 How It Works As we write tests, we don’t bother asking whether a desired utility function exists; we just use it! (It helps to pretend that we have a loyal helper sitting next to us who will quickly fi ll in the bodies of any functions we call that do not exist as yet.) We write our tests in terms of these magic functions with Intent-Revealing Names [SBPP], passing as parameters only those things that will be verifi ed in the assertions or that should affect the outcome of the test. Once we’ve written the test in this very intent-revealing style, we must implement all of the magic functions that we’ve been calling. The functions that create objects are our Creation Methods; they encapsulate the complexity of object creation. The simple ones call the appropriate constructor, passing it suitable default values for anything needed but not supplied as a parameter. If any of the constructor argu- ments are other objects, the Creation Method will fi rst create those depended-on objects before calling the constructor. The Creation Method may be placed in all the same places where we put Test Utility Methods (page 599). As usual, the decision is based on the expected scope of reuse and the Creation Method’s dependencies on the API of the SUT. A related pattern is Object Mother (see Test Helper on page 643), which is a combination of Creation Method, Test Helper, and optionally Automated Tear- down (page 503). When to Use It We should use a Creation Method whenever constructing a Fresh Fixture (page 311) requires signifi cant complexity and we value Tests as Documentation. Another key indicator for using Creation Method is that we are building the system in a highly incremental way and we expect the API of the system (and especially the object constructors) to change frequently. Encapsulating knowl- edge of how to create a fi xture object is a special case of SUT API Encapsulation (see Test Utility Method), and it helps us avoid both Fragile Tests (page 239) and Obscure Tests. The main drawback of a Creation Method is that it creates another API for test automaters to learn. This isn’t much of a problem for the initial test devel- opers because they are typically involved in building this API but it can create “one more thing” for new additions to the team to learn. Even so, this API should be pretty easy to understand because it is just a set of Factory Methods [GOF] organized in some way. If we are using a Prebuilt Fixture (page 429), we should use Finder Methods (see Test Utility Method) to locate the prebuilt objects. At the same time, we Creation Method Chapter 20 Fixture Setup Patterns Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 417 may still use Creation Methods to lay mutable objects that we plan to modify on top of an Immutable Shared Fixture (see Shared Fixture on page 317). Several variations of Creation Method are worth exploring. Variation: Parameterized Creation Method While it is possible (and often very desirable) for Creation Methods to take no parameters whatsoever, many tests will require some customization of the cre- ated object. A Parameterized Creation Method allows the test to pass in some attributes to be used in the creation of the object. In such a case, we should pass only those attributes that are expected to affect (or those we want to demon- strate do not affect) the test’s outcome; otherwise, we could be headed down the slippery slope to Obscure Tests. Variation: Anonymous Creation Method An Anonymous Creation Method automatically creates a Distinct Generated Value (see Generated Value on page 723) as the unique identifi er for the object it is creating even though the arguments it receives may not be unique. This behav- ior is invaluable for avoiding Unrepeatable Tests (see Erratic Test on page 228) because it ensures that every object we create is unique, even across multiple test runs. If the test cares about some attributes of the object to be created, it can pass them as parameters of the Creation Method; this behavior turns the Anony- mous Creation Method into a Parameterized Anonymous Creation Method. Variation: Parameterized Anonymous Creation Method A Parameterized Anonymous Creation Method is a combination of several other variations of Creation Method in that we pass in some attributes to be used in the creation of the object but let the Creation Method create the unique identi- fi er for it. A Creation Method could also take zero parameters if the test doesn’t care about any of the attributes. Variation: Named State Reaching Method Some SUTs are essentially stateless, meaning we can call any method at any time. By contrast, when the SUT is state-rich and the validity or behavior of methods is affected by the state of the SUT, it is important to test each method from each possible starting state. We could chain a bunch of such tests together in a single Test Method (page 348), but that approach would create an Eager Test (see Assertion Roulette on page 224). It is better to use a series of Single- Condition Tests (see page 45) for this purpose. Unfortunately, that leaves us Creation Method Creation Method Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 418 with the problem of how to set up the starting state in each test without a lot of Test Code Duplication (page 213). One obvious solution is to put all tests that depend on the same starting state into the same Testcase Class (page 373) and to create the SUT in the appropri- ate state in the setUp method using Implicit Setup (page 424) (called Testcase Class per Fixture; see page 631). The alternative is to use Delegated Setup by calling a Named State Reaching Method; this approach allows us to choose some other way to organize our Testcase Classes. Either way, the code that sets up the SUT will be easier to understand if it is short and sweet. That’s where a Named State Reaching Method comes in handy. By encapsulating the logic required to create the test objects in the cor- rect state in a single place (whether on the Testcase Class or a Test Helper), we reduce the amount of code we must update if we need to change how we put the test object into that state. Variation: Attachment Method Suppose we already have a test object and we want to modify it in some way. We fi nd ourselves performing this task in enough tests to want to code this modifi ca- tion once and only once. The solution in this case is an Attachment Method. The main difference between this variation and the original Creation Method pattern is that we pass in the object to be modifi ed (one that was probably returned by another Creation Method) and the object we want to set one of its attributes to; the Attachment Method does the rest of the work for us. Implementation Notes Most Creation Methods are created by doing an Extract Method [Fowler] refac- toring on parts of an existing test. When we write tests in an “outside-in” man- ner, we assume that the Creation Methods already exist and fi ll in the method bodies later. In effect, we defi ne a Higher-Level Language (see page 41) for defi n- ing our fi xtures. Nevertheless, there is another, completely different way to defi ne Creation Methods. Variation: Reuse Test for Fixture Setup We can set up the fi xture by calling another Test Method to do the fi xture setup for us. This assumes that we have some way of accessing the fi xture that the other test created, either through a Registry [PEAA] object or through instance variables of the Testcase Object (page 382). It may be appropriate to implement a Creation Method in this way when we already have tests that depend on other tests to set up their test fi xture but Creation Method Chapter 20 Fixture Setup Patterns Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 419 we want to reduce the likelihood that a change in the test execution order of Chained Tests (page 454) will cause tests to fail. Mind you, the tests will run more slowly because each test will call all the preceding tests it depends on each time each test is run rather than each test being run only once per test run. Of course, each test needs to call only the specifi c tests it actually depends on, not all tests in the test suite. This slowdown won’t be very noticeable if we have replaced any slow components, such as a database, with a Fake Object (page 551). Wrapping the Test Method in a Creation Method is a better option than calling the Test Method directly from the client Test Method because most Test Methods are named based on which test condition(s) they verify, not what (fi x- ture) they leave behind. The Creation Method lets us put a nice Intent-Revealing Name between the client Test Method and the implementing Test Method. It also solves the Lonely Test (see Erratic Test) problem because the other test is run explicitly from within the calling test rather than just assuming that it was already run. This scheme makes the test less fragile and easier to understand but it won’t solve the Interacting Tests (see Erratic Test) problem: If the test we call fails and leaves the test fi xture in a different state than we expected, our test will likely fail as well, even if the functionality we are testing is still working. Motivating Example In the following example, the testPurchase test requires a Customer to fi ll the role of the buyer. The fi rst and last names of the buyer have no bearing on the act of pur- chasing, but are required parameters of the Customer constructor; we do care that the Customer’s credit rating is good (“G”) and that he or she is currently active. public void testPurchase_firstPurchase_ICC() { Customer buyer = new Customer(17, "FirstName", "LastName", "G","ACTIVE"); // } public void testPurchase_subsequentPurchase_ICC() { Customer buyer = new Customer(18, "FirstName", "LastName", "G","ACTIVE"); // } The use of constructors in tests can be problematic, especially when we are building an application incrementally. Every change to the parameters of the constructor will force us to revisit a lot of tests or jump through hoops to keep the constructor signatures backward compatible for the sake of the tests. Creation Method Creation Method Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 420 Refactoring Notes We can use an Extract Method refactoring to remove the direct call to the construc- tor. We can give the new Creation Method an appropriate Intent-Revealing Name such as createCustomer based on the style of Creation Method we have created. Example: Anonymous Creation Method In the following example, instead of making that direct call to the Customer constructor, we now use the Customer Creation Method. Notice that the coupling between the fi xture setup code and the constructor has been removed. If another parameter such as phone number is added to the Customer constructor, only the Customer Creation Method must be updated to provide a default value; the fi xture setup code remains insulated from the change thanks to encapsulation. public void testPurchase_firstPurchase_ACM() { Customer buyer = createAnonymousCustomer(); // } public void testPurchase_subsequentPurchase_ACM() { Customer buyer = createAnonymousCustomer(); // } We call this pattern an Anonymous Creation Method because the identity of the customer does not matter. The Anonymous Creation Method might look something like this: public Customer createAnonymousCustomer() { int uniqueid = getUniqueCustomerId(); return new Customer(uniqueid, "FirstName" + uniqueid, "LastName" + uniqueid, "G", "ACTIVE"); } Note the use of a Distinct Generated Value to ensure that each anonymous Customer is slightly different to avoid accidentally creating an identical Customer. Example: Parameterized Creation Method If we wanted to supply some of the Customer’s attributes as parameters, we could defi ne a Parameterized Creation Method: public void testPurchase_firstPurchase_PCM() { Customer buyer = createCreditworthyCustomer("FirstName", "LastName"); Creation Method Chapter 20 Fixture Setup Patterns Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 421 // } public void testPurchase_subsequentPurchase_PCM() { Customer buyer = createCreditworthyCustomer("FirstName", "LastName"); // } Here’s the corresponding Parameterized Creation Method defi nition: public Customer createCreditworthyCustomer( String firstName, String lastName) { int uniqueid = getUniqueCustomerId(); Customer customer = new Customer(uniqueid,firstName,lastName,"G","ACTIVE"); customer.setCredit(CreditRating.EXCELLENT); customer.approveCredit(); return customer; } Example: Attachment Method Here’s an example of a test that uses an Attachment Method to associate two customers to verify that both get the best discount either of them has earned or negotiated: public void testPurchase_relatedCustomerDiscount_AM() { Customer buyer = createCreditworthyCustomer("Related", "Buyer"); Customer discountHolder = createCreditworthyCustomer("Discount", "Holder"); createRelationshipBetweenCustomers( buyer, discountHolder); // } Behind the scenes, the Attachment Method does whatever it takes to establish the relationship: private void createRelationshipBetweenCustomers( Customer buyer, Customer discountHolder) { buyer.addToRelatedCustomersList( discountHolder ); discountHolder.addToRelatedCustomersList( buyer ); } Although this example is relatively simple, the call to this method is still easier to understand than reading both the method calls of which it consists. Creation Method Creation Method Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 422 Example: Test Reused for Fixture Setup We can reuse other tests to set up the fi xture for our test. Here is an example of how not to do it: private Customer buyer; private AccountManager sut = new AccountManager(); private Account account; public void testCustomerConstructor_SRT() { // Exercise buyer = new Customer(17, "First", "Last", "G", "ACTIVE"); // Verify assertEquals( "First", buyer.firstName(), "first"); // } public void testPurchase_SRT() { testCustomerConstructor_SRT(); // Leaves in field "buyer" account = sut.createAccountForCustomer( buyer ); assertEquals( buyer.name, account.customerName, "cust"); // } The problem here is twofold. First, the name of the Test Method we are calling describes what it verifi es (e.g., a name) and not what it leaves behind (i.e., a Customer in the buyer fi eld. Second, the test does not return a Customer; it leaves the Customer in an instance variable. This scheme works only because the Test Method we want to reuse is on the same Testcase Class; if it were on an unrelated class, we would have to do a few backfl ips to access the buyer. A better way to accomplish this goal is to encapsulate this call behind a Creation Method: private Customer buyer; private AccountManager sut = new AccountManager(); private Account account; public void testCustomerConstructor_RTCM() { // Exercise buyer = new Customer(17, "First", "Last", "G", "ACTIVE"); // Verify assertEquals( "First", buyer.firstName(), "first"); // } public void testPurchase_RTCM() { buyer = createCreditworthyCustomer(); account = sut.createAccountForCustomer( buyer ); assertEquals( buyer.name, account.customerName, "cust"); // } public Customer createCreditworthyCustomer() { testCustomerConstructor_RTCM(); Creation Method Chapter 20 Fixture Setup Patterns Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... of this one test Implicit Setup is a way to reuse the fixture setup code for all Test Methods (page 348) in a Testcase Class (page 373) How It Works All tests in a Testcase Class create identical Fresh Fixtures by doing test fixture setup in a special setUp method on the Testcase Class The setUp method is called automatically by the Test Automation Framework (page 298) before it calls each Test Method... several Test Methods on the same Testcase Class need an identical Fresh Fixture If all Test Methods need the exact same fixture, then the entire Minimal Fixture needed by each test can be set up in the setUp method This form of Test Method organization is known as Testcase Class per Fixture (page 63 1) When the Test Methods need different fixtures because we are using a Testcase Class per Feature (page 62 4)... garbage-collected } Refactoring Notes These tests contain a fair amount of Test Code Duplication We can remove this duplication by refactoring this Testcase Class to use Implicit Setup There are two refactoring cases to consider First, when we discover that all tests are doing similar work to set up their test fixtures but are not sharing a setUp method, we can do an Extract Method [Fowler] refactoring of... interaction with the SUT out of the very numerous tests and into a much smaller number of places where it is easier to maintain It can, however, lead to Obscure Tests (page 1 86) when a Mystery Guest makes the test fixture used by each test less obvious It can also lead to a Fragile Fixture (see Fragile Test) if all tests in the class do not really need identical test fixtures Implementation Notes The main implementation... Fixture Setup Patterns Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Implicit Setup How do we construct the Fresh Fixture? We build the test fixture common to several tests in the setUp method Implicit Setup setup Setup Fixture test_ 1 Also known as: Hooked Setup, FrameworkInvoked Setup, Shared Setup Method test_ 2 SUT test_ n Testcase Class To execute an automated test, we require... Fixture to be built before the first test method that needs it? We use Lazy Initialization of the fixture to create it in the first test that needs it Testcase Object Implicit setUp Lazy Setup setUp testMethod_1 Is Fixture Set Up Yet? Test Suite Object Testcase Object Exercise No Create Fixture SUT Implicit setUp testMethod_n Shared Fixtures (page 317) are often used to speed up test execution by reducing the... Fixture, and hence Suite Fixture Setup, is to overcome the problem of Slow Tests (page 253) caused by too many test fixture objects being created each time every test is run Of course, a Shared Fixture can lead to Interacting Tests (see Erratic Test on page 228) or even a Test Run War (see Erratic Test) ; the sidebar “Faster Tests Without Shared Fixtures” (page 319) describes other ways to solve this... Fixture to be built before the first test method that needs it? We wrap the test suite with a Decorator that sets up the shared test fixture before running the tests and tears it down after all tests are done Setup Decorator setUp Testcase Object Fixture Setup Decorator TestSuite Object Fixture Implicit setUp Inline Setup Exercise Verify Inline Teardown Implicit tearDown Testcase Object SUT Implicit setUp... which we include all setup logic within each Test Method without factoring out any common code, and Delegated Setup, in which we move all common fixture setup code into a set of Creation Methods (page 415) that we can call from within the setup part of each Test Method Implicit Setup removes a lot of Test Code Duplication (page 213) and helps prevent Fragile Tests (page 239) by moving much of the nonessential... our tests one bit because the Test Automation Framework calls the setUp and tearDown methods for each Testcase Object All we have done is moved the code We need to find a way to set up the fixture only once per test run Lazy Setup 439 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Refactoring Notes We can reduce the number of times we set up the fixture by converting this test . the Test Method, unlike with In-line Setup (page 408) and Delegated Set- up (page 411). Fixture SUT Testcase Class setup test_ 1 test_ 2 test_ n Setup Fixture SUT Testcase Class setup test_ 1 test_ 2 test_ n Setup Also. of Test Method organization is known as Testcase Class per Fixture (page 63 1). When the Test Methods need different fi xtures because we are using a Testcase Class per Feature (page 62 4) or Testcase. garbage-collected } Refactoring Notes These tests contain a fair amount of Test Code Duplication. We can remove this duplication by refactoring this Testcase Class to use Implicit Setup. There are two refactoring