Let’s look at an example in Java. Listing 2-1 shows a simple Java class with several clues about its intent in the way it is constructed. Consider the ScoreWatcher
class that is part of a system to track sports scores. It encapsulates the function of obtaining the scores from a news feed.
class ScoreWatcher {
private final NewsFeed feed;
private int pollFrequency; // Seconds
public ScoreWatcher(NewsFeed feed, int pollFrequency) {
this.feed = feed;
this.pollFrequency = pollFrequency;
}
public void setPollFrequency(int pollFrequency) {
this.pollFrequency = pollFrequency;
}
public int getPollFrequency() {
return pollFrequency;
}
public NewsFeed getFeed() {
return feed;
}
...
}
First, look at the attributes defined for the class. The class author defined the feed
attribute as final
3 while pollFrequency
is not. What does this tell us? It expresses the intent that feed
should only be set once on construction, but pollFrequency
can be modified throughout the life of the object. The presence of both a getter and setter for pollFrequency
but only a getter for feed
reinforces that reading.
3. In Java, the final
keyword indicates that the declared variable cannot be changed once it has been initialized.
But that only gives us an understanding of the implementation intent. What functional intent might this support? We could make a reasonable conclusion in the context that exactly one of these classes should be allocated for each news feed we could use. We could also infer that perhaps there is exactly one NewsFeed
per score to be monitored, which is used to initialize the ScoreWatcher
. We could also speculate that the management of multiple feeds, if available, might hide behind the single feed interface. This would require verification but seems reasonable under the circumstances.
However, that hypothesis has a weakness that is probably due to a limit in the expressiveness of Java. Without knowing the construction of the NewsFeed
class, we can speculate that it is possible to modify the object referenced by feed
even though the reference itself cannot be changed. In C++, we could declare the attribute as
const NewsFeed * const feed;
which communicates not only that the pointer cannot be changed, but that you cannot use the pointer to change the object it references. This provides an additional marker of contextual immutability in C++ that does not exist in Java. It is relatively easy in Java to make all instances of a class immutable. It takes considerably more effort to make an object immutable through a particular reference, perhaps requiring the creation of an immutability proxy to wrap the instance.
So how does this change our tests? The construction of the class—the implementation—clearly specifies that the feed given to the class should not change during the lifetime of the class. Is this the intent? Let’s look at a test that verifies that assumption in Listing 2-2.
class TestScoreWatcher {
@Test
public void testScoreWatcher_SameFeed() {
// Set up
NewsFeed expectedFeed = new NewsFeed();
int expectedPollFrequency = 70;
// Execute SUT4
ScoreWatcher sut = new ScoreWatcher(expectedFeed,
expectedPollFrequency);
// Verify results
Assert.assertNotNull(sut); // Guard assertion
Assert.assertSame(expectedFeed, sut.getFeed());
// Garbage collected tear down
}
}
4. SUT is an acronym for Software Under Test. [xTP]
In JUnit, the assertSame
assertion verifies that the expected and actual references refer to the same object. Going back to our speculation about the intent of the class, it is reasonable to assume that it is important to reference the same feed, but is the same NewsFeed
an overspecification of that condition? For example, what if the implementation chose to enforce the immutability of the initial news feed by cloning the attribute before returning it from the getter, thus ensuring that any changes did not affect the internal state of the ScoreWatcher
’s NewsFeed
? In such a case, testing for the identity of the constructor argument is incorrect. The design intent more likely requires verifying the deep equality of the feed
.
3.137.178.9