Literal Value
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 714 of xUnit Test Patterns for the latest information.
Also known as: Hard-Coded Value, Constant Value
How do we specify the values to be used in tests?
Use literal constants for object attributes and assertions.
BigDecimal expectedTotal = new BigDecimal("99.95"); Example LiteralValueSketch embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
The values we use for the attributes of objects in our test fixture and the expected outcome of our test are often related to each other 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 this is what will drive the correct behavior into the system under test (SUT).
Literal Values are a common way to specify the values of attributes of objects in a test.
How It Works
We use a literal constant of the appropriate type for each attribute of an object or for use as a parameter to a method call to the SUT or an Assertion Method (page X). The expected values are calculated by hand, calculator or spreadsheet and hard-coded within the test as Literal Values.
When To Use It
Using a Literal Value inline makes it very clear what value is being used; there is no doubt about it since it is in our face. Unfortunately, it can make it difficult to see the relationships between values used in various places in the test and that can lead to Obscure Tests (page X). It certainly makes sense to use Literal Values if the values are provided to us by the requirements and we want to make it clear that we are using those values (though we might want to consider a Data-Driven Test (page X) to avoid the effort and transcription errors associated with copying the data into test methods.)
One down side 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, we'll have tests that 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 because it is the content of the file or record that drives the behavior of the SUT. Using a Literal Value as the key does nothing to help the reader understand the test 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 Goals of Test Automation on page X) if we use Derived Values (page X) but if the values are not important to the specification of the logic being tested, we should consider using Generated Values (page X).
Implementation Notes
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 set up and result verification), this can make it hard to see the relationship between test preconditions and postconditions. Introducing an evocatively named symbolic constant can make the 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 use that wherever we would have used the literal.
Variation: Symbolic Constant
When we need to use the same Literal Value in several places in a single Test Method (page X) or within several distinct tests, it is a good practice to use a symbol constant instead of a Literal Value. A symbol constant is fuctionally equivalent to a Literal Value but it reduces the likelihood of High Test Maintenance Cost (page X).
Variation: Self-Describing Value
When several attributes of an object need the same kind of value, it is useful to use different values as this helps us to prove that the correct attribute is being used by the SUT. When an attribute or parameter is an unconstrained string, it can be useful to use 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 "Joe Blow" especially when debugging or when the attributes are included in the test failure output.
Example: Literal Value
Since 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 Literal Value 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()); } Example HardCodedConstants embedded from java/com/clrstream/camug/example/test/MiscExamples.java
The Product constructor requires 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 as hard-coded literal constants. In the next example, we'll use a symbolic constant instead.
Refactoring Notes
We can reduce the Test Code Duplication (page X) in the form of the hard-coded Literal Value of 19.95 by doing a Replace Magic Number with Symbolic Constant[Fowler] refactoring.
Example: Symbolic Constant
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()); } Example SymbolicConstants embedded from java/com/clrstream/camug/example/test/MiscExamples.java
Example: Self-Describing Value
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()); } Example SelfDescribingValue embedded from java/com/clrstream/camug/example/test/MiscExamples.java
Example: Distinct Value
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()); } Example DistinctValue embedded from java/com/clrstream/camug/example/test/MiscExamples.java
This also happens to be an example of a self-describing value.
Copyright © 2003-2008 Gerard Meszaros all rights reserved