Characterization Testing to Separate Concerns in the Card Class¶
Do This First...
You'll want to start at the lab1-start
tag of the repository on GitHub, so using git:
Background¶
Characterization tests, sometimes called Approval tests, assume that the code works as desired, so any tests written can make that assumption as well. They often leverage textual output vs. the typical property/state testing we do in unit tests. We use them when we don't have good (or any) test coverage, and don't have any kind of specification that would let us write tests from scratch. Instead, we:
- Write a test without specifying the expected outcome
- Let the test fail
- Copy whatever the actual outcome was
- Use that actual outcome as the expected outcome
- And the test should pass
Goal¶
We want to extract the display methods in Card
into a new class to separate the console output concerns from the Card
's behavior.
However, those display methods don't have test coverage, so we'll need to write some tests before we refactor.
How Will We Know We're Done?
We know we're done when we have removed all System.out.print
statements from the Card
class, as well as any "display" methods that return a String.
Scenarios¶
We'll start with a test for the Card#display()
method.
We know that the main difference in display behavior is for cards with a rank of 10 (because they take up two characters) and all other cards (which only take a single character), so we'll create two characterization tests to cover both scenarios.
A. Characterize "Ten" Card Display¶
-
Create a new test class called
CardDisplayTest
. -
Create a test method
displayTenAsString
that creates a card with a Rank of 10, with any Suit, calls thedisplay()
method, and asserts that the result is equal to an empty String, e.g.:When you run all of the tests (as you should try to do!) this test will fail.
-
To make the test pass, copy the "actual" result from the test output into the assertion and run it again. It should now pass. You now have characterized the behavior for cards with the Rank of 10 (
Rank.TEN
).Watch out for ESCape Characters
Be careful when copying the actual result as there are ESC (escape) characters embedded in the output. In IntelliJ, use the
click to show difference
to show the hidden escape characters, which makes it easier to copy from.
B. Characterize Non-Ten Card¶
Now we do the same thing, but for a single-character Rank.
-
Create another test method
displayNonTenAsString
that creates a card with any non-ten Rank card, e.g., 7 (Rank.SEVEN
). -
As above, assert that the display is an empty string, run the test and watch it fail.
-
Copy the actual output into the test and make sure it passes.
-
Make sure all other tests pass before moving on. (You're always running all of the tests, right?)
C. Refactor Card Display¶
Now that we have some test coverage, let's move the console-based display code out of Card
and into a new ConsoleCard
class.
-
Create new
public
methods onCard
for querying the Rank (with arank()
query method) and Suit (with asuit()
query method) and replace direct references to the instance variables in thedisplay()
method with these query methods.Extract Method
Use the Extract Method automated refactoring to do this: Cmd Option M (Mac) or Ctrl Alt M (Windows/Linux). Only replace the references inside the
display()
method, leave all other references as they are. -
Make the
display()
method inCard
into a static method. Use the automated refactoring "Make Static"1 to do this. Have theCard
be passed in as a parameter. The result should be: -
Use the "Move Static Members" refactoring (F6 key) to move the display method to a new class
ConsoleCard
(in the same package:com.r2ha.blackjack
).Tests Should Pass
Make sure all the tests pass when you are done.
If you haven't already done a git commit, now's a good time to do so.
Think About...
We covered the (tiny) decision/display logic around a 10 card and a non-10 card. What display logic did we not cover?
Click for an Answer
The card's display color (red or black) could use some tests. It turns out there's already a test that covers this in the CardTest class. You can write some more tests to fully cover all the cases. Leave the existing tests where they are, as we'll come back to them in a later lab.
You're Done
That's the end of this lab. Well done!
If you want to compare your solution with mine, you can checkout from the repository on GitHub using the tag lab1-solution
.
-
To bring up the
Refactor This...
menu, press Ctrl T on Mac, or Ctrl Alt Shift T on Windows. ↩