65.9K
CodeProject 正在变化。 阅读更多。
Home

MVC 单元测试无限制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (94投票s)

2014年4月23日

CPOL

9分钟阅读

viewsIcon

239808

downloadIcon

6426

在本文中,我们将学习如何在 Asp.Net MVC 中执行单元测试。

必备组件

我们将讨论 MVC 单元测试,因此至少对 MVC 有基本概念是必须的。

你应该知道:

  • MVC 的目的是什么?
  • MVC 如何工作?
  • 在 MVC 应用程序的不同层之间传递数据有哪些不同的方式?
  • 如何在 MVC 中执行导航?

如果你是 Asp.net MVC 的新手,请先尝试找出上述问题的答案。点击这里查看我们的 MVC 逐步学习系列。

我们将学习什么?

注意

如你所见,我将本文分解为许多子主题。本文是考虑到每位读者都完全不了解单元测试而编写的。如果你了解上面列出的任何子主题,可以跳过该部分,直接进入下一个子主题 J

单元测试基础

当我们听到 Asp.Net MVC 这个词时,主要会想到两件事:

  • 对 HTML 的完全控制。
  • 易于单元测试。

如果你已经熟悉单元测试的基础知识,那么可以直接从“迈向 MVC 单元测试的途径”开始。

什么是单元测试?

非常基本的定义是:以自动化方式测试代码中每一个最小的可测试块。

还是感到困惑?

让我把它简化一下。

  • 在现实世界中,每个应用程序都包含许多模块。例如,一个简单的电子商务应用程序可能包含库存模块、客户管理模块、订单模块等。
  • 每个模块都是由一个或多个类组成的。
  • 每个类都以函数的形式暴露许多功能。

在单元测试中,我们以自动化方式单独测试这些函数。

“自动化方式”到底是什么意思?

我们将编写另一个代码来测试我们的其他代码(测试函数的结果)。

单元测试的重要性是什么?

要理解这一点,请在不使用任何工具或应用程序的情况下回答以下问题:“996*965/5 的结果是什么?”

还有 3 个问题:

  • 你认为你的答案正确吗?
  • 如果正确,你花了多长时间才得出结果?
  • 如果操作中的数字发生变化,你能重复使用你脑海中执行的计算过程吗?

    现在,只需用计算器替换你脑海中执行的手动工作。

  • 现在你将 100% 确定结果。
  • 答案会很快。
  • 重复使用同一个计算器进行另一次计算。

    自动化使事情更加*准确、快速和可重用*。

当我们为代码编写自动化单元测试用例时,它将成为证明我们代码正常工作的证据。最棒的是,无论何时我们对代码进行任何更改,我们只需重新运行之前编写的单元测试用例,并确认以前的功能没有被破坏。

单元测试的基本示例

步骤 1. 创建一个需要用以下代码块进行测试的类库。

public class CustomMaths
{
    public int Add(int num1,int num2)
    {
        return Number1 + Number2;
    } 
}

步骤 2. 如下创建单元测试项目。

步骤 3. 将之前创建的类库的引用添加到这个新创建的项目中。

步骤 4. 如下创建测试类。

[TestClass]
public class TestMathsClass
{
    [TestMethod]
    public void TestAdd()
    {
        //Arrange
        CustomMaths maths = new CustomMaths();

        //Act
        int result = maths.Add(6, 5);

        //Assert
        Assert.AreEqual<int>(11, result);
    }
}

解释 – 如你所见,单元测试遵循简单的三个步骤。

  • Arrange (准备) - 创建对象并准备测试功能所需的一切。
  • Act (执行) – 执行并获取输出。
  • Assert (断言) – 将最终输出与预期输出进行比较。

步骤 5. 构建你的解决方案,并从“测试”>“窗口”>“测试资源管理器”打开测试资源管理器窗口。

步骤 6. 右键单击测试用例,然后选择“运行选定的测试”。

注意:在此示例中,我们使用硬编码值(5 和 6)进行测试,但在实际场景中,我们不会使用硬编码值,而是使用一些数据源(如 Excel 或数据库)作为输入参数和返回值。

迈向 MVC 单元测试的途径

现在你了解了什么是单元测试以及它是如何工作的。那么是时候了解单元测试在 MVC 中是如何工作的了。

我们编写的逻辑有哪几种类型?

通常当我们说“逻辑”时,我们唯一想到的是“业务逻辑”。在实际应用程序中,我们最终会编写许多不同类型的逻辑。

业务逻辑

与业务相关的逻辑。例如,税收计算、订单处理、转换(从 Excel 到 PDF)等。

数据转换逻辑

我们的数据库中存储了出生日期,我们想在页面中显示年龄。将我们的业务数据转换为特定于视图的数据的逻辑称为数据转换逻辑。

表示逻辑

根据数据库中的某些值(或任何其他值)改变页面中(或最终用户将获得的任何输出)呈现的逻辑,称为呈现逻辑。例如 – 如果员工工资大于 5 万,则以蓝色显示,否则以绿色显示。

用户交互逻辑

处理不同用户交互(如点击某个控件)的逻辑称为用户交互逻辑。

数据库逻辑

处理数据库特定代码的逻辑称为数据库逻辑。这种逻辑的例子是执行存储过程。

测试传统 Web Forms 应用程序的难点。

在使用传统 Web Form 应用程序时,我们能做的最好事情是将其分解为 3(或者更多)层。每一层都将执行一些特定的逻辑。在这种情况下,业务逻辑和数据库逻辑将被编写为两个独立的层(分别称为业务层和数据库层)。这两个层的单元测试将很容易,因为它们将作为简单的类库(包含一组类和函数)编写,因此这两个层可以按照我们上面做的方式进行测试。

这里的问题是:

  • UI 与代码后端紧密耦合。
  • 用户交互逻辑、呈现逻辑和数据转换逻辑将写在代码后端中。
  • 无法对代码后端进行单元测试,因为代码后端包含事件处理程序,而将事件处理程序作为函数调用将是一项非常困难的任务,因为它通常有两个参数,即“object sender”和“EventArgs e”,它们无法通过编程生成。

因此,无法对用户交互逻辑、数据转换逻辑和呈现逻辑进行单元测试。

在 MVC 中进行测试?

Asp.Net MVC 的基本组件是:

  • 模型 (Model) – 业务数据和业务逻辑。
  • 视图 (View) – Aspx 或 Razor(或可能是用户自定义的)UI 包含一些可视化元素。
  • 控制器 (Controller) – 处理用户交互逻辑。

1. 用户交互逻辑测试

我们知道:

  • Asp.Net MVC 中的每个请求都通过控制器。
• 控制器包含用户交互逻辑。
• 控制器与视图没有紧密耦合。

因此,在 Asp.Net MVC 中可以测试用户交互逻辑。

代码演示

Asp.Net MVC 用户交互测试或控制器测试包括:

  • 测试 ViewResult。
  • 测试 ViewData/ViewBag。
  • 测试 RedirectResult。

演示 1 – 测试 ViewResult

假设我们有以下动作方法。

//Action Method
public ActionResult GetView(int id)
{
    if (id == 0)
    {
        return View("View2");
    }
    else
    {
        return View("View1");
    }
}

我们的任务是创建一个测试方法,它将确认动作方法是否生成正确的 ViewResult。

//Test Cases
[TestMethod]
public void TestForViewWithValue0()
{
    //Arrange
    TestingController t = new TestingController();

    //Act
    ViewResult r = t.GetView(0) as ViewResult;
            
    //Asert
    Assert.AreEqual("View2", r.ViewName);
          
}

[TestMethod]
public void TestForViewWithValueOtherThanZero()
{
    //Arrange
    TestingController t = new TestingController();

    //Act
    ViewResult r = t.GetView(1) as ViewResult;
            
    //Asert
    Assert.AreEqual("View1", r.ViewName);
          
}

演示 2 – 测试 ViewData/ViewBag

假设我们有以下动作方法。

//Action Method
public ActionResult Action2()
{
    ViewData["Name"] = "SomeName";
    return View();
}

我们的任务是创建一个测试方法,它将确认动作方法是否生成正确的 ViewData。

//Test Cases
[TestMethod]
public void TestForViewData()
{
    //Arrange
    TestingController t = new TestingController();

    //Act
    ViewResult r = t.Action2() as ViewResult;


    //Asert
    Assert.AreEqual("Sukesh", r.ViewData["Name"]);

}

演示 3 – 测试重定向

假设我们有以下动作方法。

//Action Method
public ActionResult Details(int Id)
{
    if (Id < 0)
        return RedirectToAction("Index","SomeElse");

    return View("Details");

}

我们的任务是创建一个测试方法,它将确认动作方法是否重定向到正确的位置。

//Test Cases
[TestMethod]
public void TestDetailsForRedirect()
{
    TestingController controller = new TestingController();
    var result = controller.Details(-1) as RedirectToRouteResult;
    Assert.AreEqual("Index", result.RouteValues["action"]);
    Assert.AreEqual("SomeElse", result.RouteValues["controller"]);
}
[TestMethod]
public void TestDetailsForViewResult()
{
    TestingController controller = new TestingController();
    ViewResult result1 = (ViewResult)controller.Details(2);
    Assert.AreEqual("Details", result1.ViewName);
}

注意:这里的测试步骤不会改变。一旦创建了测试用例,只需构建它,然后从测试资源管理器窗口执行测试用例,如“单元测试的基本示例”中所示。

2. 业务逻辑

正如我们之前所说,业务逻辑和业务数据是 Asp.Net MVC 中模型的一部分。
业务逻辑将作为简单的 .Net 类实现,其中包含几个函数。我们可以像在“单元测试的基本示例”中那样轻松地对其进行单元测试。

3. 数据库逻辑

在 Asp.Net MVC 的讨论中,从未明确提及数据库层。我们
通常将其作为一个单独的层,通过业务层访问。
归根结底,它仍然是一个包含几个函数的单个类。
单元测试将与上述相同。

4. 数据转换逻辑和呈现逻辑。

这是所有声音都低沉的地方。这两个逻辑决定了需要显示什么数据
以及如何显示。
它们直接与视图相关联。如果我们在视图中编写这种逻辑,单元测试
将无法进行。

让我们看看一个在视图本身中包含数据转换逻辑和呈现逻辑的应用程序。

我们有什么?
  • Employee Model (员工模型)
public class Employee
{
    public string EmployeeName { get; set; }
    public string Address { get; set; }
    public DateTime DateOfBirth { get; set; }
    public int Salary { get; set; }
}
  • Employee Controller (员工控制器)
public class EmployeeController : Controller
{
    public ActionResult Show()
    {
        Employee e = GetEmployee();
        return View(e);
    }…
}
  • Employee Show View (Show.cshtml) (员工显示视图)
<div>
    Employee Detail<br />
    Employee Name : @Model.EmployeeName<br />
    Address : @Model.Address


    <br />
    @{
        int age = DateTime.Now.Year - Model.DateOfBirth.Year;
        if (Model.DateOfBirth > DateTime.Now.AddYears(-age))
        {
            age--;
        }
    }
    Age : @age


    <br />
    @if (Model.Salary > 20000)
    {
        <span style="color: red">Salary : @Model.Salary</span>
    }
    else
    {
        <span style="color: green">Salary : @Model.Salary</span>
    }
</div>

如你所见,我们正在:

  • 将出生日期转换为年龄并显示出来 - 数据转换逻辑。
  • 根据工资改变显示文本的颜色 – 呈现逻辑。

这种 UI 设计、呈现逻辑和数据转换逻辑之间的紧密耦合成为完全单元测试的障碍。

解决方案是什么?

解决方案将更具逻辑性而非技术性。

我们将在模型和视图之间引入一个新层,并称之为 ViewModel。

模型将被识别为与业务相关的数据和逻辑,而 ViewModel 将被识别为与视图相关的数据和逻辑。

模型和视图之间不会有任何直接连接。视图将始终连接到 ViewModel,其中将包含:

  1. 对模型的引用。
  2. 根据视图要求的数据转换逻辑。
  3. 基于视图要求的呈现逻辑。

因此,第一步是创建如下所示的员工视图模型。

public class EmployeeVM
{
    public Employee emp { get; set; }
   
    public EmployeeVM(Employee e1)
    {
        emp = e1;
    }
    public int Age
    {
        get
        {
            int age = DateTime.Now.Year - emp.DateOfBirth.Year;
            if (emp.DateOfBirth > DateTime.Now.AddYears(-age))
            {
                age--;
            }
            return age;
        }
    }


    public string SalaryColor
    {
        get
        {
            if(emp.Salary>20000)
            {
                return "red";
            }
            else
            {
                return "green";
            }
        }
    }
}

第二步是将视图更改为 ViewModel 类的强类型视图,而不是模型,如下所示。

<div>
    Employee Detail<br />
    Employee Name : @Model.emp.EmployeeName<br />
    Address : @Model.emp.Address <br />
    Age : @Model.Age
        <br />
    <span style="color:@Model.SalaryColor">Salary : @Model.emp.Salary</span>
</div>
我们成功了。视图模型只是一个包含两个属性的类,可以
使用简单的单元测试逻辑进行测试。这意味着我们的呈现逻辑和数据
转换逻辑现在是可测试的。

观看以下关于 ASP.NET MVC 中视图模型的视频:-

结论

MVC 是一种旨在解决 UI 层面问题的架构模式。我们刚刚看到了如何完全将 UI 与逻辑分离,并使我们的每一个逻辑代码都成为可测试的代码。持续编码,持续学习,持续分享。

别忘了投票和评论。

如需 WCF、MVC、商业智能、设计模式、WPF、

TFS 和基本原理等各种技术培训,请随时联系 SukeshMarla@Gmail.com 或访问 www.sukesh-marla.com

欲了解更多此类内容,请点击此处。订阅文章更新或在 Twitter 上关注@SukeshMarla

查看 .NET、C#、ASP.NET、SQL、WCF、WPF、WWF、SharePoint、设计模式、UML 等 600 多个常见问题及答案。

© . All rights reserved.