Crafting Java with Test-Driven Development, Part 10: Building the View
- Preventing Unnecessary Duplication
- Building a Panel
- Where Do We Go from Here?
Preventing Unnecessary Duplication
Part 9 of this series introduced test code to verify the contents of the Texas Hold ’Em title bar. It’s one simple line in HoldEmTest:
assertEquals("Hold ’Em", frame.getTitle());
It’s also one simple line in the production class, HoldEm:
frame.setTitle("Hold ’Em");
Those two lines each contain the same hard-coded string literal. Sometimes we create duplication in production code, sometimes we create it in the tests, and sometimes we create duplication across test and production code. Regardless, we’ll need to eliminate it before moving on.
We could introduce a constant, perhaps a static final field defined on HoldEm. We could also consider using the Java resource bundle, a construct designed to help us manage locale-specific resources. We might want to sell our application internationally; in that case, we’d have to provide internationalization support in the application. Part of that internationalization would be done by using resource bundles.
I said we might want to sell our application internationally. We’re really not sure yet. So, do we want to use resource bundles yet? From a pure Agile standpoint, they’re something we don’t need. Introducing them would seem to be premature.
What isn’t premature, however, is our need to eliminate duplication. We must stomp out all duplication; otherwise, our application will slowly but surely die. Given a number of options for eliminating duplication, we can choose any one of them, as long as the one we choose doesn’t introduce unnecessary complexity. Using resource bundles is a simple solution to this problem, and also one that fits into an established standard. The cost is roughly the same either way, so we choose the solution that results in a more flexible design.
We’ll want to create a utility method that extracts a string from the resource bundle. A test for this utility might write a sample properties file containing fixed key-value pairs, and then assert that the utility method extracted this information. The problem is, however, that we don’t want to overwrite the same properties file that the rest of our application normally uses.
One way we can solve this problem is by designing the bundle utility to allow the use of different property files. That sounds more like a related handful of methods than a single utility method. Let’s apply the single responsibility principle and put this common functionality into its own class. We’ll name it Bundle. The test and associated production code are shown in Listings 1 and 2.
Listing 1 BundleTest.
package util; import java.io.*; import junit.framework.*; public class BundleTest extends TestCase { private String existingBundleName; private static final String SUFFIX = "test"; private static final String TESTFILE = String.format("./%s/%s%s.properties", Bundle.PACKAGE, Bundle.getName(), SUFFIX); protected void setUp() { deleteTestBundle(); existingBundleName = Bundle.getName(); Bundle.use(existingBundleName + SUFFIX); } protected void tearDown() { Bundle.use(existingBundleName); deleteTestBundle(); } private void deleteTestBundle() { new File(TESTFILE).delete(); } public void testGet() throws IOException { BufferedWriter writer = new BufferedWriter(new FileWriter(TESTFILE)); writer.write("key=value"); writer.newLine(); writer.close(); assertEquals("value", Bundle.get("key")); } }
Listing 2 Bundle.
package util; import java.util.*; public class Bundle { static final String PACKAGE = "util"; private static String baseName = "holdem"; private static ResourceBundle bundle; public static String get(String key) { if (bundle == null) load(); return bundle.getString(key); } private static void load() { bundle = ResourceBundle.getBundle(PACKAGE + "." + getName()); } public static String getName() { return baseName; } public static void use(String name) { baseName = name; bundle = null; } }
I see a lot of systems in which each class contains code that loads the resource bundle. To me, this is unnecessary duplication. It also introduces strong dependencies of your system to Sun’s implementation specifics. We’ll instead encapsulate that detail in our Bundle class.
Once the Bundle class is in place, we can update our HoldEmTest code:
assertEquals(Bundle.get(HoldEm.TITLE), frame.getTitle());
and our HoldEm code:
static final String TITLE = "holdem.title"; ... frame.setTitle(Bundle.get(HoldEm.TITLE));
Of course, we need to create the properties file! Per the code, it should be named holdem.properties, and should appear in the util directory. Here are its contents:
holdem.title=Hold ’Em
Having the Bundle utility class in place will pay off as we add more text to the user interface.