【问题标题】:Should I create a custom exception type so my code is easier to unit test我是否应该创建一个自定义异常类型,以便我的代码更容易进行单元测试
【发布时间】:2012-01-31 23:46:27
【问题描述】:

在我的单元测试中,我测试了一个预期 RuntimeException 的方法,我想将我的组件抛出的那些与方法中调用的代码抛出的区分开来。

创建自定义异常类型是不必要的,并且如果方法抛出相同的异常类型但出于不同的原因(例如无效参数异常。

似乎告诉他们的唯一方法是消息或错误代码。因为在开发过程中可以更改消息,所以错误代码似乎是唯一可靠的选择。

创建错误代码系统的最佳做法是什么,这样它们就不会与外部包冲突,例如。第三方库?

【问题讨论】:

  • 我有一个特定的案例,我必须区分相同类型的异常,所以我想知道是否有最佳实践系统或有意义且安全的错误代码编号。跨度>

标签: java unit-testing exception


【解决方案1】:

创建自定义异常类型是不必要的,并且不能解决 如果方法抛出相同的异常类型但不同的问题 原因,例如无效参数异常。

为什么你认为没有必要?这是你应该做的。派生您自己的自定义异常类,从您的代码中抛出它们的实例并在外部捕获它们(在您的单元测试中)。可以重复 catch 语句以预期多个不同的异常类:

try {
    // something
} catch (MySpecificException e) {
    // you know that your code threw this
} catch (Exception e) {
    // this is coming from somewhere else
}

【讨论】:

  • 从 InvalidArgumentException 子类化 MyInvalidArgumetException 不会给测试提供哪个参数无效的线索。
  • @user42882:想法是您可以拥有指定此信息的属性。错误代码不是继续的方法,在这里。
  • @user42882 当然,您不会从 InvalidArgumentException 继承。该例外已经具有足够具体的含义。如果您需要该级别的特异性,则可以改为派生 InvalidPortNumber 等异常。
  • 您最终会为每个检查创建一个自定义异常类,例如。 NotExactly64CharsInvalidArgumentException,IntNotIn1000_1200RangeInvalidArgumentException。对于开发人员来说,一条清晰的消息和一个简单的 InvalidArgumentException 就足够了,但是单元测试呢?
  • @user42882 您显然不会在定义特定的自定义异常方面走得太远。对用户和单元测试来说足够/必要的东西略有不同。您可能希望将人类可读的文本消息与您的异常一起包含(以便它们被记录、显示给用户等),但您的单元测试应该只关心被抛出的异常类。不要让你的单元测试依赖于对人类文本消息的解析。
【解决方案2】:

--编辑--

抱歉,我没有看到 java 标签。即使以下示例使用 PHP 结构,这些原则仍然适用。

--原创--

我只在少数非常特殊的情况下使用自定义异常代码,并将这些代码存储在自定义异常类中,该类扩展了默认异常类。它们作为常量存储在类中,因为值并不重要,但上下文很重要。

考虑:

class CoreLib_Api_Exception extends Exception
{
    const EXCEPTION_FORMAT = '%s (%s): %s';

    const CODE_FILE_DNE                 = 100;
    const CODE_DIR_BASE_EQUALS_REMOVE   = 101;

    const CODE_XML_READER_UNABLE_TO_OPEN = 200;
    const CODE_XML_READER_UNABLE_TO_READ = 201;
}

// Example usage
class CoreLib_Api_Reader
{
    protected function getReader()
    {
        $reader = new CoreLib_Api_Xml_Reader();

        if (!@$reader->open($this->getFileUri())) {

            $e = new CoreLib_Api_Exception(sprintf('Could not open %s for parsing', $this->getFileUri()), CoreLib_Api_Exception::CODE_XML_READER_UNABLE_TO_OPEN);

            throw $e;
        }
    }
}

// Calling  code
try {

    $reader = CoreLib_Api_Reader();

    $reader->setFileUri($fileUri);
    $reader->getReader();

} catch (Exception $e) {

    // If code is anything other than open, throw it
    if ($e->getCode() !== CoreLib_Api_Exception::CODE_XML_READER_UNABLE_TO_OPEN) {
        throw $e;
    }

    $e = null;

    $reader = null;
}

通过使用异常代码,我可以检查确定阅读器是否无法打开文件,如果是则忽略异常并继续,否则抛出异常并中断流程。

如果我的一个异常代码与第三方异常代码发生冲突,正如我之前提到的,使用常量并不重要,上下文将决定我要匹配哪个代码。

【讨论】:

  • 使用类常量是我避免幻数并将常量与上下文联系起来的最初冲动。但同样,我更愿意使用标准异常类型,因为它们适用于大多数情况。
  • 是的,各种异常类型也可以正常工作。出于某种原因,我不在乎他们。每次进行 API 调用时都必须检查 7 种不同的异常类型,这对我来说似乎是多余的。我宁愿只处理异常,并测试“针孔”异常代码,如果匹配,则继续。
  • 以前从未听说过这样做。这在设置代码时可能会起作用,但是您将如何检查代码?
  • 一个 PHP 例子: throw new \InvalidArgumetException('The server returned bad response: ' . $responseStr, errHash('bad-server-response'));.在捕获中: if(errHash('bad-server-response') == $e->getCode()) { 在这里处理 ... }
  • 有趣。这具有一定的灵活性,但是它会受到所有此类变量的影响,如果要将“bad-server-response”重命名为其他名称,则必须更改每个代码点。如果您使用类常量,您可以将值更改为您喜欢的任何值,而无需更新代码。
【解决方案3】:

我为预期的 RuntimeException 测试一个方法

我认为这是一个错误。 RuntimeException 应仅用于指示代码本身可以检测到的代码中的错误。测试应该只测试指定的(定义的)行为。但是当某些代码中存在错误时,它的行为是未定义的(谁知道错误可能在哪里或它可能会做什么)。因此,尝试指定某些代码应该抛出的RuntimeExceptions 是没有意义的;这就像指定代码在“存在错误时”的行为方式。向维护程序员(很可能就是你)抛出带有特定消息的特定 RuntimeExceptions 应该被视为一种礼貌。

【讨论】:

  • 这不是真的。异常不一定是由错误引起的。抛出异常可能是被测单元合同的一部分。
  • @OliCharlesworth “抛出异常可能是合同的一部分”:我断言只有 检查 例外是合同的一部分。合约还将提供调用者必须满足的先决条件。未能满足先决条件理想情况下应该会抛出一个合适的、信息丰富的RuntimeException,但合同不应该指定那是什么。甚至它肯定会发生。
  • @Raedwald 断言所有你想要的。这并不意味着其他人都同意。
猜你喜欢
  • 2014-03-25
  • 1970-01-01
  • 1970-01-01
  • 2016-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多