有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

列表上洗牌方法的java单元测试

考虑以下类:

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()方法进行单元测试?我正在使用JUnit4进行测试

我有以下选择:

  1. 测试shuffle()以查看它是否不会生成异常
  2. 测试shuffle()并检查牌组是否确实被洗牌

选项2的伪代码示例:

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

这里唯一的罪魁祸首是,当执行为选项2编写的测试(它也继承了选项1)时,如果洗牌没有按预期工作,那么代码执行将永远不会停止

我将如何解决这个问题?是否可能限制JUnit测试中的执行时间


共 (4) 个答案

  1. # 1 楼答案

    目前,您的类是tightly coupled,带有Collections.shuffle函数。静态函数因使测试更加困难而臭名昭著。(除此之外,测试Collections.shuffle没有任何意义;据推测,它工作正常。)

    为了解决这个问题,您可以在类中引入一个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在提供的ICardShuffler上调用shuffle

    最后,您可以将当前功能转移到该接口的实现中:

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

    注意:除了方便测试之外,这个seam还允许您实现新的洗牌方法,而无需修改Deck中的任何代码

  2. # 2 楼答案

    我不明白你的伪代码。。。为什么要使用while循环?只需在甲板上呼叫shuffle。如果抛出异常,测试将失败。如果甲板顺序相同,则测试失败。你还需要更多吗

  3. # 3 楼答案

    我会这样做:

    • 首先,我将使Deck类实现Iterator<Card>
    • 第二,在第一次调用shuffletake之后,我将使Deck类不可变。不允许添加卡片

    然后我会这样做

     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,以确保每次洗牌不会产生相同的结果。这可以通过多种方式实现Here就是一个例子

    仅供参考,我这里用的是火腿肠和番石榴

    Iterators

    Hamcrest

  4. # 4 楼答案

    您还可以编写自己的类CollectionHelper,并使用它来代替集合

    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();
        }
    }