Skip to content

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:

git checkout lab1-start

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

  1. Create a new test class called CardDisplayTest.

    package com.r2ha.blackjack;
    
    import org.junit.jupiter.api.Test;
    
    import static org.assertj.core.api.Assertions.assertThat;
    
    class CardDisplayTest {
    }
    
  2. Create a test method displayTenAsString that creates a card with a Rank of 10, with any Suit, calls the display() method, and asserts that the result is equal to an empty String, e.g.:

    assertThat(card.display())
        .isEqualTo("");
    

    When you run all of the tests (as you should try to do!) this test will fail.

  3. 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.

  1. Create another test method displayNonTenAsString that creates a card with any non-ten Rank card, e.g., 7 (Rank.SEVEN).

  2. As above, assert that the display is an empty string, run the test and watch it fail.

  3. Copy the actual output into the test and make sure it passes.

  4. 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.

  1. Create new public methods on Card for querying the Rank (with a rank() query method) and Suit (with a suit() query method) and replace direct references to the instance variables in the display() 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.

  2. Make the display() method in Card into a static method. Use the automated refactoring "Make Static"1 to do this. Have the Card be passed in as a parameter. The result should be:

    public static String display(Card card) {
    
  3. 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.


  1. To bring up the Refactor This... menu, press Ctrl T on Mac, or Ctrl Alt Shift T on Windows.