Hard-Coded Value, Constant Value
How do we specify the values to be used in tests?
We use literal constants for object attributes and assertions.
BigDecimal expectedTotal = new BigDecimal("99.95");
The values we use for the attributes of objects in our test fixture and the expected outcome of our test are often related to one another in a way that is defined in the requirements. Getting these values—and, in particular, the relationship between the pre-conditions and the post-conditions—right is crucial because it drives the correct behavior into the SUT.
Literal Values are a popular way to specify the values of attributes of objects in a test.
We use a literal constant of the appropriate type for each attribute of an object or for use as an argument of a method call to the SUT or an Assertion Method (page 362). The expected values are calculated by hand, calculator, or spreadsheet and hard-coded within the test as Literal Values.
Using a Literal Value in-line makes it very clear which value is being used; there is no doubt about the value's identity because it is right in front of our face. Unfortunately, using Literal Values can make it difficult to see the relationships between the values used in various places in the test, which may in turn lead to Obscure Tests (page 186). It certainly makes sense to use Literal Values if the testing requirements specify which values are to be used and we want to make it clear that we are, in fact, using those values. [We might sometimes consider using a Data-Driven Test (page 288) instead to avoid the effort and transcription errors associated with copying the data into test methods.]
One downside of using a Literal Value is that we might use the same value for two unrelated attributes; if the SUT happens to use the wrong one, tests may pass even though they should not. If the Literal Value is a filename or a key used to access a database, the meaning of the value is lost—the content of the file or record actually drives the behavior of the SUT. Using a Literal Value as the key does nothing to help the reader understand the test in such a case, and we are likely to suffer from Obscure Tests.
If the values in the expected outcome can be derived from the values in the fixture setup logic, we will be more likely to use the Tests as Documentation (see page 23) if we use Derived Values (page 718). Conversely, if the values are not important to the specification of the logic being tested, we should consider using Generated Values (page 723).
The most common way to use a Literal Value is with literal constants within the code. When the same value needs to be used in several places in the test (typically during fixture setup and result verification), this approach can obscure the relationship between the test pre-conditions and post-conditions. Introducing an evocatively named symbolic constant can make this relationship much clearer. Likewise, if we cannot use a self-describing value, we can still make the code easier to use by defining a suitably named symbolic constant and using it wherever we would have used the Literal Value.
When we need to use the same Literal Value in several places in a single Test Method (page 348) or within several distinct tests, it is a good practice to use a Symbolic Constant instead of a Literal Value. A Symbolic Constant is functionally equivalent to a Literal Value but reduces the likelihood of High Test Maintenance Cost (page 265).
When several attributes of an object need the same kind of value, using different values provides advantages by helping us to prove that the SUT is working with the correct attribute. When an attribute or argument is an unconstrained string, it can be useful to choose a value that describes the role of the value in the test (a Self-Describing Value). For example, using "Not an existing customer" for the name of a customer might be more helpful to the reader than using "Joe Blow," especially when we are debugging or when the attributes are included in the test failure output.
Because Literal Value is usually the starting point when writing tests, I'll dispense with a motivating example and cut straight to the chase. Here's an example of the Literal Value pattern in action. Note the use of Literal Values in both the fixture setup logic and the assertion.
public void testAddItemQuantity_1() throws Exception {
Product product = new Product("Widget", 19.95);
Invoice invoice = new Invoice();
// Exercise
invoice.addItemQuantity(product, 1);
// Verify
List lineItems = invoice.getLineItems();
LineItem actualItem = (LineItem)lineItems.get(0);
assertEquals(new BigDecimal("19.95"),
actualItem.getExtendedPrice());
}
The Product
constructor requires both a name and a cost. The assertion on the extendedCost
of the lineItem
requires a value for the total cost of the product for that line item. In this example, we included these values as hard-coded literal constants. In the next example, we'll use symbolic constants instead.
We can reduce the Test Code Duplication (page 213) in the form of the hard-coded Literal Value of 19.95
by doing a Replace Magic Number with Symbolic Constant [Fowler] refactoring.
This refactored version of the original test replaces the duplicated Literal Value of the widget's price (19.95) with a suitably named Symbolic Constant that is used during fixture setup as well as result verification:
public void testAddItemQuantity_1s() throws Exception {
BigDecimal widgetPrice = new BigDecimal("19.95");
Product product = new Product("Widget", widgetPrice);
Invoice invoice = new Invoice();
// Exercise
invoice.addItemQuantity(product, 1);
// Verify
List lineItems = invoice.getLineItems();
LineItem actualItem = (LineItem)lineItems.get(0);
assertEquals(widgetPrice, actualItem.getExtendedPrice());
}
This refactored version of the test provides a Self-Describing Value for the mandatory name argument passed to the Product
constructor. This value is not used by the method we are testing; it is merely stored for later access by another method we are not testing here.
public void testAddItemQuantity_1b() throws Exception {
BigDecimal widgetPrice = new BigDecimal("19.95");
Product product = new Product("Irrelevant product name",
widgetPrice);
Invoice invoice = new Invoice();
// Exercise
invoice.addItemQuantity(product, 1);
// Verify
List lineItems = invoice.getLineItems();
LineItem actualItem = (LineItem)lineItems.get(0);
assertEquals(widgetPrice, actualItem.getExtendedPrice());
}
This test needs to verify that the item's name is taken from the product's name. We'll use a Distinct Value for the name and the SKU so we can tell them apart.
public void testAddItemQuantity_1c() throws Exception {
BigDecimal widgetPrice = new BigDecimal("19.95");
String name = "Product name";
String sku = "Product SKU";
Product product = new Product(name, sku, widgetPrice);
Invoice invoice = new Invoice();
// Exercise
invoice.addItemQuantity(product, 1);
// Verify
List lineItems = invoice.getLineItems();
LineItem actualItem = (LineItem)lineItems.get(0);
assertEquals(name, actualItem.getName());
}
This also happens to be an example of a self-describing value.
Calculated Value
How do we specify the values to be used in tests?
We use expressions to calculate values that can be derived from other values.
BigDecimal expectedTotal = itemPrice.multiply(QUANTITY);
The values we use for the attributes of objects in our test fixtures and the result verification parts of our tests are often related to one another in a way that is defined in the requirements. Getting these values—and, in particular, the relationship between the pre-conditions and the post-conditions—right is crucial because it drives the correct behavior into the SUT and helps the tests act as documentation of our software.
Often, some of these values can be derived from other values in the same test. In these cases the benefits from using our Tests as Documentation (see page 23) are improved if we show the derivation by calculating the values using the appropriate expression.
Computers are really good at math and string concatenation. We can avoid doing the math in our head (or with a calculator) by coding the math for expected results as arguments of the Assertion Method (page 362) calls directly into the tests. We can also use Derived Values as arguments for fixture object creation and as method arguments when exercising the SUT.
Derived Values, by their very nature, encourage us to use variables or symbolic constants to hold the values. These variables/constants can be initialized at compile time (constants), during class or Testcase Object (page 382) initialization, during fixture setup, or within the body of the Test Method (page 348).
We should use a Derived Value whenever we have values that can be derived in some deterministic way from other values in our tests. The main drawback of using Derived Values is that the same math error (e.g., rounding errors) could appear in both the SUT and the tests. To be safe, we might want to code a few of the pathological test cases using Literal Values (page 714) just in case such a problem might be present. If the values we are using must be unique or don't affect the logic in the SUT, we may be better off using Generated Values (page 723) instead.
We can use a Derived Value either as part of fixture setup (Derived Input or One Bad Attribute) or when determining the expected values to be compared with those generated by the SUT (Derived Expectation). These uses are described in a bit more detail later in this section.
Sometimes our test fixture contains similar values that the SUT might compare or use to base its logic on the difference between them. For example, a Derived Input might be calculated in the fixture setup portion of the test by adding the difference to a base value. This operation makes the relationship between the two values explicit. We can even put the value to be added in a symbolic constant with an Intent-Revealing Name [SBPP] such as MAXIMUM_ALLOWABLE_TIME_DIFFERENCE
.
A Derived Input is often employed when we need to test a method that takes a complex object as an argument. For example, thorough "input validation" testing requires that we exercise the method with each of the attributes of the object set to one or more possible invalid values to ensure that it handles all of these cases correctly. Because the first rejected value could cause termination of the method, we must verify each bad attribute in a separate call to the SUT; each of these calls, in turn, should be done in a separate test method (each should be a Single-Condition Test; see page 45). We can instantiate the invalid object easily by first creating a valid object and then replacing one of its attributes with an invalid value. It is best to create the valid object using a Creation Method (page 415) so as to avoid Test Code Duplication (page 213).
When some value produced by the SUT should be related to one or more of the values we passed in to the SUT as arguments or as values in the fixture, we can often derive the expected value from the input values as the test executes rather than using precalculated Literal Values. We then use the result as the expected value in an Equality Assertion (see Assertion Method).
The following test doesn't use Derived Values. Note the use of Literal Values in both the fixture setup logic and the assertion.
public void testAddItemQuantity_2a() throws Exception {
BigDecimal widgetPrice = new BigDecimal("19.99");
Product product = new Product("Widget", widgetPrice);
Invoice invoice = new Invoice();
// Exercise
invoice.addItemQuantity(product, 5);
// Verify
List lineItems = invoice.getLineItems();
LineItem actualItem = (LineItem)lineItems.get(0);
assertEquals(new BigDecimal("99.95"),
actualItem.getExtendedPrice());
}
Test readers may have to do some math in their heads to fully appreciate the relationship between the values in the fixture setup and the value in the result verification part of the test.
To make this test more readable, we can replace any Literal Values that are actually derived from other values with formulas that calculate these values.
The original example contained only one line item for five instances of the product. We therefore calculated the expected value of the extended price attribute by multiplying the unit price by the quantity, which makes the relationship between the values explicit.
public void testAddItemQuantity_2b() throws Exception {
BigDecimal widgetPrice = new BigDecimal("19.99");
BigDecimal numberOfUnits = new BigDecimal("5");
Product product = new Product("Widget", widgetPrice);
Invoice invoice = new Invoice();
// Exercise
invoice.addItemQuantity(product, numberOfUnits);
// Verify
List lineItems = invoice.getLineItems();
LineItem actualItem = (LineItem)lineItems.get(0);
BigDecimal totalPrice = widgetPrice.multiply(numberOfUnits);
assertEquals(totalPrice, actualItem.getExtendedPrice());
}
Note that we have also introduced symbolic constants for the unit price and quantity to make the expression even more obvious and to reduce the effort of changing the values later.
Suppose we have the following Customer Factory Method [GOF], which takes a CustomerDto
object as an argument. We want to write tests to verify what occurs when we pass in invalid values for each of the attributes in the CustomerDto
. We could create the CustomerDto
in-line in each Test Method with the appropriate attribute initialized to some invalid value.
public void testCreateCustomerFromDto_BadCredit() {
// fixture setup
CustomerDto customerDto = new CustomerDto();
customerDto.firstName = "xxx";
customerDto.lastName = "yyy";
// etc.
customerDto.address = createValidAddress();
customerDto.creditRating = CreditRating.JUNK;
// exercise the SUT
try {
sut.createCustomerFromDto(customerDto);
fail("Expected an exception");
} catch (InvalidInputException e) {
assertEquals( "Field", "Credit", e.field );
}
}
public void testCreateCustomerFromDto_NullAddress() {
// fixture setup
CustomerDto customerDto = new CustomerDto();
customerDto.firstName = "xxx";
customerDto.lastName = "yyy";
// etc.
customerDto.address = null;
customerDto.creditRating = CreditRating.AAA;
// exercise the SUT
try {
sut.createCustomerFromDto(customerDto);
fail("Expected an exception");
} catch (InvalidInputException e) {
assertEquals( "Field", "Address", e.field );
}
}
The obvious problem with this code is that we end up with a lot of Test Code Duplication because we need at least one test per attribute. The problem becomes even worse if we are doing incremental development: We will require more tests for each newly added attribute, and we will have to revisit all existing tests to add the new attribute to the Factory Method signature.
The solution is to define a Creation Method that produces a valid instance of the CustomerDto
(by doing an Extract Method [Fowler] refactoring on one of the tests) and uses it in each test to create a valid DTO. Then we simply replace one of the attributes with an invalid value in each of the tests. Each test now has an object with One Bad Attribute, with each one invalid in a slightly different way.
public void testCreateCustomerFromDto_BadCredit_OBA() {
CustomerDto customerDto = createValidCustomerDto();
customerDto.creditRating = CreditRating.JUNK;
try {
sut.createCustomerFromDto(customerDto);
fail("Expected an exception");
} catch (InvalidInputException e) {
assertEquals( "Field", "Credit", e.field );
}
}
public void testCreateCustomerFromDto_NullAddress_OBA() {
CustomerDto customerDto = createValidCustomerDto();
customerDto.address = null;
try {
sut.createCustomerFromDto(customerDto);
fail("Expected an exception");
} catch (InvalidInputException e) {
assertEquals( "Field", "Address", e.field );
}
}
How do we specify the values to be used in tests?
We generate a suitable value each time the test is run.BigDecimal uniqueCustomerNumber = getUniqueNumber();
When initializing the objects in the test fixture, one issue that must be dealt with is the fact that most objects have various attributes (fields) that need to be supplied as arguments to the constructor. Sometimes the exact values to be used affect the outcome of the test. More often than not, however, it is important only that each object use a different value. When the precise values of these attributes are not important to the test, it is important not to have them visible within the test!
Generated Values are used in conjunction with Creation Methods (page 415) to help us remove this potentially distracting information from the test.
Instead of deciding which values to use in our tests while we are coding the tests, we generate the values when we actually execute the tests. We can then pick values to satisfy specific criteria such as "must be unique in the database" that can be determined only as the test run unfolds.
We use a Generated Value whenever we cannot or do not want to specify the test values until the test is executing. Perhaps the value of an attribute is not expected to affect the outcome of the test and we don't want to be bothered to define Literal Values (page 714), or perhaps we need to ensure some quality of the attribute that can be determined only at runtime. In some cases, the SUT requires the value of an attribute to be unique; using a Generated Value can ensure that this criterion is satisfied and thereby prevent Unrepeatable Tests (see Erratic Test on page 228) and Test Run Wars (see Erratic Test) by reducing the likelihood of a test conflicting with its parallel incarnation in another test run. Optionally, we can use this distinct value for all attributes of the object; object recognition then becomes very easy when we inspect the object in a debugger.
One thing to be wary of is that different values could expose different bugs. For example, a single-digit number may be formatted correctly, whereas a multidigit number might not (or vice versa). Generated Values can result in Nondeterministic Tests (see Erratic Test); if we encounter nondeterminism (sometimes the test passes and then fails during the very next run), we must check the SUT code to see whether differences in value could be the root cause.
In general, we shouldn't use a Generated Value unless the value must be unique because of the nondeterminism such a value may introduce. The obvious alternative is to use a Literal Value. A less obvious alternative is to use a Derived Value (page 718), especially when we must determine the expected results of a test.
We can generate values in a number of ways. The appropriateness of each technique depends on the circumstance.
When we need to ensure that each test or object uses a different value, we can take advantage of Distinct Generated Values. In such a case, we can create a set of utility functions that will return unique values of various types (e.g., integers, strings, floating-point numbers). The various getUnique
methods can all be built upon an integer sequence number generator. For numbers that must be unique within the scope of a shared database, we can use database sequences or a sequence table. For numbers that must be unique within the scope of a particular test run, we can use an in-memory sequence number generator (e.g., use a Java static variable that is incremented before usage). In-memory sequence numbers that start from the number 1 each time a test suite is run offer a useful quality: The values generated in each test are the same for each run and can simplify debugging.
One way to obtain good test coverage without spending a lot of time analyzing the behavior and generating test conditions is to use different values each time we run the tests. Using a Random Generated Value is one way to accomplish this goal. While use of such values may seem like a good idea, it makes the tests nondeterministic (Nondeterministic Tests) and can make debugging failed tests very difficult. Ideally, when a test fails, we want to be able to repeat that test failure on demand. To do so, we can log the Random Generated Value as the test is run and show it as part of the test failure. We then need to find a way to force the test to use that value again while we are troubleshooting the failed test. In most cases, the effort required outweighs the potential benefit. Of course, when we need this technique, we really need it.
An optional enhancement is to combine a Generated Value with a Derived Value by using the same generated integer as the root for all attributes of a single object. This result can be accomplished by calling getUniqueInt
once and then using that value to build unique strings, floating-point numbers, and other values. With a Related Generated Value, all fields of the object contain "related" data, which makes the object easier to recognize when debugging. Another option is to separate the generation of the root from the generation of the values by calling generateNewUniqueRoot
explicitly before calling getUniqueInt, getUniqueString
, and so on.
Another nice touch for strings is to pass a role-describing argument to the function that is combined with the unique integer key to make the code more intent-revealing. Although we could also pass such arguments to the other functions, of course we wouldn't be able to build them into an integer value.
The following test uses Literal Values for the arguments to a constructor:
public void testProductPrice_HCV() {
// Setup
Product product =
new Product( 88, // ID
"Widget", // Name
new BigDecimal("19.99")); // Price
// Exercise
// ...
}
We can convert the test to use Distinct Generated Values by replacing the Literal Values with calls to the appropriate getUnique
method. These methods simply increment a counter each time they are called and use that counter value as the root for construction of an appropriately typed value.
Here is the same test using a Distinct Generated Value. For the getUniqueString
method, we'll pass a string describing the role ("Widget Name").
public void testProductPrice_DVG() {
// Setup
Product product =
new Product( getUniqueInt(), // ID
getUniqueString("Widget"), // Name
getUniqueBigDecimal()); // Price
// Exercise
// ...
}
static int counter = 0;
int getUniqueInt() {
counter++;
return counter;
}
BigDecimal getUniqueBigDecimal() {
return new BigDecimal(getUniqueInt());
}
String getUniqueString(String baseName) {
return baseName.concat(String.valueOf( getUniqueInt()));
}
This test uses a different generated value for each argument of the constructor call. The numbers generated in this way are consecutive but the test reader still needs to look at a specific attribute when debugging to get a consistent view. We probably should not generate the price value if the logic we were testing was related to price calculation because that would force our verification logic to accommodate different total costs.
We can ensure that all values used by the test are obviously related by separating the generation of the root value from the construction of the individual values. In the following example, we've moved the generation of the root to the setUp
method so each test method gets a new value only once. The methods that retrieve the various values (e.g., getUniqueString
) simply use the previously generated root when deriving the Generated Values.
public void testProductPrice_DRVG() {
// Setup
Product product =
new Product( getUniqueInt(), // ID
getUniqueString("Widget"), // Name
getUniqueBigDecimal()); // Price
// Exercise
// ...
}
static int counter = 0;
public void setUp() {
counter++;
}
int getUniqueInt() {
return counter;
}
String getUniqueString(String baseName) {
return baseName.concat(String.valueOf( getUniqueInt()));
}
BigDecimal getUniqueBigDecimal() {
return new BigDecimal(getUniqueInt());
}
If we looked at this object in an object inspector or database or if we dumped part of it to a log, we could readily tell which object we were looking at regardless of which field we happened to see.
Dummy, Dummy Parameter, Dummy Value, Placeholder, Stub
How do we specify the values to be used in tests when the only usage is as irrelevant arguments of SUT method calls?
We pass an object that has no implementation as an argument of a method called on the SUT.
Invoice inv = new Invoice( new DummyCustomer() );
Getting the SUT into the right state to start a test often requires calling other methods of the SUT. These methods commonly take as arguments objects that are stored in instance variables for later use. Often, these objects (or at least some attributes of these objects) are never used in the code that we are actually testing. Instead, we create them solely to conform to the signature of some method we must call to get the SUT into the right state. Constructing these objects can be nontrivial and adds unnecessary complexity to the test.
In these cases, a Dummy Object can be passed as an argument, eliminating the need to build a real object.
We create an instance of some object that can be instantiated easily and with no dependencies; we then pass that instance as the argument of the method of the SUT. Because it won't actually be used within the SUT, we don't need any implementation for this object. If any of the methods of the Dummy Object are invoked, the test really should throw an error. Trying to invoke a nonexistent method will typically produce that result.
We can use Dummy Objects whenever we need to use objects as attributes of other objects or arguments of methods on the SUT or other fixture objects. Using Dummy Objects helps us avoid Obscure Tests (page 186) by leaving out the irrelevant code that would be necessary to build real objects and by making it clear which objects and values are not used by the SUT.
If we need to control the indirect inputs or verify the indirect outputs of the SUT, we should probably use a Test Stub (page 529) or a Mock Object (page 544) instead. If the object will be used by the SUT but we cannot provide the real object, we should consider providing a Fake Object (page 551) that provides just enough behavior for the test to execute.
We can use one of the value patterns when the SUT really does need to use the object in some way. Either a Literal Value (page 714), a Generated Value (page 723), or a Derived Value (page 718) may be appropriate, depending on the circumstance.
We can use a Dummy Argument whenever methods of the SUT take objects as arguments1 and those objects are not relevant to the test.
We can use a Dummy Attribute whenever we are creating objects that will be used as part of the fixture or as arguments of SUT methods, and some of the attributes of those objects are not relevant to the test.
The simplest implementation of a Dummy Object is to pass a null
value as the argument. This approach works even in a statically typed language such as Java, albeit only if the method being called doesn't check for null arguments. If the method complains when we pass it null
, we'll need to employ a slightly more sophisticated implementation. The biggest disadvantage to using null
is that it is not very descriptive.
In dynamically typed languages such as Ruby, Perl, and Python, the actual type of the object will never be checked (because it will never be used), so we can use any class such as String
or Object
. In such a case, it is useful to give the object a Self-Describing Value (see Literal Value) such as "Dummy Customer."
In statically typed languages (such as Java, C#, and C++), we must ensure that the Dummy Object is type compatible with the parameter it is to match. Type compatibility is much easier to achieve if the parameter has an abstract type (e.g., an Interface
in Java) because we can create our own trivial implementation of the type or pass a suitable Pseudo-Object (see Hard-Coded Test Double on page 568). If the parameter type is a concrete class, we may be able to create a trivial instance of it or we may need to create an instance of a Test-Specific Subclass (page 579) within our test.
Some Mock Object frameworks have Test Utility Methods (page 599) that will generate a Dummy Object for a specified class that takes a String
argument for a Self-Describing Value.
While the Dummy Object may, in fact, be null
, it is not the same as a Null Object [PLOPD3]. A Dummy Object is not used by the SUT, so its behavior is either irrelevant or it should throw an exception when executed. In contrast, a Null Object is used by the SUT but is designed to do nothing. That's a small but very important distinction!
In this example, we are testing the Invoice
but we require a Customer
to instantiate the invoice. The Customer
requires an Address
, which in turn requires a City
. Thus we find ourselves creating several additional objects just to set up the fixture. But if we know that the behavior we are testing should not access the Customer
at all, why do we need to create it and all the objects on which it depends?
public void testInvoice_addLineItem_noECS() {
final int QUANTITY = 1;
Product product = new Product(getUniqueNumberAsString(),
getUniqueNumber());
State state = new State("West Dakota", "WD");
City city = new City("Centreville", state);
Address address = new Address("123 Blake St.", city, "12345");
Customer customer= new Customer(getUniqueNumberAsString(),
getUniqueNumberAsString(),
address);
Invoice inv = new Invoice(customer);
// Exercise
inv.addItemQuantity(product, QUANTITY);
// Verify
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(inv, product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
This test is quite cluttered as a result of the extra object creation. How is the behavior we are testing related to the Address
and City
? From this test, we can only assume that there is some relation. But this misleads the test reader!
If the objects in the fixture are not relevant to the test, they should not be visible in the test. Therefore, we should try to eliminate the need to create all these objects. We could try passing in null
for the Customer
. In this case, the constructor checks for null
and rejects it, so we have to find another way.
The solution is to replace the object that is not important to our test with a Dummy Object. In dynamically typed languages, we could just pass in a string. In statically typed languages such as Java and C#, however, we must pass in a type-compatible object. In this case, we have chosen to do an Extract Interface [Fowler] refactoring on Customer
to create a new interface and then create a new implementation class called DummyCustomer
. Of course, as part of the Extract Interface refactoring, we must replace all references to Customer
with the new interface name so that the DummyCustomer
will be acceptable. A less intrusive option would be to use a Test-Specific Subclass of Customer
that adds a test-friendly constructor.
Here's the same test using a Dummy Object instead of the Product
name and the Customer
. Note how much simpler the fixture setup has become!
public void testInvoice_addLineItem_DO() {
final int QUANTITY = 1;
Product product = new Product("Dummy Product Name",
getUniqueNumber());
Invoice inv = new Invoice( new DummyCustomer() );
LineItem expItem = new LineItem(inv, product, QUANTITY);
// Exercise
inv.addItemQuantity(product, QUANTITY);
// Verify
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
assertLineItemsEqual("", expItem, actual);
}
Using a Dummy Object for the name of the Product
was simple because it is a string and has no uniqueness requirement. Thus we were able to use a Self-Describing Value. We were not able to use a Dummy Object for the Product
number because it must be unique, so we left it as a Generated Value. The Customer
was a bit trickier because the LineItem
's constructor expected a non-null object. Because this example is written in Java, the method parameter is strongly typed; for this reason, we needed to create an alternative implementation of the ICustomer
interface with a no-argument constructor to simplify in-line construction. Because the DummyCustomer
is never used, we have created it in-line rather than declaring a variable to hold it. This choice reduces the fixture setup code by one line, and the presence of the in-line constructor call within the call to the Invoice
constructor reinforces the message that we need the Dummy Object only for the constructor call and not for the rest of the test.
Here is the code for the DummyCustomer
:
public class DummyCustomer implements ICustomer {
public DummyCustomer() {
// Real simple; nothing to initialize!
}
public int getZone() {
throw new RuntimeException("This should never be called!");
}
}
We have implemented the DummyCustomer
class with just those methods declared in the interface; because each method throws an exception, we know when it is hit. We could also have used a Pseudo-Object for the DummyCustomer
. In other circumstances we might have been able to simply pass in null
or construct a dummy instance of the real class. The major problem with the latter technique is that we won't know for sure if the Dummy Object is actually used.
When [UTwJ] refers to a "dummy object," these authors are referring to what this book terms a Test Stub. See Mocks, Fakes, Stubs, and Dummies in Appendix B for a more thorough comparison of the terminology used in various books and articles. The JMock and NMock frameworks for testing with Mock Objects support auto-generation of Dummy Objects.
18.191.102.112