【问题标题】:JAVA: JUnitTest, testing method that throws two exceptionsJAVA:JUnitTest,抛出两个异常的测试方法
【发布时间】:2013-03-09 22:36:29
【问题描述】:

我正在测试一种引发两种不同异常的方法。这是我的标题:

@Test (expected = A8InvalidInputException.class)
public void testGuessCharacter() throws A8InvalidInputException, A8AlreadyGuessedException { ... }

主体有两个 try/catch 块(对 SO 的搜索会导致一篇帖子说这是您测试抛出异常的方式),一个用于每个异常。在我看来,我应该把它分成两种测试方法,特别是因为我只能有一个 expected 属性。但是,当我这样做时,应该测试 A8InvalidInputException 的方法需要 A8AlreadyGuessedException 的 try/catch,而应该测试 A8AlreadyGuessedException 的方法需要 A8InvalidInputException 的 try/catch。我不确定如何编写这个测试。这是我正在尝试测试的方法:

/**
 * This method returns whether a specified character exists in the keyPhrase field
 * @param guess  a character being checked for in the keyPhrase field
 * @return  returns whether a specified character exists in the keyPhrase field
 * @throws A8InvalidInputException  if a non-valid character is passed as input to this method
 * @throws A8AlreadyGuessedException  if a valid character which has already been guessed is passed as input to this method
 */
public boolean guessCharacter(char guess) throws A8InvalidInputException, A8AlreadyGuessedException
{
    if(isValidCharacter(guess))
    {
        guess = Character.toLowerCase(guess);

        if(guessedCharacters.contains(guess) )
        {
            throw new A8AlreadyGuessedException("" + guess);
        }
        else
        {
            guessedCharacters.add(guess);
            if(keyPhrase.contains("" + guess))
                return true;
            else
            {
                numberOfGuessesLeft--;
                return false;
            }
        }       
    }
    else
    {
        throw new A8InvalidInputException("" + guess);
    }
}

【问题讨论】:

  • 反正方法太长了。
  • 戴夫,我的导师提供了。我没写。除此之外,根据 Java 编码指南/样式指南,我认为 30 行是业界公认的最大值。
  • "行业接受的最大值?"从来没有听说过这样的事情。更重要的是,它具有足够高的圈复杂度,让我感到抽搐:条件嵌套太深而无法方便推理,并且在不需要的地方嵌套。重复的逻辑,因为当其他一切都需要一个字符串时有一个字符。简单的扁平化(大约gist.github.com/davelnewton/258761cbf3143da4288c)解决了这个问题。适当的重构使测试变得如此微不足道,几乎变得有趣。
  • 嗯,测试是为了练习,所以一点也不复杂。我有另一位讲师教我们在将输入作为参数传递之前验证输入。它变得令人困惑。
  • 发生数据验证的位置在很大程度上无关紧要。但你错了;该方法处于圈复杂度的上限(10 是典型的默认最大值;我在 ~5 左右感到抽搐)。方法应该做一件事,并且完全做到。这种方法明显不止一件事,增加了认知负荷而没有相应的好处。因此,测试变得更加困难。

标签: java exception junit junit4


【解决方案1】:

只需在 throws 子句中添加两个异常:

@Test (expected = A8InvalidCharacterException.class) 
public void testInvalidCharacterScenario() throws A8InvalidInputException, A8AlreadyGuessedException { ... }

@Test (expected = A8InvalidInputException.class) 
public void testInvalidInputScenario() throws A8InvalidInputException, A8AlreadyGuessedException { ... }

然后,如果一个测试抛出另一个异常(意外的异常),那么您的测试将自动失败。

【讨论】:

    【解决方案2】:

    一个方法在运行时只能抛出一个异常。这就是为什么只能有一个预期属性。

    您可能希望拥有三个测试用例:一个当方法抛出一个异常时,一个当该方法抛出另一个异常时,一个当该方法根本没有抛出任何异常时。

    不要在您的@Test 方法中添加任何try/catch 语句,只需声明它们会引发异常。

    【讨论】:

    • 艾蒂安,好吧,这是有道理的,但你是什么意思,只是声明他们抛出异常?我这周刚刚学习了 JUnitTesting,所以这对我来说还不是很直观。谢谢。
    • 我的意思是把 throws A8InvalidInputException, A8AlreadyGuessedException 放在测试方法的标题中,但我现在看到你做到了。那么为什么你需要在 body 中使用 try/catch 块呢?
    • 我在 SO 上看到了 post,说 try/catch 块是实现此目的的方法。否则,我不确定如何编写测试。
    • 您链接的帖子是关于更复杂的情况。在您的情况下,您不需要(也不应该)在您的测试方法中使用 try/catch 块。
    • 好的,根据我上面提交的内容,你能给我一个关于身体应该是什么样子的线索吗?
    【解决方案3】:

    是的,你应该把它分成两个单元测试。一个具有无效输入以触发A8InvalidInputException,另一个具有“已经猜到”输入以触发A8AlreadyGuessedException

    【讨论】:

      【解决方案4】:

      考虑一下,为了让您的测试更简单一些,将它们分成两个不同的部分 - 分别测试 isValidCharacter 和测试 guessCharacter

      假设isValidCharacter(guess) 将失败,如果您收到一个无效的猜测,我认为在该方法中抛出A8InvalidInputException 将是理想的。

      public boolean isValidCharacter(char guess) throws A8InvalidInputException {
          // have your logic to check the guess, and if it's invalid, throw
      }
      

      那么你需要做的就是测试那个特定的方法,看看它是否在虚假输入上抛出异常。

      @Test (expected = A8InvalidInputException.class)
      public void testIsValidCharacterWithInvalidCharacter() {
          // write your test here.
      }
      

      接下来,您可以将您的方法更改为只关​​心 isValidCharacter 方法的快乐路径,因为如果您不返回布尔值,则会引发异常。

      最后,您只关心guessCharacter 的测试是否会抛出A8AlreadyGuessedException

      @Test (expected = A8AlreadyGuessedException.class)
      public void testGuessCharacterWithAlreadyGuessedValue() {
          // write your test here.
      }
      

      【讨论】:

      • 诚,谢谢,但我无法更改源代码。我的导师提供了。我也会在这里编写单独的方法(也许在将其作为参数传递之前测试有效性?)因为我认为一种方法只应该做一件事,但是嘿,我是学生。我知道什么?
      • 嗯,方法可以做很多事情,但是当你测试时,你应该只测试一件事。我仍然强烈认为isValidCharacter 应该抛出异常,因为它是代码中唯一负责断言无效输入的部分。同样,它是关于限制您必须通过测试达到的表面积 - 它使测试编写工作更容易,并且产生的测试不那么脆弱。
      猜你喜欢
      • 2013-02-15
      • 1970-01-01
      • 1970-01-01
      • 2014-04-18
      • 2021-02-08
      • 2018-12-10
      • 1970-01-01
      • 2018-05-19
      相关资源
      最近更新 更多