简体   繁体   English

通过事件C#处理异常的单元测试方法

[英]Unit Testing Methods with Handled Exceptions Via Events C#

I've decided to create Unit Tests for a WinForm application I have. 我已经决定为我拥有的WinForm应用程序创建单元测试。 I used a custom layered MVC approach (View, Controller, Model) which has made unit testing easier by creating a mock view and testing the controller methods. 我使用了自定义的分层MVC方法(视图,控制器,模型),该方法通过创建模拟视图并测试控制器方法使单元测试更加容易。 I've encountered an interesting problem with exceptions. 我遇到了一个有趣的异常问题。 In my application, exceptions are propagated via events. 在我的应用程序中,异常是通过事件传播的。 My controller subscribes to an "Exception Event" contained in the model which has exception information. 我的控制器订阅了包含异常信息的模型中包含的“异常事件”。 In the event handler, the controller takes that information and calls the View's "Display Error" method. 在事件处理程序中,控制器获取该信息并调用View的“ Display Error”方法。 Here's some partial code to depict what I'm doing: 这是描述我在做什么的部分代码:

interface IView
{
    public void DisplayError(string message);
}

public class Controller
{
    IView _view;
    Model _model;
    public Controller(IView view, Model model)
    {
        _view = view;
        _model = model;
        model.ErrorRaised += ErrorRaisedEventHandler(handle_error);
    }

    private void handle_error(object sender, ErrorEventArgs e)
    {
        _view.DisplayError(e.Message);
    }
}

public Model
{
    event ErrorRaisedEventHandler ErrorRaised;

    public void DoSomething()
    {
        try
        {
            //Do something bad
        }
        catch (Exception e)
        {
            ErrorRaised(this, new ErrorEventArgs(e.Message))
        }
    }
}

Is there a best practice for unit testing this? 是否有最佳实践对此进行单元测试? I was searching for something along the lines of asserting the output message from the exceptions but didn't get very far. 我一直在寻找从异常中断言输出消息的方式,但是并没有走得太远。 Thanks! 谢谢!

Your code doesn't even compile, public Model is missing class and model.ErrorRaised += ErrorRaisedEventHandler is missing new. 您的代码甚至无法编译, public Model缺少类和public Model model.ErrorRaised += ErrorRaisedEventHandler缺少新的。 Please do the needful next time, as it deters people... 请下次执行有需要的操作,因为它会阻止人们...

Working Code 工作守则

public interface IView
{
    void DisplayError(string message);
}

public class MyView : IView
{
    public string ErrorMessage;
    public void DisplayError(string message)
    {
        ErrorMessage = message;
        System.Diagnostics.Debug.Write(message);
    }
}

public delegate void ErrorRaisedEventHandler(object sender, ErrorEventArgs e);

public class Controller
{
    IView _view;
    Model _model;
    public Controller(IView view, Model model)
    {
        _view = view;
        _model = model;
        _model.ErrorRaised += new ErrorRaisedEventHandler((s,e) => _view.DisplayError(e.GetException().Message));
    }
}

public class Model
{
    public event ErrorRaisedEventHandler ErrorRaised;

    int monthsAlive = 0;
    public int MonthsAliveInPlanet(int yearBorn, int yearInTime, int monthsInPlanetsYear)
    {
        try
        {
            //Do something bad - divisioin by zero
            monthsAlive = (yearInTime - yearBorn) / monthsInPlanetsYear;               
        }
        catch (Exception e)
        {
            ErrorRaised(this, new ErrorEventArgs(e));
        }
        return monthsAlive;
    }
}

Best Practice 最佳实践

Generally you should put all the logic in the Controller not in the Model. 通常,您应将所有逻辑放在控制器中而不是模型中。 However, to entertain your example of raising an exception event from the Model via the Controller to the View I've written a method: MonthsAliveInPlanet. 但是,为了娱乐您的示例,该示例将异常事件从Model经由Controller引发到视图,我编写了一个方法:MonthsAliveInPlanet。

Sometimes little bits of logic do end up in the Model, simple stuff like an Age Calculation DateTime.Now.Year - yearBorn and to simulate a Division By Zero exception I've used a "monthsInPlanetsYear". 有时,逻辑中的某些逻辑确实会出现在模型中,例如年龄计算DateTime.Now.Year - yearBorn这样的简单东西,并且为了模拟“被零除”例外,我使用了“ monthsInPlanetsYear”。

Is there a best practice for Unit Testing this? 是否有最佳实践来进行单元测试?

Probably not, you would be better moving the logic to the controller and testing that. 可能不是,您最好将逻辑移至控制器并进行测试。 Nevertheless, if you did want to tick the "best practice" box and write a Unit Test for it I'd recommend using the "Create IntelliTests" feature included with VS2015: 不过,如果您确实想勾选“最佳实践”框并为其编写单元测试,我建议您使用VS2015附带的“创建IntelliTests”功能:

在此处输入图片说明

This will churn out the following generic Unit Test code (as seen above, with the 2 failing tests and 1 passing test) that you can build on to test all edge cases . 这将产生出可用于测试所有边缘情况的以下通用单元测试代码(如上所示,具有2个失败测试和1个通过测试)。

[PexClass(typeof(Model))]
[PexAllowedExceptionFromTypeUnderTest(typeof(InvalidOperationException))]
[PexAllowedExceptionFromTypeUnderTest(typeof(ArgumentException), AcceptExceptionSubtypes = true)]
[TestClass]
public partial class ModelTest
{
    /// <summary>Test stub for MonthsAliveInPlanet(Int32, Int32, Int32)</summary>
    [PexMethod]
    public int MonthsAliveInPlanetTest([PexAssumeUnderTest]Model target, int yearBorn, int yearInTime, int monthsInPlanetsYear)
    {
        int result = target.MonthsAliveInPlanet(yearBorn, yearInTime, monthsInPlanetsYear);
        return result;
        // TODO: add assertions to method ModelTest.MonthsAliveInPlanetTest(Model, Int32, Int32, Int32)
    }
}

Edge Cases 边缘案例

So an edge case I'd add to test the Division By Zero error message is bubbled up to the view: 因此,我要添加一个边缘情况以测试“按零除”错误消息冒泡到视图:

[TestMethod]
/// <summary>Test stub for MonthsAliveInPlanet(Int32, Int32, Int32)</summary>
public void MonthsAliveInPlanetTestShouldFailWithZeroMonthsInYear()
{
    //Arrange
    IView view = new MyView();
    Model target = new Model();
    Controller ctrl = new Controller(view, target);                        
    int yearBorn = 0;
    int yearInTime = 0;
    int monthsInPlanetsYear = 0;

    //Act
    int result = target.MonthsAliveInPlanet(yearBorn, yearInTime, monthsInPlanetsYear);

    //Assert
    Assert.AreEqual("Attempted to divide by zero.",((MyView)view).ErrorMessage);
}

Although I've entertained the idea of trivial logic in the Model and bubbling up an error message if an exception occurs, it is not good practice . 尽管我对模型中的微不足道的逻辑很满意,并且在发生异常时冒泡了一条错误消息,但这不是一个好习惯

Validate Model Data Using DataAnnotations Attributes 使用DataAnnotations属性验证模型数据

Exceptions are exceptional and the Model should be dumb so any View can bind to it, with the Controller doing the work. 异常是例外,模型应该是愚蠢的,以便任何视图都可以绑定到它,而Controller则可以完成工作。 Its best practice to put all the business logic in the Controller and use DataAnnotations Attributes to validate the Model, you don't want exceptions to ever occur in the Model, eg: 最好的做法是将所有业务逻辑放入Controller并使用DataAnnotations属性验证模型,您不希望模型中发生异常,例如:

public class Demo {
    [StringLength(50),Required]
    public object Name { get; set; }
    [StringLength(15)]
    public object Color { get; set; }
    [Range(0, 9999)]
    public object Weight { get; set; }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM