练习 - 使用 GitHub Copilot 工具开发单元测试
GitHub Copilot 有助于为代码编写单元测试。 可通过多种方式使用 GitHub Copilot 生成单元测试:
- 生成测试用例:可使用 GitHub Copilot 为代码生成测试用例。 Copilot 可根据你已编写的代码来建议测试用例。 你随后可使用这些测试用例为代码创建单元测试。
- 生成测试方法:Copilot 还可为代码生成测试方法。 你可使用这些测试方法为代码创建单元测试。
- 生成测试断言:Copilot 可推荐你可在单元测试中使用的断言。 这些断言可帮助你验证代码的行为。
- 生成测试模拟:Copilot 可生成你可在单元测试中使用的模拟对象。 这些模拟对象有助于将要测试的代码与其依赖项隔离。
- 生成测试数据:Copilot 可生成你可在单元测试中使用的测试数据。 此测试数据有助于使用不同的输入测试代码。
- 生成测试设置代码:Copilot 可为单元测试生成设置代码。 此代码有助于在运行测试之前设置测试环境。
- 生成测试清理代码:Copilot 可为单元测试生成清理代码。 此代码有助于在运行测试之后清理测试环境。
现有解决方案包括名为 UnitTests 的单元测试项目。 此项目已包含部分涵盖 ApplicationCore 项目中的服务类的单元测试。
在本练习中,使用 GitHub Copilot 完成以下任务:
检查 UnitTests 项目实现的单元测试方法。
扩展 UnitTests 项目以开始测试
Library.Infrastructure项目中的数据访问类。
检查 UnitTests 项目实现的单元测试方法
现有代码库包括实现以下文件夹结构的 UnitTests 项目:
- UnitTests\
- ApplicationCore\
- LoanService\
ExtendLoan.csReturnLoan.cs
- PatronService\
RenewMembership.cs
- LoanService\
- LoanFactory.cs
- PatronFactory.cs
- ApplicationCore\
此结构反映并支持 Services 项目的 ApplicationCore 部分。
- ApplicationCore\
- Services\
- LoanService.cs:包含
ExtendLoan和ReturnLoan方法。 - PatronService.cs:包含
RenewMembership方法。
- LoanService.cs:包含
- Services\
在练习的本节中,使用 GitHub Copilot 检查 UnitTests 项目实现的单元测试方法。
请使用以下步骤完成本练习的这一部分:
打开“聊天”视图。
将以下文件添加到聊天上下文:
LoanFactory.cs、PatronFactory.cs、ExtendLoan.cs、ReturnLoan.cs、RenewMembership.cs、LoanService.cs、PatronService.cs。文件位于
tests/UnitTests、tests/UnitTests/ApplicationCore/LoanService、tests/UnitTests/ApplicationCore/PatronService和src/Library.ApplicationCore/Services文件夹中。输入以下提示:
@workspace Explain the approach to unit testing that's been implemented in this workspace.此提示要求 GitHub Copilot 说明工作区上下文中的单元测试方法。 提示中包含多个文件,以提供响应的上下文。
花点时间查看 GitHub Copilot 的响应。
你应会看到类似于以下描述的说明:
- 模拟依赖项:测试使用
NSubstitute创建接口的模拟实现。 - 测试组织:测试根据要测试的方法被组织到单独的文件中。 例如,
ExtendLoan和ReturnLoan方法在LoanService文件夹下的单独文件中进行测试。 - 断言:断言用于验证每个测试的预期结果。
- 测试场景:测试涵盖每种方法的各种场景,例如基于输入返回不同的状态。
- 隔离性:每个测试都与其他测试相隔离,以防止副作用。
- 工厂方法:工厂方法用于一致地创建测试数据。
- 模拟依赖项:测试使用
输入以下提示:
@workspace What are the benefits of this approach to unit testing?花点时间查看 GitHub Copilot 的响应。
你应会看到一个说明,它标识了类似于以下列表的优势列表:
- 依赖项的隔离:使用模拟可将工作单元与其依赖项隔离开来。
- 可重复性和一致性:工厂方法提供了创建测试数据的一致方法。
- 可读性和可维护性:描述性测试方法和明确的测试步骤安排提高了测试的可读性。
- 全面覆盖:测试各种场景可确保代码可靠并处理不同的边缘情况。
- 分离关注点:测试数据创建与测试逻辑本身的分离使测试保持干净,并专注于要测试的行为。
- 即时反馈:运行单元测试提供了关于代码正确性的即时反馈。
将以下文件添加到聊天上下文:
ExtendLoan.cs、ReturnLoan.cs、RenewMembership.cs、LoanFactory.cs、PatronFactory.cs、LoanService.cs、PatronService.cs。输入以下提示:
@workspace How can I extend the `UnitTests` project to begin testing methods in the `Library.Infrastructure` project? Provide a process overview.此提示要求 GitHub Copilot 提供一个扩展
UnitTests项目以开始在Library.Infrastructure项目中测试方法的过程概述。 提示中包含多个文件,以提供响应的上下文。花点时间查看 GitHub Copilot 的响应。
你应会看到类似于以下描述的说明:
1. Add Project Reference: - Open the `UnitTests.csproj` file located at `tests/UnitTests/UnitTests.csproj`. - Add a reference to the `Library.Infrastructure` project. 2. Identify methods to test: - Identify the methods in the `Library.Infrastructure` project that need to be tested. For example, if you want to test the `LoanService` class, identify the methods in the `LoanService` class that need to be tested. 3. Create Test Classes: - Create new test classes in the `tests/UnitTests` directory to test the methods in the `Library.Infrastructure` project. For example, if you want to test `LoanService`, create a file named `LoanServiceTests.cs`. 4. Write Unit Tests: - In the newly created test classes, write unit tests for the methods in the `Library.Infrastructure` project. Use the xUnit framework for writing tests and NSubstitute for mocking dependencies. 5. Run Tests: - Use Visual Studio Code's integrated terminal or the Test Explorer to run the tests. - Ensure that the tests are discovered and executed correctly.
扩展 UnitTests 项目以开始测试数据访问类
在练习的本节中,使用 GitHub Copilot 帮助为 Library.Infrastructure 项目创建单元测试。
Library.Infrastructure 项目包含与文件系统交互以加载和保存数据的数据访问类。 该项目包括以下类:
- JsonData:加载和保存 JSON 数据的类。
- JsonLoanRepository:实现 ILoanRepository 接口并使用 JsonData 类加载和保存贷款数据的类。
- JsonPatronRepository:实现 IPatronRepository 接口并使用 JsonData 类加载和保存顾客数据的类。
首先,为 JsonLoanRepository 类编写单元测试。
请使用以下步骤完成本练习的这一部分:
将以下文件添加到聊天上下文:
UnitTests.csproj。在“聊天”视图中输入以下提示:
@workspace Explain how to add a reference to the Library.Infrastructure project inside `UnitTests.csproj`.此提示要求 GitHub Copilot 说明如何在
Library.Infrastructure文件中添加对UnitTests.csproj项目的引用。使用 GitHub Copilot 响应更新 UnitTests.csproj 文件。
更新后的 UnitTests.csproj 文件应类似于以下 XML 代码:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> <IsTestProject>true</IsTestProject> </PropertyGroup> <ItemGroup> <PackageReference Include="coverlet.collector" Version="6.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="xunit" Version="2.5.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" /> </ItemGroup> <ItemGroup> <Using Include="Xunit" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\src\Library.ApplicationCore\Library.ApplicationCore.csproj" /> <ProjectReference Include="..\..\src\Library.Infrastructure\Library.Infrastructure.csproj" /> </ItemGroup> </Project>打开
JsonLoanRepository.cs文件。JsonLoanRepository.cs位于src/Library.Infrastructure/Data/文件夹中。花点时间查看
JsonLoanRepository.cs文件。using Library.ApplicationCore; using Library.ApplicationCore.Entities; namespace Library.Infrastructure.Data; public class JsonLoanRepository : ILoanRepository { private readonly JsonData _jsonData; public JsonLoanRepository(JsonData jsonData) { _jsonData = jsonData; } public async Task<Loan?> GetLoan(int id) { await _jsonData.EnsureDataLoaded(); foreach (Loan loan in _jsonData.Loans!) { if (loan.Id == id) { Loan populated = _jsonData.GetPopulatedLoan(loan); return populated; } } return null; } public async Task UpdateLoan(Loan loan) { Loan? existingLoan = null; foreach (Loan l in _jsonData.Loans!) { if (l.Id == loan.Id) { existingLoan = l; break; } } if (existingLoan != null) { existingLoan.BookItemId = loan.BookItemId; existingLoan.PatronId = loan.PatronId; existingLoan.LoanDate = loan.LoanDate; existingLoan.DueDate = loan.DueDate; existingLoan.ReturnDate = loan.ReturnDate; await _jsonData.SaveLoans(_jsonData.Loans!); await _jsonData.LoadData(); } } }注意以下有关
JsonLoanRepository类的详细信息:-
JsonLoanRepository类包含两种方法:GetLoan和UpdateLoan。 -
JsonLoanRepository类使用JsonData对象来加载和保存贷款数据。
首先,为
GetLoan方法编写单元测试。-
在
UnitTests项目下创建以下文件夹结构。- Infrastructure\
- JsonLoanRepository\
此文件夹结构反映用于
Library.ApplicationCore单元测试的方法。- Infrastructure\
在
GetLoan文件夹中创建名为JsonLoanRepository的类文件。花点时间考虑
GetLoan单元测试的字段和类构造函数要求。JsonLoanRepository.GetLoan方法在被调用时接收贷款 ID 参数。 该方法使用_jsonData.EnsureDataLoaded获取最新的 JSON 数据,并使用_jsonData.Loans搜索匹配的贷款。 如果该方法找到匹配的贷款 ID,它将返回填充的贷款对象 (populated)。 如果该方法找不到匹配的贷款 ID,则返回null。对于 GetLoan 单元测试:
可使用一个模拟贷款存储库对象 (
_mockLoanRepository) 来帮助测试找到匹配 ID 的情况。 使用要查找的 ID 加载模拟。ReturnLoanTest类演示如何模拟ILoanRepository接口并实例化一个模拟贷款存储库对象。可使用一个非模拟贷款存储库对象 (
_jsonLoanRepository) 来测试未找到匹配 ID 的情况。 只需指定一个文件中没有的贷款 ID 即可(任何超过 100 的 ID 均可)。需要一个
JsonData对象来创建非模拟JsonLoanRepository对象。 由于UnitTests项目无权访问由JsonData项目创建的ConsoleApp对象,因此你需要使用IConfiguration接口创建一个对象。
将以下文件添加到聊天上下文:
JsonLoanRepository.cs、ReturnLoan.cs、LoanService.cs、LoanFactory.cs、JsonData.cs。输入以下提示:
@workspace Create fields and a class constructor for the `GetLoan.cs` file. The class will be used to create unit tests for the GetLoan method in the `JsonLoanRepository.cs` file. Create the following private readonly fields: `_mockLoanRepository`, `_jsonLoanRepository`, `_configuration`, and `_jsonData`. Instantiate the fields in the `GetLoanTest` constructor. Use `ConfigurationBuilder` to create a `_configuration` object that can be used to instantiate the JsonData object.此提示要求 GitHub Copilot 推荐字段和类构造函数。
花点时间查看 GitHub Copilot 的响应。
你应会看到类似于以下代码片段的代码建议:
using NSubstitute; using Library.ApplicationCore; using Library.ApplicationCore.Entities; using Library.ApplicationCore.Interfaces; using Library.Infrastructure.Data; using Microsoft.Extensions.Configuration; namespace UnitTests.Infrastructure.JsonLoanRepository; public class GetLoanTest { private readonly ILoanRepository _mockLoanRepository; private readonly JsonLoanRepository _jsonLoanRepository; private readonly IConfiguration _configuration; private readonly JsonData _jsonData; public GetLoanTest() { _mockLoanRepository = Substitute.For<ILoanRepository>(); _configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); _jsonData = new JsonData(_configuration); _jsonLoanRepository = new JsonLoanRepository(_jsonData); } // Add test methods here }使用 GitHub Copilot 提供的代码建议更新
GetLoan.cs。注意以下问题:
命名空间与代码中
JsonLoanRepository的使用之间存在冲突。 应更新 GetLoans.cs 中的命名空间,以遵循ReturnLoan.cs和RenewMembership.cs文件中使用的模式。如果代码中无法识别
ILoanRepository,则可能需要向文件顶部添加using的Library.ApplicationCore指令。如果
_configuration对象未正确实例化,则可能需要更新包含ConfigurationBuilder的代码行。 可以简化代码以使用_configuration = new ConfigurationBuilder().Build();。如果 GitHub Copilot 建议
using Library.ApplicationCore.Interfaces,则可以从文件顶部将其删除。
更新
GetLoan.cs文件以匹配以下代码片段:using NSubstitute; using Library.ApplicationCore; using Library.ApplicationCore.Entities; using Library.Infrastructure.Data; using Microsoft.Extensions.Configuration; namespace UnitTests.Infrastructure.JsonLoanRepositoryTests; public class GetLoanTest { private readonly ILoanRepository _mockLoanRepository; private readonly JsonLoanRepository _jsonLoanRepository; private readonly IConfiguration _configuration; private readonly JsonData _jsonData; public GetLoanTest() { _mockLoanRepository = Substitute.For<ILoanRepository>(); _configuration = new ConfigurationBuilder().Build(); _jsonData = new JsonData(_configuration); _jsonLoanRepository = new JsonLoanRepository(_jsonData); } }将以下文件添加到聊天上下文:
JsonLoanRepository.cs、ReturnLoan.cs、LoanService.cs、LoanFactory.cs、JsonData.cs、Loans.json。选择
GetLoan.cs文件的内容,然后在“聊天”视图中输入以下提示:@workspace Update the selection to include a unit test for the `JsonLoanRepository.GetLoan` method. The unit test should test the case where a loan ID is found in the data. Use `_mockLoanRepository` to arrange the expected return loan. Use `_jsonLoanRepository` to return an actual loan. Asserts should verify that the return loan ID matches the expected loan ID. Use a loan ID that exists in the `Loans.json` file.此提示要求 GitHub Copilot 为
JsonLoanRepository.GetLoan方法推荐单元测试。 该单元测试应测试在数据中找到贷款 ID 的情况。 该测试应使用_mockLoanRepository来安排预期的归还贷款,使用_jsonLoanRepository来返回实际的贷款,并使用断言来验证归还贷款 ID 是否与预期的贷款 ID 相匹配。 贷款 ID 应存在于Loans.json文件中。花点时间查看 GitHub Copilot 的响应。
你应会看到类似于以下代码片段的代码建议:
using NSubstitute; using Library.ApplicationCore; using Library.ApplicationCore.Entities; using Library.Infrastructure.Data; using Microsoft.Extensions.Configuration; using Xunit; namespace UnitTests.Infrastructure.JsonLoanRepositoryTests; public class GetLoanTest { private readonly ILoanRepository _mockLoanRepository; private readonly JsonLoanRepository _jsonLoanRepository; private readonly IConfiguration _configuration; private readonly JsonData _jsonData; public GetLoanTest() { _mockLoanRepository = Substitute.For<ILoanRepository>(); _configuration = new ConfigurationBuilder().Build(); _jsonData = new JsonData(_configuration); _jsonLoanRepository = new JsonLoanRepository(_jsonData); } [Fact(DisplayName = "JsonLoanRepository.GetLoan: Returns loan when loan ID is found")] public async Task GetLoan_ReturnsLoanWhenLoanIdIsFound() { // Arrange var loanId = 1; // Use a loan ID that exists in the Loans.json file var expectedLoan = new Loan { Id = loanId, BookItemId = 101, PatronId = 202, LoanDate = DateTime.Now, DueDate = DateTime.Now.AddDays(14) }; _mockLoanRepository.GetLoan(loanId).Returns(expectedLoan); await _jsonData.EnsureDataLoaded(); // Ensure data is loaded // Act var actualLoan = await _jsonLoanRepository.GetLoan(loanId); // Assert Assert.NotNull(actualLoan); Assert.Equal(expectedLoan.Id, actualLoan?.Id); } }使用 GitHub Copilot 提供的代码建议更新
GetLoan.cs。JsonLoanRepository.GetLoan方法调用_jsonData.EnsureDataLoaded以确保加载 JSON 数据。 可删除测试方法中调用_jsonData.EnsureDataLoaded的代码行(如包含)。如果代码中无法识别
Loan类,请确保 GetLoan.cs 文件的顶部有using Library.ApplicationCore.Entities语句。Loan类位于Library.ApplicationCore.Entities命名空间中。生成
AccelerateDevGitHubCopilot解决方案以确保不会出现错误。对于找不到贷款 ID 的情况,使用 GitHub Copilot 的自动完成功能创建测试。
在
GetLoan_ReturnsLoanWhenLoanIdIsFound方法后面创建一个空白行。接受自动完成建议以创建新的测试方法。
花点时间查看 GitHub Copilot 的响应。
GitHub Copilot 的自动完成功能可能会模拟预期的贷款(即使不需要它)。 可删除模拟预期贷款的代码,但需要一个
Loans.json文件中不存在的贷款 ID。你应会看到类似于以下代码片段之一的代码建议:
[Fact(DisplayName = "JsonLoanRepository.GetLoan: Returns null when loan ID is not found")] public async Task GetLoan_ReturnsNullWhenLoanIdIsNotFound() { // Arrange var loanId = 999; // Use a loan ID that does not exist in the Loans.json file var expectedLoan = new Loan { Id = loanId, BookItemId = 101, PatronId = 202, LoanDate = DateTime.Now, DueDate = DateTime.Now.AddDays(14) }; _mockLoanRepository.GetLoan(loanId).Returns(expectedLoan); // Act var actualLoan = await _jsonLoanRepository.GetLoan(loanId); // Assert Assert.Null(actualLoan); }接受自动完成建议,该建议会指定一个数据集中没有的
loanId值。如果没有任何建议分配不在数据集中的
loanId号码,则可以使用 Ctrl+Enter 键盘快捷方式列出其他建议。注意,单元测试需要对 JSON 数据文件的访问权限。
JsonLoanRepository.GetLoan方法使用JsonData对象来加载和保存贷款数据。JSON 数据文件位于
Library.Console\Json文件夹中。 需要更新UnitTests.csproj文件以在测试项目中包括这些文件。将以下 XML 片段添加到
UnitTests.csproj文件:<ItemGroup> <None Include="..\..\src\Library.Console\Json\**\*"> <Link>Json\%(RecursiveDir)%(FileName)%(Extension)</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>这可确保在运行测试时将 JSON 数据文件复制到输出目录。
检查你的工作
有多种方法可运行 JsonLoanRepository 类的单元测试。 可使用 Visual Studio Code 的测试资源管理器、集成终端或 dotnet test 命令。
请使用以下步骤完成本练习的这一部分:
确保已在编辑器中打开 GetLoans.cs 文件。
生成解决方案并确保没有错误。
右键单击“AccelerateDevGitHubCopilot”,然后选择“生成”。
注意测试方法左侧的“绿色播放按钮”。
打开 Visual Studio Code 的“测试资源管理器”视图。
若要打开“测试资源管理器”视图,请选择左侧活动栏上的烧杯状图标。 测试资源管理器在用户界面中标记为“正在测试”。
测试资源管理器是一个树状视图,显示了工作区中的所有测试用例。 可使用测试资源管理器运行/调试测试用例并查看测试结果。
展开 UnitTests 和基础节点以查找
GetLoanTest。运行“JsonLoanRepository.GetLoan:找到贷款 ID 时返回贷款”测试用例。
注意“测试资源管理器”视图和编辑器中的测试结果。
你应会看到一个绿色复选标记,指示测试已通过。
使用编辑器运行“JsonLoanRepository.GetLoan:找不到贷款 ID 时返回 null”测试用例。
注意“测试资源管理器”视图和编辑器中的测试结果。
若要从编辑器运行测试,请选择测试方法左侧的绿色“开始”按钮。
确保“JsonLoanRepository.GetLoan:找不到贷款 ID 时返回 null”测试通过。
你应会在两个测试的左侧看到一个绿色的复选标记。