如何测试特定原因引发的异常?

3
什么是确定异常原因并进行修正的正确方式?
考虑以下代码。我尝试解析XML字符串,但有时候,传入的XML不会是顶层元素,这意味着它需要被包含在根元素中。
当出现这种情况时,解析器会抛出一个XmlException异常,但它可能会因为很多原因而抛出。我想捕获特定的异常。
我这样做,我承认这可能不太好:
var doc = new XmlDocument();
try
{
    doc.LoadXml(xml);
}
catch(XmlException e)
{
    if(e.Message.Contains("multiple root elements"))
    {
        doc.LoadXml($"<root>{xml}</root>");
    }
    else
    {
        throw e;
    }
}

这感觉像是一种hack。正确的做法是什么?

你可以使用XmlException.Data来获取更多详细信息。 - KHL
@KhalilLazhar,很奇怪,每次我遇到任何异常时,Data属性都是空字典...我想我从来没有见过带有填充数据属性的系统异常... :( - David Hruška
1
你重新抛出异常的方式会清除堆栈跟踪,这不是一个理想的情况。解析异常消息也不是一个好方法。这里有两篇关于正确处理异常的好文章,我经常链接它们:http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx http://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET 很可能,任何 XML 异常都是外部异常。因此,详细解析它们并不是你的工作。 - Christopher
你确定你正在解决正确的问题吗?XML旨在成为结构化数据。为什么你没有接收到结构化数据,而且为什么不能在生成此输入的任何地方进行修复呢? - Damien_The_Unbeliever
@Deane - 问题在于 - 你试图解决的是错误的问题。而且解决方案不是技术性的。你有人们向你扔垃圾,并声称它是XML,但实际上它是不规范的文本,通常类似于XML,但实际上不符合规范。与你的老板交谈。解释说你被给了一些甚至不能验证,更不用说符合模式的垃圾。你可以尝试坚持下去,但如果你让这种情况继续发生,那么每次你的代码被交付垃圾并失败时,将再次受到责备,因为你已经表现得好像应该由来处理这个问题。 - Damien_The_Unbeliever
显示剩余6条评论
3个回答

3

C# 有一个新功能,使用 when 关键字在 catch 子句中过滤异常:

try
{

} 
catch (XmlException ex) when ( ex.Message.Contains(...) )
{
   //handle
}

您可以使用多个字段来识别异常类型,例如 InnerExceptionStackTraceData。正如 @David Hruška 所建议的那样,HResult 属性也是一个识别异常类型的好地方。 Message 不是用于检查的最佳属性,因为它通常针对内置类型进行本地化,并且因此可能在其他文化设置下看起来不同。

1
我想重申,字符串解析消息不是一个好主意。看看是否有任何字段或包含的异常可以为您提供更多细节。 - Christopher
我不确定它是否解决了问题。由于这是手动检查消息,您没有识别出具体异常。但是,+1表示显示了新功能,我完全忘记了这个 :) - David Hruška
@MartinZikmund,我还会添加HResult,因为我们有时会检查此属性。 - David Hruška
是的,我知道,这只是一种替代方案,但它让我想起了这个很酷的功能,所以作为对自己和OP的提醒 :-)。但是,@David Hruška,你的解决方案更可靠。 - Martin Zikmund
3
消息在其他框架版本中也可能发生变化,这将对您的代码造成重大影响。切勿这样做。 - M.kazem Akhgary

1
你可以尝试按照这里描述的方式为XmlException.HResult制作一个开关:

https://msdn.microsoft.com/en-us/library/system.xml.xmlexception(v=vs.110).aspx

我唯一不确定的是它是否指向特定的异常类型(如XmlException)或特定的异常“消息”。
如果这不能帮助你,我认为你别无选择,只能检查消息。
编辑:还有,如上所述,你应该使用throw;而不是throw e;,因为后者会清除StackTrace。ReSharper也会警告这个问题。

不幸的是,对于.NET Standard和.NET Core,HResult数字在所有平台上都没有标准化,因此这仅在.NET Framework上可靠。我认为现在是向微软施加压力提出更可靠解决方案的时候了。 - NightOwl888

0

有多种方法可以实现你想要做的事情。 不同的测试框架也带来了自己的工具来帮助解决这个问题。 例如,如果你正在使用MSVS测试框架,最简单的选择是仅检查异常类型。在这种情况下,你只需使用“ExceptedExceptionAttribute”标记测试方法,并指定从该方法抛出的预期异常类型,如下所示:

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Action_ThrowsException()
{
    // test logic here
}

还有另一个可用的构造函数,允许指定精确的异常消息作为第二个参数,但很少使用。您可以在MSDN中找到ExpectedExceptionAttribute的文档。

另一个选择是,在其他答案中已经建议的情况下,您可以拥有更多的控制权,这可以封装在帮助程序方法中,如下所示:

private static T Throws<T>(Action action) where T : Exception
{
    try
    {
        action();
        Assert.Fail();
    }
    catch (T ex)
    {
        // This exception was expected.
        return ex;
    }
}

使用这个辅助方法,你现在可以将你的测试方法写成如下形式:
[TestMethod]
public void Action_ThrowsException()
{
    // test logic here
    ArgumentException aex = Throws<ArgumentException>(()=>{
        // Test logic here
    });

    Assert.AreEqual("Expected error message", aex.Message);
}

正如您所看到的,此选项为您提供了更高的灵活性,因为现在您可以明确验证异常的其他方面。

顺便说一下:第二个解决方案是作为xUnit框架的一部分给出的。


这实际上不是 UnitTest 的一部分。这是运行时代码。我处于这样的情况:有时 XML 会有一个根元素,有时则没有,在尝试解析之前我无法知道。 - Deane
感谢提供的额外信息。在这种情况下,您可以选择以下选项之一:
  1. 使用if语句来分支您的逻辑以处理两种情况
  2. 尝试以最可能的方式解析,如果失败,则尝试其他逻辑。 “if”选项(选项#1)更可取,因为它将更快,并且可能需要更少的资源。 这实际上归结为识别XML片段是否具有根元素,是吗?如果是这样,我将能够分享一些代码来完成此操作。
- Artak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接