【问题标题】:How can I check if any exception has already been thrown?如何检查是否已经抛出任何异常?
【发布时间】:2013-07-25 18:58:22
【问题描述】:

我正在为一项服务进行一些测试自动化,并想出了一个巧妙的方法来将一些常见的设置和验证汇总到一个“会话”类中。

从概念上讲,测试用例可能如下所示:

using (var managerSession = new Session(managerRole))
{
    // A manager puts some items in warehouse
}

using (var employeeSession = new Session(employeeRole))
{
    // An employee moves items from warehouse to store
}

using (var customerSession = new Session(customerRole))
{
    // A customer can buy items from the store
}

在 Session 对象构造函数中,我使用每个角色的适当身份验证等设置到我正在测试的服务的连接,并且在会话 Dispose() 方法中,我有一个公共验证块,例如,检查没有服务器在会话生命周期内引发了-side 错误或警告。

当然,这有点滥用 IDispose 模式,如果 using 块中的测试代码抛出异常并且验证块也抛出异常,则第二个异常将掩盖第一个异常。

从概念上讲,如果我们有这种情况:

using (var managerSession = new Session(managerRole))
{
    Assert.IsTrue(managerSession.DoJob(), "Manager did not do his job");
}

...并且断言失败或对 managerSession.DoJob() 的调用引发异常,那么我希望 Session Dispose() 方法跳过验证块,即

public void Dispose()
{
    if (NoExceptionThrown())
    {
         Assert.IsFalse(this.serviceConnection.HasErrors(), "Service connection has errors");
    }

    this.serviceConnection.Dispose();
}

...这样,如果测试方法实际上因“经理没有完成他的工作”而失败,则该测试方法永远不会因“服务连接有错误”而失败

我的问题是:这里是否有可能实现“NoExceptionThrown()”方法?是否有一些可以检查的全局属性,或者隐藏在 Thread.CurrentThread 中的东西可以使用?

更新:

我的问题是不是如何重构这个:-)

我当然可以改用这种模式:

Session.ForRole(managerRole, (session) => { /* Test code here */ });

静态方法 ForRole() 定义如下

public static void ForRole(Role r, Action<Session> code)
{
    var session = new Session(r);
    try 
    {
        code(session);
        Assert.IsFalse(session.serviceConnection.HasErrors());
    }
    finally 
    {
        session.Dispose();
    }
}

但我很好奇是否存在某种方式来获取如上所述的异常状态。

【问题讨论】:

  • 两件事。首先,是的,这是在滥用IDisposable。使用IDisposable 表示“我拥有一个应该尽快释放的非托管资源”。让它意味着别的东西会使你的代码难以阅读。其次,您关心任何异常,还是只关心未捕获异常?假设测试代码抛出异常,捕获它并正常完成。应该标记吗?
  • 啊,我只关心未捕获异常。我会更新问题...
  • 至于滥用 IDisposable:我实际上更担心错过每个会话的通用验证,而不是潜在的错误屏蔽,所以这个问题更像是一个“很高兴拥有”的功能。实际的测试代码块是 10 到 100 行 UI 自动化代码,我们已经发现了未检测到的缺陷,因为其中一个块中遗漏了一个验证步骤(或报告在错误的块中)。

标签: c# exception dispose


【解决方案1】:

如果IDisposable.Dispose 的重载采用Exception 类型的参数来指示在与其清理关联的finally 上下文中挂起的异常(如果有),那将会很有帮助。虽然Dispose 方法通常不应该关心异常的细节,但在Dispose 方法期间可能会出现条件,应该报告给调用者。从Dispose 抛出的任何异常都将替换在调用者的finally 上下文中挂起的任何异常,因此如果Dispose 方法可以在替换之前封装挂起的异常会很有帮助。不幸的是,不存在这样的功能,我不希望添加任何功能。

虽然有一些技巧可以用来实现类似预期的效果,但唯一语义正确的方法是将异常作为Dispose 方法的参数。任何其他方法的问题是Dispose 可能在多个嵌套的finally 块中运行,其中一些具有未决异常,而另一些则没有;检查执行上下文以确定嵌套最深的 finally 块的状态的代码可能会失败,如果该块不是保护被处置对象生命周期的块。

【讨论】:

    【解决方案2】:

    嗯,一个确定但丑陋的方法是在你的类上设置一个布尔值(默认为 false)。

    using (var managerSession = new Session(managerRole))
    {
        Assert.IsTrue(managerSession.DoJob(), "Manager did not do his job");
        managerSession.NoExceptionThrown = true;
    }
    

    我认为使用 IDisposable 模式不会比这更干净。

    在这种情况下,try...catch...finally 可能会更好,因为您可以直接访问异常对象,您可以测试它是否为空(这是一个词吗?),甚至可能保存以备后用。你不应该失去任何东西,因为using 只是最后尝试捕获的糖。

    【讨论】:

    • 是的,空性是一个完美的词。
    • 更严肃的说,在 Dispose 方法中设置一个控制验证块的属性实际上并不是一个好主意。如果我们无论如何都要在每个 using 块上添加一些东西,那么最好将现有的验证代码重构为一个单独的方法并在关闭范围之前调用它
    • @Christoffer 是的,我认为您正在尝试通过使用来做一个圆形插入方孔的事情。正如 supercat 指出的那样,正确的方法是传递异常对象或指示是否发生异常以进行处理。所以,我想你可以添加适当的方法,只是放弃使用并自己调用 Dispose(exception) ?那里也有问题,我认为你不能强迫语言完全按照你的意愿去做,而不把它弄脏并重新连接它的头:c。据我所知,没有办法检查 catch 块之外的任何异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-07
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    • 2013-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多