C# 异常和测试-当我捕获到所有异常时,如何针对特定的异常进行单元测试?

C# 异常和测试-当我捕获到所有异常时,如何针对特定的异常进行单元测试?,c#,unit-testing,asp.net-mvc-4,exception,C#,Unit Testing,Asp.net Mvc 4,Exception,我正在编写MVC4 web应用程序。通常,我尝试将“try{}catch{}”块放在每个向用户返回ActionResult的控制器方法中。我这样做是为了捕获所有异常并显示适当的消息,所以用户永远不会看到类似以下内容: “引用未设置为对象的实例” 我的控制器通常如下所示: try { } catch(MyFirstCustomException ex) { //set some message for the user and do some cleaning etc. return Acti

我正在编写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();
}
public enum LoginViewModelStatus
{
NotLoggedIn = 0,
LoginSuccessfull = 1,
LoginFailed = 2,
UserNotActivatedException = 3,
UnknownErrorException = 100
}
try
{
    ActionClass action = new ActionClass();
    action.HandleLogin(...)

}
// Catchblock here
但是现在我遇到了以下情况:我有AccountControllerLogIn方法,我想编写一个单元测试(使用Microsoft单元测试框架),它将断言未激活其帐户的用户将无法登录。我有一个名为UserNotActivatedException的特殊异常,当检测到这种尝试时会抛出该异常。问题是——因为我捕获了控制器中的所有异常,所以我的测试实际上永远不会看到这个异常本身——因此测试总是会失败。我通过为我的模型创建特殊的状态枚举绕过了这个问题,如下所示:

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();
}
public enum LoginViewModelStatus
{
NotLoggedIn = 0,
LoginSuccessfull = 1,
LoginFailed = 2,
UserNotActivatedException = 3,
UnknownErrorException = 100
}
try
{
    ActionClass action = new ActionClass();
    action.HandleLogin(...)

}
// Catchblock here
当某些事情发生时,通过将其设置为某个值(因此,当我捕获特殊的UserNotActivatedException时,我将loginModelStatus设置为UserNotActivatedException等等)

我的问题是:

  • 有更好的选择吗
  • 我也在考虑在其他控制器中使用这种设计,这里有什么缺点吗
  • 使用大量自定义异常为用户显示消息是一种好的设计,还是使用更小型的if(someCondition){return false;}测试更好

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

    例如,在您的情况下,
    Controller
    将多个异常转换为不同的视图-测试当您提供导致异常场景的数据时,
    Controller
    是否返回您期望的视图

    若控制器使用的较低级别的方法可能引发异常,那个么也要测试它们,但这次是为了引发特定的异常

    有多少例外就够了,这取决于你自己。良好的异常记录可能比多样性更重要。在大多数情况下,您无论如何都不应该向用户显示异常信息,而应该显示类似“灾难性错误。如果需要帮助,则使用id AB455记录错误”。所有“预期异常”情况都应作为正常流程处理并提交给用户


    请注意,只要您有处理所有异常的代码,就可以从操作中抛出异常。类操作筛选器可用于为特定操作/整个应用程序配置异常策略。

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

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

    class ActionClass
    {
        public bool HandleLogin(...)
        {
            ...
        }
    }
    
    然后像这样使用类:

    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();
    }
    
    public enum LoginViewModelStatus
    {
    NotLoggedIn = 0,
    LoginSuccessfull = 1,
    LoginFailed = 2,
    UserNotActivatedException = 3,
    UnknownErrorException = 100
    }
    
    try
    {
        ActionClass action = new ActionClass();
        action.HandleLogin(...)
    
    }
    // Catchblock here
    

    这将允许您测试逻辑。

    您可以将代码包装在try部分中,以便能够对该部分进行单元测试。
    class ActionClass
    {
        public bool HandleLogin(...)
        {
            ...
        }
    }
    
    这里,单元可测试部分简单地“包装”在
    MyUnitTestableMethod
    方法中:

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

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

    就我的阅读而言,对代码逻辑使用异常不是一个好的做法。异常对于清理和回滚之类的事情很有用。此外,使用大量try-catch块会降低应用程序的性能。“引用未设置为对象的实例”。不必在异常中捕获类似的内容,您只需检查对象是否为null,并根据该值采取必要的操作。使用try/catch块不会降低性能,但是异常非常昂贵,使用带有异常的逻辑流是不好的,是的,尽管与.NET Framework的版本相比有所改进,但这些例外情况的成本仍然很高。我认为,对于一些框架,不记得MS测试框架,您通常可以在方法上添加一个属性,以期望在方法中抛出一种类型的异常,即使您有一个try/catch块。他们中的一些人称之为ExpectedException(typeof(BlahexException))之类的东西,而不是在每个操作方法中编写样板代码,为什么不使用?嗯,我想,这应该可以很好地工作-但是,使用status enum是一个坏主意吗?嗯,我认为这是一个坏主意,因为:1。它将在代码中引入复杂性;2.这将仅用于单元测试。不要在生产代码中编写用于单元测试的代码。