异常和测试 - 当我捕获所有异常时,如何对特定异常进行单元测试

本文关键字:异常 单元测试 测试 | 更新日期: 2023-09-27 18:37:15

我正在编写MVC4 Web应用程序。通常,我尝试将"try{}catch{}"块放在每个向用户返回ActionResult的控制器方法中。我这样做是为了捕获所有异常并显示适当的消息,因此用户永远不会看到以下内容:

"引用未设置为对象的实例"

我的控制器通常如下所示:

try
{
}
catch(MyFirstCustomException ex)
{
//set some message for the user and do some cleaning etc.
return ActionResult();
}
catch(MySecondCustomException ex) (and so on...)
{
//set some message for the user and do some cleaning etc.
return ActionResult();
}
catch(Exception ex)
{
//set some message for the user and do some cleaning etc.
return ActionResult();
}

但是现在我遇到了以下情况:我有AccountController和一个LogIn方法,我想编写一个单元测试(使用Microsoft单元测试框架),这将断言尚未激活其帐户的用户将无法登录。我有一个名为UserNotActiveException的特殊异常,当检测到此类尝试时,会抛出该异常。问题是 - 由于我在控制器中捕获了所有异常,因此我的测试永远不会真正看到此异常本身 - 因此测试将始终失败。我设法通过为我的模型创建特殊状态枚举来绕过这个问题,如下所示:

public enum LoginViewModelStatus
{
NotLoggedIn = 0,
LoginSuccessfull = 1,
LoginFailed = 2,
UserNotActivatedException = 3,
UnknownErrorException = 100
}

并在发生某些事情时将其设置为某个值(因此,当我捕获我的特殊UserNotActivated Exception时 - 我将loginModelStatus设置为UserNotActivated Exception等

我的问题:

  1. 有没有更好的选择?
  2. 我也在考虑在其他控制器中使用这种设计,这里有任何缺点吗?
  3. 使用
  4. 大量自定义异常来向用户显示消息是好的设计,还是使用更多的迷你 if(someCondition){return false;} 测试更好?

异常和测试 - 当我捕获所有异常时,如何对特定异常进行单元测试

您可以将代码包装在try部分中,以便能够对此部分进行单元测试。在这里,单元可测试部分被简单地"包装"在MyUnitTestableMethod方法中:

try
{
    MyUnitTestableMethod();
}
catch(MyFirstCustomException ex)
{
    // ...
}
catch(MySecondCustomException ex) (and so on...)
{
    // ...
}
catch(Exception ex)
{
    // ...
}

吻:保持理智的简单(或保持简单和愚蠢):)

您应该测试代码在所有情况下是否返回预期结果,并且或多或少忽略方法的工作方式。

即,在您的情况下Controller将多个异常转换为不同的视图 - 测试当您输入导致异常情况的数据时,Controller返回您期望的视图。

如果控制器使用的较低级别的方法可能会引发异常 - 也测试它们,但这次是为了抛出特定的异常。

多少

例外就足够了,这取决于你。良好的异常记录可能比多样性更重要。在大多数情况下,您不应该向用户显示异常中的信息,而应该显示类似"灾难性错误。如果需要帮助,错误记录在 ID AB455"。所有"预期异常"情况都应作为正常流进行处理并呈现给用户。

请注意,只要您有处理所有异常的代码,就可以从操作中引发异常。操作筛选器(如 HandleErrorAttribute)可用于为特定操作/整个应用程序配置例外策略。

似乎你的代码"太稳定了"。也就是说,您的逻辑永远不会产生错误。从稳定性的角度来看,它很好,但不是很可测试。

在这种情况下,我会有一个类来处理自定义逻辑,在返回 ActionResult 以分离逻辑之前捕获从该类生成的所有异常。

class ActionClass
{
    public bool HandleLogin(...)
    {
        ...
    }
}

并像这样使用类:

try
{
    ActionClass action = new ActionClass();
    action.HandleLogin(...)
}
// Catchblock here

这将允许您测试逻辑。