考虑以下课程:
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();
}
}
我如何对shuffle()
方法进行单元测试?我正在使用JUnit 4进行测试。
我有以下选择:
shuffle()
以确定它不会产生异常。shuffle()
并检查牌组是否实际上被洗牌。选项2的伪代码示例:
while notShuffled
create new Deck
take cards and check if they are shuffled
这里唯一的罪魁祸首是,当执行为选项2编写的测试(也继承包含选项1)时,如果改组不能按预期工作,那么代码执行将永远不会停止。
我该如何解决这个问题?是否可能限制JUnit测试中的执行时间?
答案 0 :(得分:3)
目前,您的班级为tightly coupled且具有Collections.shuffle
功能。静态函数因使事情更难以测试而臭名昭着。 (最重要的是,你测试Collections.shuffle
没有意义;大概是正确的。{/ p>
为了解决这个问题,您可以在班级中引入seam来实现此改组功能。这是通过将shuffle
函数提取到角色(由接口表示)来完成的。例如:
public interface ICardShuffler {
void shuffle(List<Card> cards);
}
然后,您的Deck
类可以配置为保持对此接口的某些实现的实例的引用,并在必要时调用它:
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);
}
...
这允许您的单元测试使用test double(如模拟对象)来验证预期的行为是否发生(即,shuffle
在提供的{{1}上调用shuffle
}})。
最后,您可以将当前功能移动到此接口的实现中:
ICardShuffler
注意:除了便于测试之外,此接缝还允许您实现新的混洗方法,而无需修改public class CollectionsCardShuffler implements ICardShuffler {
public void shuffle(List<Card> cards) {
Collections.shuffle(cards);
}
}
中的任何代码。
答案 1 :(得分:1)
我不明白你的伪代码...为什么要使用while循环?只需在甲板上打电话。如果抛出异常,则测试失败。如果卡组的顺序相同,则测试失败。你还需要更多吗?
答案 2 :(得分:1)
您也可以编写自己的类 CollectionsHelper 并使用它代替 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;
}
}
在每次测试开始时,您可以调用 CollectionsHelper.setTest()
以使用确定性随机播放。
你的类看起来像:
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();
}
}
答案 3 :(得分:0)
我会做以下事情:
Deck
类工具Iterator<Card>
。 Deck
或shuffle
后,我会使take
类不可变。即。不允许添加卡片。然后我会做这样的事情......
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);
}
然后我会找到一种多次运行testShuffle
的方法,以确保每次shuffle不会产生相同的结果。这可以通过多种方式完成。 Here就是一个例子。
作为一个FYI,我在这里使用Hamcrest和Guava。