对 Windows 应用商店应用程序中的 Visual C# 代码进行单元测试

本主题介绍一种方法,其中使用 Visual Studio Express 2012 for Windows 8 和 Microsoft 单元测试框架,针对 Windows 应用商店应用程序中的 Visual C# 类创建单元测试。Rooter 类通过实现计算给定数的平方根的估计的函数来演示限制计算理论的模糊内存。Maths 应用程序之后可使用此函数为用户演示可通过 math 完成的有趣操作。

本主题演示如何使用单元测试作为开发的第一步。在此方法中,首先编写验证要测试的系统的特定行为的测试方法,然后编写通过测试的代码。通过按照以下过程的顺序进行更改,您可调转此策略的顺序,即先编写要测试的代码,然后编写单元测试。

本主题还为单元测试和要测试的 DLL 创建一个 Visual Studio 解决方案和单独的项目。您还可在 DLL 项目中直接包含单元测试,也可以为单元测试和 DLL 创建不同的解决方案。

备注

Visual Studio Ultimate、VS Premium 和 VS Professional 提供其他适用于单元测试的功能。

  • VS Ultimate、VS Premium 和 VS Professional 能让您使用已为 Microsoft 测试资源管理器创建外接程序适配器的任何第三方和开放源代码单元测试框架。还可为测试分析并显示代码覆盖率信息。

  • VS Ultimate 和 VS Premium 能让您在每次生成后运行测试。

  • VS Ultimate 还包含 Microsoft Fakes(托管代码的隔离框架),后者通过替换系统和第三方功能的测试代码帮助您将您的测试集中于您自己的代码之上。

有关更多信息,请参见 MSDN 库中的Verifying Code by Using Unit Tests

在本主题中

创建解决方案和单元测试项目

验证测试是否在测试资源管理器中运行

向 Maths 项目添加 Rooter 类

将测试项目合并为应用程序项目

以迭代方式增加测试并使这些测试通过

调试未通过的测试

重构代码

创建解决方案和单元测试项目

  1. 在**“文件”菜单上选择“新建”,然后选择“新建项目”**。

  2. 在**“新建项目”对话框中,展开“已安装”“Visual C#”,选择“Windows Store”。然后从项目模板列表中选择“空白应用程序”**。

  3. 将项目命名为 Maths,并确保选中**“创建解决方案的目录”**。

  4. 在解决方案资源管理器中,选择解决方案名称,从快捷菜单中选择**“添加”,然后选择“新建项目”**。

  5. 在**“新建项目”对话框中,展开“已安装”“Visual C#”,然后选择“Windows 应用商店”。然后从项目模板列表中选择“单元测试库(Windows Store 应用程序)”**。

    创建单元测试项目

  6. 在 Visual Studio 编辑器中打开 UnitTest1.cs。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
    using Maths;
    
    namespace RooterTests
    {
        [TestClass]
        public class UnitTest1
    
            [TestMethod]
            public void TestMethod1()
            {
    
            }
    

    请注意:

    1. 每个测试都是使用 [TestMethod] 定义的。测试方法必须返回 void,并且不能具有任何参数。

    2. 测试方法必须位于使用 [TestClass] 特性修饰的类中。

      运行测试时,将为每个测试类创建一个实例。将按未指定顺序调用测试方法。

    3. 您可定义在每个模块、每个类或每个方法前后调用的特定方法。有关更多信息,请参见 MSDN 库中的在单元测试中使用 Microsoft.VisualStudio.TestTools.UnitTesting 成员

验证测试是否在测试资源管理器中运行

  1. UnitTest1.cs 文件的 TestMethod1 中插入一些测试代码:

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(0, 0);
    }
    

    请注意,Assert 类提供的若干静态方法可用于验证测试方法的结果。

  2. 在**“测试”菜单上,选择“运行”,然后选择“全部运行”**。

    将生成并运行测试项目。随即显示“测试资源管理器”窗口,并且测试列出在**“已通过的测试”**下。窗口底部的“摘要”窗格将提供有关所选测试的其他详细信息。

    测试资源管理器

向 Maths 项目添加 Rooter 类

  1. 在“解决方案资源管理器”中,选择**“Maths”项目名称。从快捷菜单中选择“添加”,然后选择“类”**。

  2. 将类文件命名为 Rooter.cs

  3. 将以下代码添加到 Rooter 类 Rooter.cs 文件中:

        public Rooter()
        {
        }
    
        // estimate the square root of a number
        public double SquareRoot(double x)
        {
            return 0.0;
        }
    
    
    

    Rooter 类声明一个构造函数和 SqareRoot estimator 方法。

  4. SqareRoot 方法只是一个最小实现,足以为测试设置测试基本结构。

将测试项目合并为应用程序项目

  1. 将对 Maths 应用程序的引用添加到 RooterTests 项目。

    1. 在解决方案资源管理器中,选择**“RooterTests”项目,然后选择快捷菜单上的“添加引用...”**。

    2. 在**“添加引用 - RooterTests”对话框上,展开“解决方案”,再选择“项目”。然后选择“Maths”**项目。

      添加一个对 Maths 项目的引用

  2. 向 UnitTest1.cs 文件添加 using 语句:

    1. 打开 UnitTest1.cs

    2. 在 using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 行下添加以下代码:

      using Maths;
      
  3. 添加使用 Rooter 函数的测试。将下列代码添加到 UnitTest1.cpp

        [TestMethod]
        public void BasicTest()
        {
            Maths.Rooter rooter = new Rooter();
            double expected = 0.0;
            double actual = rooter.SquareRoot(expected * expected);
            double tolerance = .001;
            Assert.AreEqual(expected, actual, tolerance);
        }
    
  4. 生成解决方案。

    新测试将显示在测试资源管理器的**“未运行的测试”**节点中。

  5. 在测试资源管理器中,选择**“全部运行”**。

    已通过基本测试

您已设置测试和代码项目,并验证了您可在代码项目中运行运行函数的测试。现在您可开始编写真实测试和代码。

以迭代方式增加测试并使这些测试通过

  1. 添加新测试:

        [TestMethod]
        public void RangeTest()
        {
            Rooter rooter = new Rooter();
            for (double v = 1e-6; v < 1e6; v = v * 3.2)
            {
                double expected = v;
                double actual = rooter.SquareRoot(v*v);
                double tolerance = ToleranceHelper(expected);
                Assert.AreEqual(expected, actual, tolerance);
            }
        }
    

    提示

    建议您不要更改已通过的测试。而是添加新测试,更新代码以便测试通过,然后添加其他测试等。

    当您的用户更改其要求时,请禁用不再正确的测试。编写新测试并使它们以相同的增量方式一次运行一个。

  2. 在测试资源管理器中,选择**“全部运行”**。

  3. 测试将不会通过。

    RangeTest 未通过

    提示

    编写测试后,立即验证每个测试是否都将失败。这帮助您避免易犯的错误,不会编写从不失败的测试。

  4. 增强受测代码,以便新测试通过。将 Rooter.cs 中的 SqareRoot 函数更改为:

        public double SquareRoot(double x)
        {
            double estimate = x;
            double diff = x;
            while (diff > estimate / 1000)
            {
                double previousEstimate = estimate;
                estimate = estimate - (estimate * estimate - x) / (2 * estimate);
                diff = Math.Abs(previousEstimate - estimate);
            }
            return estimate;
        }
    
  5. 生成解决方案,然后在测试资源管理器中,选择**“全部运行”**。

    现在所有三个测试都将通过。

提示

通过添加测试的方式一次性开发代码。确保所有测试在每次迭代后都通过。

调试未通过的测试

  1. UnitTest1.cs 添加另一个测试:

        // Verify that negative inputs throw an exception.
        [TestMethod]
        public void NegativeRangeTest()
        {
            string message;
            Rooter rooter = new Rooter();
            for (double v = -0.1; v > -3.0; v = v - 0.5)
            {
                try
                {
                    // Should raise an exception:
                    double actual = rooter.SquareRoot(v);
    
                    message = String.Format("No exception for input {0}", v);
                    Assert.Fail(message);
                }
                catch (ArgumentOutOfRangeException ex)
                {
                    continue; // Correct exception.
                }
                catch (Exception e)
                {
                    message = String.Format("Incorrect exception for {0}", v);
                    Assert.Fail(message);
                }
            }
        }
    
  2. 在测试资源管理器中,选择**“全部运行”**。

    测试将不会通过。在测试资源管理器中选择测试名称。将突出显示失败的断言。失败消息将在测试资源管理器的细节窗格中可见。

    NegativeRangeTests 未通过

  3. 若要查看测试未通过的原因,请单步执行以下函数:

    1. 在 SquareRoot 函数的开头设置断点。

    2. 在未通过测试的快捷菜单上,选择**“调试选定的测试”**。

      当运行在断点处停止时,请单步执行以下代码。

    3. 向 Rooter 方法添加代码以捕获异常:

          public double SquareRoot(double x)
          {
              if (x < 0.0)
              {
                  throw new ArgumentOutOfRangeException();
          }
      
    1. 在测试资源管理器中,选择**“全部运行”**以测试已纠正的方法,并确保您未引入回归测试。

所有测试都将通过。

所有测试通过

重构代码

简化了 SquareRoot 函数的核心计算过程。

  1. 更改结果实现

    // old code
    //result = result - (result*result - v)/(2*result);
    // new code
    result = (result + v/result) / 2.0;
    
  2. 选择**“全部运行”**以测试已重构的方法,并确保您未引入回归测试。

提示

一组稳定的优良单元测试可保证您在更改代码时不会引入 Bug。

重构测试代码以消除重复代码。

请注意,RangeTest 方法对 Assert 方法中使用的公差变量的分母进行硬编码。如果您计划添加其他使用同一公差计算的测试,则在多个位置使用硬编码的值可能导致错误。

  1. 向 Unit1Test 类添加一个私有方法以计算公差值,然后改调用该方法。

        private double ToleranceHelper(double expected)
        {
            return expected / 1000;
        }
    
        ...
    
        [TestMethod]
        public void RangeTest()
        {
            ...
            // old code
            // double tolerance = expected/1000;
            // new code
            double tolerance = ToleranceHelper(expected);
            Assert.AreEqual(expected, actual, tolerance);
        }
        ...
    
  2. 选择**“全部运行”**以测试已重构的方法,并确保您未引入错误。

备注

若要向测试类添加帮助器方法,请勿向该方法添加 [TestMethod] 特性。测试资源管理器未注册要运行的方法。