6

Consider the following class:

public class Deck {
    private final Queue<Card> queue = new LinkedList<>();

    public Deck() { }

    public Deck(final Collection<Card> cards) {
        Objects.requireNonNull(cards);
        queue.addAll(cards);
    }

    public void add(final Card card) {
        Objects.requireNonNull(card);
        queue.add(card);
    }

    public void addAll(final Collection<Card> cards) {
        Objects.requireNonNull(cards);
        queue.addAll(cards);
    }

    public void shuffle() {
        Collections.shuffle((List<Card>)queue);
    }

    public Card take() {
        return queue.remove();
    }
}

How would I unit test the shuffle() method? I am using JUnit 4 for testing.

I have the following options:

  1. Test shuffle() to see that it does not generate an exception.
  2. Test shuffle() and check if the deck actually gets shuffled.

Example pseudocode of option 2:

while notShuffled
    create new Deck
    take cards and check if they are shuffled

The only culprit here is that when executing a test written for option 2 (which also inheritly includes option 1), if the shuffling does not work as intended, then the code execution will never stop.

How would I solve this issue? Is it possibly to limit the execution time in JUnit tests?

skiwi
  • 66,971
  • 31
  • 131
  • 216
  • " then the code execution will never stop." <-- uuh... Code of the test? – fge Apr 16 '14 at 12:24
  • 2
    I think an additional problem you're not considering is if the shuffle method keeps the order of the list the same. I guess it comes down to what are the expectations of your shuffle method. As for a method never completing, that's theoretically a problem with any method under test. I'm not sure you need to explicitly worry about that. – Stealth Rabbi Apr 16 '14 at 12:25
  • @fge Added pseudocode for that now, didn't realise it was not clear. – skiwi Apr 16 '14 at 12:26
  • @StealthRabbi (Clarified it in edit) I am considering that *my* `Deck.shuffle` method keeps the list the same, I am however not considering that the `Collections.shuffle` implementation may be broken. – skiwi Apr 16 '14 at 12:27
  • @Vakh Your answer was entirely valid, IMO. Fancy undeleting to receive my upvote? – Duncan Jones Apr 16 '14 at 12:33
  • How do you define shuffled? `Collections.reverse` also "shuffles" in that it changes the order of all cards. What are you worried about in your function, that it does nothing at all or that the shuffling is not pure random? (You can use statistical randomness tests to verify how random the shuffle is, but it seems overkill) – Erwin Bolwidt Apr 16 '14 at 13:05
  • @ErwinBolwidt I am worried about it doing nothing at all, or worse, throwing away cards, or even worse, adding cards. – skiwi Apr 16 '14 at 13:07
  • Too bad you use JUnit and not TestNG... I don't know for JUnit but TestNG _does_ implement timeout for tests – fge Apr 16 '14 at 13:09
  • @skiwi Do you have an `equals` and `hashCode` method defined on your `Card` class? (Ones that you trust and tested) Once you have, you can use `HashSet`, and the `contains` method on any `Collection` to make it a lot easier to check those things. – Erwin Bolwidt Apr 16 '14 at 13:42

4 Answers4

7

Currently, your class is tightly coupled with the Collections.shuffle function. Static functions are notorious for making things more difficult to test. (On top of that, there's no point in you testing Collections.shuffle; presumably, it works correctly.)

In order to address this, you can introduce a seam in your class for this shuffling functionality. This is done by extracting the shuffle function into a role (represented by an interface). For example:

public interface ICardShuffler {
    void shuffle(List<Card> cards);
}

Then, your Deck class can be configured to keep a reference to an instance of some implementation of this interface, and invoke it when necessary:

public class Deck {
    private final Queue<Card> queue = new LinkedList<>();

    private ICardShuffler cardShuffler;

    public Deck(ICardShuffler cardShuffler) {
        this.cardShuffler = cardShuffler;
    }
    ...
    public void shuffle() {
        cardShuffler.shuffle((List<Card>)queue);
    }
    ...

This allows your unit test to use a test double, like a mock object, to verify that the expected behavior occurs (i.e., that shuffle invokes shuffle on the provided ICardShuffler).

Finally, you can move the current functionality into an implementation of this interface:

public class CollectionsCardShuffler implements ICardShuffler {
    public void shuffle(List<Card> cards) {
        Collections.shuffle(cards);
    }
}

Note: In addition to facilitating testing, this seam also allows you to implement new methods of shuffling without having to modify any of the code in Deck.

Lilshieste
  • 2,744
  • 14
  • 20
  • 1
    Good answer WRT design. However, I don't believe that simply verifying that the mock was called in the `shuffle` method is enough here. If the list passed to the shuffler is different than the list iterated in `take` the correct contact between the two methods would not hold. I believe the better solution is to use default behavior in `shuffle` and test `shuffle's` effect on `take`. This is based on the idea that the only requirement of `shuffle` is to change the order of cards returned by `take`. You cannot test this contact by testing `shuffle` alone. – John B Apr 16 '14 at 15:15
  • @JohnB "If the list passed to the shuffler is different than the list iterated in `take`..." The unit test for `shuffle` should verify that the correct list is being passed to the `ICardShuffler.shuffle` method. (In fact, that's the *only* thing that the test would need to verify.) If that unit test fails, then the contract between the two methods is invalid (because, by definition, `shuffle` would be considered broken). – Lilshieste Apr 16 '14 at 15:33
  • Disagree. Seems like you are wanting to test the implementation of `shuffle` rather than the contract between the two methods: `suffle` & `take`. I could, for example, pass a different list to the shuffler if I wanted to refactor the code to maintain the original list of cards if I wanted to start a new game. – John B Apr 16 '14 at 15:35
  • Actually, I'm explicitly trying *not* to test the implementation of `Collections.shuffle`. :-) In any case, if you pass a different list to `ICardShuffler.shuffle` it breaks the expected behavior of `take` just as much as if you passed a different list to `Collections.shuffle`. – Lilshieste Apr 16 '14 at 15:39
  • Not if the list passed to `shuffle` is the same list used in `take`. There is no requirement that this be the same list instance held by in the class's field. Also, given that the field private, how would you verify that the right list is passed? What list instance would you validate against? Or if you validate the List's contents, there is no guarantee that the list is the same list that would be used in `take`. Again, the ONLY requirement is the contact between `take` and `shuffle`. All else is implementation that could be refactored without breaking the contact or test. – John B Apr 16 '14 at 15:47
  • You bring up a good point about the test needing access to a private field of `Deck` in order to verify the correct list is used. Coming from a TDD perspective, I would consider this a suggestion from my test that the `Deck` should not necessarily be responsible for instantiating the list of cards (i.e., an instance could be passed into a constructor). Regarding the contract between take and shuffle: the only contract is that they operate on the same list of `Card`s. It's entirely acceptable to invoke `take` without invoking `shuffle`, and vice-versa. – Lilshieste Apr 16 '14 at 15:56
  • So you are suggesting that in order to test this class you must verify that the correct INSTANCE of the list is passed to the shuffler and the correct INSTANCE of the list is used to iterate in `take`. And to do this you are going to lock down the class to require it to take a List instance. Just test the behavior. Peace, out. – John B Apr 16 '14 at 16:10
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/50801/discussion-between-lilshieste-and-john-b) – Lilshieste Apr 16 '14 at 16:16
1

I don't understand your pseudocode... why use a while loop? Just call shuffle on a deck. If an exception is thrown, test fails. If the deck is in the same order, test fails. Do you need more than that?

jgitter
  • 3,396
  • 1
  • 19
  • 26
1

You could also write your own class CollectionsHelper and use that instead of Collections.

public class CollectionsHelper {
    private CollectionsHelper(){}
    private static boolean isTest = false;
    private static int n = 0;
    public static void shuffle(List<?> l){
        if(!isTest) Collections.shuffle(l);
        else Collections.shuffle(l, new Random(n));
    }
    public static void setTest(){
        isTest = true;
        n = 0;
    }
    public static boolean isTest(){
        return isTest;
    }
    public static void setSeedForTest(int seed){
        n = seed;
    }
}

At the beginning of each test you can call CollectionsHelper.setTest() to use the deterministic shuffle. Your class would look like:

public class Deck {
    private final Queue<Card> queue = new LinkedList<>();

    public Deck() { }

    public Deck(final Collection<Card> cards) {
        Objects.requireNonNull(cards);
        queue.addAll(cards);
    }

    public void add(final Card card) {
        Objects.requireNonNull(card);
        queue.add(card);
    }

    public void addAll(final Collection<Card> cards) {
        Objects.requireNonNull(cards);
        queue.addAll(cards);
    }

    public void shuffle() {
        CollectionsHelper.shuffle((List<Card>)queue);
    }

    public Card take() {
        return queue.remove();
    }
}
lolloz98
  • 111
  • 5
0

I would do something like the following:

  • First, I would make the Deck class implement Iterator<Card>.
  • Second, I would make the Deck class immutable after the first call to shuffle or take. ie. don't allow the addition of cards.

Then I would do something like this...

 static List<List<Card>> previousDecks = new ArrayList<>();
 static List<Card> inputList = createListOfCards();

 static{
      privousDecks.add(inputList);
 }

 @Test
 public void testShuffle(){
    Deck deck = new Deck(inputList);
    deck.shuffle();

    List<Card> shuffled = new ArrayList<>();
    Iterators.addAll(shuffled, deck);

    assertThat(shuffled, 
       IsIterableContainingInAnyOrder.containsInAnyOrder(inputList));

    for (List<Card> previouslySeen : previousDecks){
        assertThat(shuffled,
            CoreMatchers.not(
            IsIterableContainingInOrder.contains(previouslySeen )));
    }
    previousDecks.add(shuffled);
 }

I would then find a way of running testShuffle multiple times to ensure that the shuffle does not produce the same result each time. This can be done multiple ways. Here is an example.

As an FYI, I am using Hamcrest and Guava here.

Iterators

Hamcrest

Community
  • 1
  • 1
John B
  • 32,493
  • 6
  • 77
  • 98