第4章:MSTest入门:框架介绍、安装与配置、[TestMethod]特性、数据驱动测试

4.1 MSTest框架是什么?

MSTest是微软官方出品的单元测试框架。它直接集成在Visual Studio里,开箱即用。

我个人习惯用MSTest做企业级项目的测试。为什么?因为它和.NET生态配合得最好。你不需要额外装什么NuGet包,新建项目时选「MSTest Test Project」就行。

我记得刚入行那会儿,团队里有人争论用NUnit还是xUnit。其实吧,选哪个都行。但如果你在微软技术栈里深耕,MSTest绝对是最省心的选择。

核心特点:

  • 官方出品,与Visual Studio深度集成
  • 支持.NET Framework和.NET Core/.NET 5+
  • 内置测试资源管理器,一键运行
  • 支持数据驱动测试、并行执行

4.2 安装与配置

说白了,安装MSTest就两步。我带你走一遍。

4.2.1 通过Visual Studio创建

打开VS,新建项目,搜索「MSTest」。你会看到这个模板:

项目模板:MSTest Test Project (.NET Core)
语言:C#
框架:.NET 6.0 (或你用的版本)

点确定,项目就建好了。它会自动引用这几个包:

包名 作用
Microsoft.NET.Test.Sdk 测试运行器的基础SDK
MSTest.TestAdapter MSTest适配器,让VS认识你的测试
MSTest.TestFramework 核心框架,包含[TestMethod]等特性

4.2.2 手动添加NuGet包

如果你用命令行或者已有项目,可以这样:

dotnet new mstest -n MyTestProject
cd MyTestProject
dotnet add package MSTest.TestFramework
dotnet add package MSTest.TestAdapter
dotnet add package Microsoft.NET.Test.Sdk

嗯,这里要注意:版本号要匹配。我曾经遇到过因为版本不一致,测试跑不起来的情况。建议都用最新稳定版。

小技巧:dotnet list package --outdated 可以查看哪些包需要更新。

4.3 [TestMethod]特性详解

这是MSTest的核心。每个测试方法都要打上这个标签。

你想想看,没有[TestMethod],测试框架怎么知道哪些方法是测试?它总不能把所有public方法都跑一遍吧。

4.3.1 基本用法

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class CalculatorTests
{
    [TestMethod]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Arrange
        var calc = new Calculator();
        
        // Act
        var result = calc.Add(2, 3);
        
        // Assert
        Assert.AreEqual(5, result);
    }
}

这里有个坑:测试类必须标记[TestClass],方法必须标记[TestMethod]。少一个都不行。我刚开始写测试时,忘了加[TestClass],结果测试列表里死活看不到我的测试方法。排查了半天...

4.3.2 常用断言方法

断言 说明
Assert.AreEqual 判断相等
Assert.AreNotEqual 判断不相等
Assert.IsTrue 判断为真
Assert.IsFalse 判断为假
Assert.IsNull 判断为空
Assert.ThrowsException 判断抛出异常

避坑指南: 我曾经用Assert.AreEqual比较两个List,结果失败了。因为默认比较的是引用。要用CollectionAssert.AreEqual才行。

4.4 数据驱动测试

为什么要数据驱动?你想想看,一个方法可能有几十种输入组合。难道每个组合写一个测试方法?那代码就太冗余了。

数据驱动测试,说白了就是「一个测试方法,多组测试数据」。

4.4.1 使用[DataRow]特性

这是最常用的方式。直接给方法传参数:

[TestClass]
public class CalculatorDataDrivenTests
{
    [TestMethod]
    [DataRow(1, 2, 3)]
    [DataRow(0, 0, 0)]
    [DataRow(-1, 1, 0)]
    [DataRow(100, 200, 300)]
    public void Add_DataDriven_ReturnsExpected(int a, int b, int expected)
    {
        var calc = new Calculator();
        var result = calc.Add(a, b);
        Assert.AreEqual(expected, result);
    }
}

运行这个测试,它会自动生成4个测试用例。每个用例独立执行,互不影响。

4.4.2 使用[DynamicData]特性

当数据量很大时,写在[DataRow]里就不太合适了。可以用[DynamicData]从外部获取数据:

[TestClass]
public class CalculatorDynamicDataTests
{
    private static IEnumerable<object[]> GetTestData()
    {
        yield return new object[] { 1, 2, 3 };
        yield return new object[] { 0, 0, 0 };
        yield return new object[] { -1, 1, 0 };
        // 可以从文件、数据库读取
    }

    [TestMethod]
    [DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)]
    public void Add_DynamicData_ReturnsExpected(int a, int b, int expected)
    {
        var calc = new Calculator();
        var result = calc.Add(a, b);
        Assert.AreEqual(expected, result);
    }
}

我个人习惯用[DynamicData]做复杂场景。比如从CSV文件读取测试数据,或者从数据库拉取。这样测试数据和代码分离,维护起来方便。

4.4.3 使用[DataSource]特性(高级)

这个特性可以直接绑定数据库或XML文件:

[TestMethod]
[DataSource("System.Data.SqlClient",
    "Data Source=.;Initial Catalog=TestDB;Integrated Security=True",
    "TestCases",
    DataAccessMethod.Sequential)]
public void TestFromDatabase()
{
    var a = (int)TestContext.DataRow["InputA"];
    var b = (int)TestContext.DataRow["InputB"];
    var expected = (int)TestContext.DataRow["Expected"];
    
    var calc = new Calculator();
    var result = calc.Add(a, b);
    Assert.AreEqual(expected, result);
}

经验之谈: 数据驱动测试能帮你发现边界问题。比如我测试一个日期处理函数,用[DataRow]传了100多组日期,结果发现闰年2月29日的处理有bug。要是手动写100个测试方法,我估计早放弃了。

4.5 本章小结

MSTest入门其实不难。记住三个关键点:

  • [TestClass] + [TestMethod]:这是标配,缺一不可
  • 断言方法:掌握常用的几个就够了,别贪多
  • 数据驱动:用[DataRow]做简单场景,用[DynamicData]做复杂场景

下一章我们会讲测试生命周期,也就是[ClassInitialize]、[TestInitialize]这些。到时候你会明白,为什么有些代码要放在初始化方法里,而不是直接写在测试方法中。

嗯,今天就到这里。动手试试吧,把上面的代码敲一遍,你会记得更牢。