1. 测试类型简介
在 Visual Studio 中,您可以编写和运行多种类型的测试,最核心的是以下两种:
- 单元测试 (Unit Test)
- 目标 : 隔离并验证应用程序中一个最小的可测试单元(通常是一个方法或一个类)的内部逻辑是否正确。
- 特点: 速度极快、不依赖外部环境(如网络、数据库)、数量最多。
- 执行者 : 主要是开发者,作为编写代码时的"安全网"。
- 集成测试 (Integration Test)
- 目标 : 将多个单元"组装"起来,测试它们协同工作以及与外部系统(如数据库、文件系统、第三方API)交互时是否正确。
- 特点: 速度较慢、需要配置真实(或测试专用)的外部环境。
- 执行者: 主要是开发者,用于验证组件间的连接和配置。
2. 单元测试:从零开始
我们将以最流行的 xUnit 框架和 Moq 模拟框架为例,展示如何搭建和编写单元测试。
步骤 2.1: 创建单元测试项目
- 打开解决方案: 在 Visual Studio 中打开您的主项目所在的解决方案。
- 添加新项目 :
- 在"解决方案资源管理器"中,右键单击最顶层的解决方案 -> 添加 -> 新建项目。
- 搜索 "xUnit 测试项目" (xUnit Test Project),选择它并点击"下一步"。
- 命名项目: 遵循标准规范,将项目命名为 [YourProjectName].Tests(例如 image-coper.Tests)。
- 选择框架 : 确保选择与您主项目完全一致的 .NET 框架版本(例如 .NET 8.0),然后点击"创建"。
步骤 2.2: 添加依赖 (关键步骤)
- 添加项目引用 :
- 在新创建的 .Tests 项目上右键 -> 添加 -> 项目引用。
- 勾选您的主项目(例如 image-coper),点击"确定"。这使得测试项目可以访问主项目中的 public 类和方法。
- 安装 Moq 模拟框架 :
- 在 .Tests 项目上右键 -> 管理 NuGet 程序包。
- 在"浏览"选项卡中搜索 Moq 并安装。
步骤 2.3: 编写您的第一个单元测试
单元测试普遍遵循一个清晰的 AAA 模式 :Arrange (准备) , Act (执行) , Assert (断言)。
- [Fact] 特性: 用于标记一个无参数的、独立的测试用例。
- [Theory] 特性: 用于标记一个有参数的、数据驱动的测试用例,通常与 [InlineData] 或 [MemberData] 配合使用,可以用不同的数据多次运行同一个测试。
C#
using Xunit;
using Moq;
using Microsoft.Extensions.Logging;
using image_coper.Controllers;
using Microsoft.AspNetCore.Mvc;
namespace image_coper.Tests
{
public class Image3ControllerTests
{
// Arrange (准备)
private readonly Mock<ILogger<ImageController>> _mockLogger;
private readonly Image3Controller _controller;
// 构造函数会在每个测试方法运行前执行,适合做通用的准备工作
public Image3ControllerTests()
{
_mockLogger = new Mock<ILogger<ImageController>>();
var mockHttpClientFactory = new Mock<IHttpClientFactory>();
// 创建 Controller 实例,并注入"假的"依赖对象
_controller = new Image3Controller(_mockLogger.Object, mockHttpClientFactory.Object);
}
// 这是一个 Fact 测试,用于测试单一场景
[Fact]
public void GetImage_InvalidPath_ReturnsBadRequest()
{
// Act (执行)
var result = _controller.GetImage("../secret-file.txt");
// Assert (断言)
Assert.IsType<BadRequestObjectResult>(result);
}
// 这是一个 Theory 测试,用于测试多个相似场景
[Theory]
[InlineData("20251020", 2025, 10, 20)]
[InlineData("20240229", 2024, 2, 29)]
public void HandleTime_WithValidDateStrings_ReturnsCorrectDateTime(string input, int year, int month, int day)
{
var result = CommonHelper.HandleTime(input);
Assert.NotNull(result);
Assert.Equal(year, result.Value.Year);
}
}
}
步骤 2.4: 理解断言 (Assert)
Assert 类是 xUnit 的核心,用于验证结果是否符合预期。
分类 | 常用方法 | 作用 |
---|---|---|
相等性 | Assert.Equal(expected, actual) / NotEqual | 验证两个值相等/不相等。 |
布尔值 | Assert.True(condition) / False | 验证条件为 true / false。 |
Null/Not Null | Assert.Null(object) / NotNull | 验证对象为 null / 不为 null。 |
集合 | Assert.Contains(expected, collection) / DoesNotContain | 验证集合中包含/不包含某个元素。 |
类型 | Assert.IsType<T>(object) | 验证对象的精确类型是 T。 |
异常 | Assert.Throws<TException>(() => code) | 验证 code 代码块会抛出 TException 异常。 |
步骤 2.5: 理解模拟 (Mocking)
为什么需要 Mock? 单元测试的核心是隔离。我们只想测试 Image3Controller 的内部逻辑,而不希望测试真的去访问网络、文件系统或数据库。Mock 对象(由 Moq 库创建)就是这些外部依赖的"特技替身"。
Moq 的核心用法:
- new Mock<IYourInterface>(): 创建一个接口的模拟对象。
- .Object: 获取这个模拟对象的实例,用于依赖注入。
- .Setup(mock => mock.Method(args)).Returns(value): "编排"模拟对象的行为。告诉它当 Method 方法被以 args 参数调用时,应该返回 value。
- .Verify(mock => mock.Method(args), Times.Once): 验证 Method 方法是否被以 args 参数调用了指定的次数。
C#
[Fact]
public void GetUser_ExistingId_ReturnsUser()
{
// Arrange
var fakeUser = new User { Id = 1, Name = "Alice" };
var mockRepo = new Mock<IUserRepository>();
// Setup: 编排 Mock 对象的行为
mockRepo.Setup(repo => repo.GetUserById(1)).Returns(fakeUser);
var controller = new UserController(mockRepo.Object);
// Act
var result = controller.GetUser(1);
// Assert
// ...
}
3. 运行与调试测试
Visual Studio 提供了强大的测试资源管理器来管理和运行测试。
- 打开测试资源管理器 : 通过顶部菜单 测试 (Test) -> 测试资源管理器 (Test Explorer) 打开。
- 自动发现: 该窗口会自动发现您项目中所有被 [Fact] 或 [Theory] 标记的测试方法。
- 运行测试 :
- 点击窗口左上角的 "全部运行" 按钮(绿色播放按钮)来运行所有测试。
- 在某个测试分组或单个测试上右键,选择 "运行"。
- 调试测试 :
- 在您的测试代码 或被测的业务代码中设置断点。
- 在测试资源管理器中,在某个测试上右键,选择 "调试"。
- 调试会话会直接从该测试开始,让您能快速、隔离地调试特定逻辑,这通常比启动整个应用进行调试要高效得多。
4. 集成测试简介
与单元测试不同,集成测试需要与真实的外部系统(或其测试版本)进行交互。
- 配置: 通常需要一个专用的测试数据库(例如 SQLite 内存数据库)和一个临时的文件系统目录。
- 工具 : 对于 ASP.NET Core API,Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<T> 是进行集成测试的利器。它可以在内存中启动您的整个应用程序,并提供一个 HttpClient 让你能像真实客户端一样发送 HTTP 请求。
- 关注点: 验证从 HTTP 请求到数据库(或文件系统)操作,再到 HTTP 响应的完整流程是否正确。它能发现单元测试无法发现的配置错误、连接问题和组件间协作问题。