【问题标题】:How to use Junit for testing long arrays?如何使用 Junit 测试长数组?
【发布时间】:2015-08-04 18:22:53
【问题描述】:

我刚开始进行单元测试 (junit)。我看过 Roy Osherove 的几个视频,他在视频中谈到了良好的做法。由于我对这个主题很陌生,我感到完全迷失了。下面是我的代码的一个简短示例,然后是我到目前为止的测试方式以及我将如何创建合适的 junit 测试。本质上,我正在创建一个包含 52 张卡片的卡片组。 当我创建一个新的“牌组”对象时,我想测试牌组是否包含所有 52 张牌。

我的代码可能有一些错误,但更多的是关于逻辑(见最后的问题)。

生产代码


想象一下,我想创建一个普通的牌组,它由 4 个花色和 13 个等级组成。我们还假设我创建了一个“card”类和一个“deck”类,其中包含一个实例变量,它是一个由 52 张卡片组成的数组。

“卡片”类:

public class card {

private String[] suits = {"h", "s", "c", "d"};
private String[] ranks = {"2","3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"};

private String suit, rank;


public card (int rank, int suit) {
    this.suit = this.suits[suit];
    this.rank = this.ranks[rank];
}

public String getRank () {
    return rank;
}

public String getSuit () {
    return suit;
}
}

“甲板”类:

public class deck {

private card[] deckCards = new card[52];

public deck() {

    int i = 0;

    for(int suit = 0; suit<4; suit++) {
        for(int rank=0; rank<13; rank++) {
            this.deckCards[i] = new card(rank,suit);
            i = i+1;
        }
    }

}

public card[] getDeckCards() {
     return deckCards;
}
}

测试


那么,如果我想创建一个 junit 测试,我的测试应该如下所示吧?

public class Main {

public static void main(String[] args) {

    deck deck = new deck();
    int counter = 0;

    for (int i = 0; i<52; i++) {

        if (deck.deckCards[i] != null) counter++;


    }

    System.out.println("Cards in deck: " + counter);

    for (int i = 0; i<52; i++) {
        if (deck.deckCards[i] != null) System.out.println(deck.deckCards[i].getRank() + deck.deckCards[i].getSuit());
    }



}
}

结果我会得到这样的结果:

  • 一副牌:52张
  • 2h,3h,4h,5h,6h,7h,8h,9h,Th,Jh,Qh,Kh,Ah, 2s,3s,4s,5s,6s,7s,8s,9s,Ts,Js,Qs ,Ks,As, 2c,3c,4c,5c,6c,7c,8c,9c,Tc,Jc,Qc,Kc,Ac, 2d,3d,4d,5d,6d,7d,8d,9d,Td,Jd,Qd,Kd,Ad

现在我可以查看所有卡片,我会发现一切都如我所愿。 显然这不是你测试的方式..

现在,如果我想创建一个 Junit 测试,我会执行以下操作:

@Test
public void testDeck() {

    // create the expected value; which is 52 cards
    card[] expected = {new card(0,0),
                       new card(1,0),
                       new card(2,0),
                       ...          ,
                       new card(12,0),
                       new card(0,1),
                       ...          ,
                       new card(12,1),
                       new card(0,2),
                       ...          ,
                       new card(12,2),
                       new card(0,3),
                       ...          ,
                       new card(12,3)}



    // instantiate the deck
    deck carddeck = new deck();
    card[] actuals = carddeck.getCardDeck();

    //assert
    assertArrayEquals(expected, actuals);
}

现在,我有几个问题:

  1. Roy 说我们不应该在测试方法/案例中使用任何逻辑。显然我确实使用逻辑来创建我的“预期”,即卡片类的构造函数。但是我怎样才能使用硬编码值呢?当我确实有预期的字符串或 int 时很容易,但如果我确实有更复杂的结构,比如这个对象呢?
  2. 如果我的卡片组有 1000 张卡片会怎样。那么手动创建一个包含 1000 个字段的数组是不可能的。但是我怎么能测试它呢?

非常感谢你帮助我:)

【问题讨论】:

  • 正如 Carl 解释的,使用类 HashSet

标签: java arrays unit-testing testing junit


【解决方案1】:

我会问自己的问题是:

  • 如果我更改算法(在本例中为deck 的构造函数),可能会出现什么问题?
  • 算法的关键规则是什么?
  • 如果出现问题,哪些测试会立即失败?

在您的示例中,我将测试以下期望:

  • 牌组由 52 张牌组成,牌组中没有两张牌是相等的(将所有牌添加到 Set 并查看其中是否有 52 张牌)。
  • border case 牌在牌组中 - 可能是 2h、2s、2c、2d、Ah、As、Ac、Ad。
  • 如果这是一个商业案例,并且某些套装和/或等级比其他的更重要,我会为它们添加一些额外的测试。

【讨论】:

    【解决方案2】:

    junit 中的理论跑者可以更好地完成这项工作。 Theories runner 会自动生成你的测试数据的各种组合。

    • 使用 TestedOn 注释。

      @RunWith(Theories)
      public class DeckTest {
          @Theory
          public void testCard() {
                   @TestedOn(0, 1, 2, 3) int suit
                   @TestedOn(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) int rank) {                     
              Card card = new Card(rank, suit);
              Deck cardDeck = new Deck();
              Card[] cards = cardDeck.getDeckCards();
              assertThat(cards, hasItem(card)); // you should static import assertThat and hasItem
          }
      }
      
    • 使用 DataPoints 和 FromDataPoints 注释

      @RunWith(Theories)
      public class DeckTest {
           @DataPoints("suits")
           public final static int[] SUITS = {0, 1, 2, 3};
           @DataPoints("rnaks")
           public final static int[] RANKS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
      
           @Theory
           public void testCard(
                           @FromDataPoints("suits") int suit,
                           @FromDataPoints("ranks") int rank) {
               Card card = new Card(rank, suit);
               Deck cardDeck = new Deck();
               Card[] cards = cardDeck.getDeckCards();
               assertThat(cards, hasItem(card));
           }
      
      }
      

    【讨论】:

      【解决方案3】:

      您知道您需要通过此方法获得 52 张不同的卡片,并且您知道只有 52 张可能的卡片。因此,您可以创建一个比较花色和等级的 equals() 方法 - 当然,测试该方法 - 然后您对 getCardDeck 的测试可以将数组中的所有卡片放入一组中。如果您的集合包含 52 张卡片,那么您的方法是有效的,因为只有 52 种可能性,并且一个集合只能包含任何卡片的一个副本,因此如果您的 52 元素数组包含任何副本,那么集合会更小。

      【讨论】:

      • 感谢您的回答,但这对我没有帮助。请考虑我对编程和测试很陌生。最后似乎也错过了我的问题;-)
      • 很抱歉,但我真的相信这确实回答了您的问题。您如何测试已退回 52 张不同的卡片?创建一个集合,将你的牌组中的所有卡片放入其中,如果该集合有 52 个元素,那么你的代码可以工作;如果没有,则您的代码无法正常工作。就是这么简单。
      • 嗯,问题是我的套牌构造器是否首先创建了 52 张牌套。我知道这是非常简单的代码,可能根本没有真正得到测试。我用它作为跳板来寻找一个更抽象问题的答案。如何在不使用逻辑的情况下测试大型结构(意味着任何 if、循环,基本上是任何生产代码)?显然我需要硬编码我的最终结果。这就是 Roy Osherove 的建议。
      • 我认为教条式地避免测试中的逻辑是错误的。需要对最终结果进行硬编码并不是“显而易见的”。当您有一种清晰、简单的方法来验证代码是否符合您的要求时(即使它包含逻辑),请使用它。
      猜你喜欢
      • 2015-09-08
      • 2012-10-24
      • 2019-01-22
      • 1970-01-01
      • 2020-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多