练习 - 使用 GitHub Copilot 工具开发单元测试

已完成

GitHub Copilot 有助于为代码编写单元测试。 可通过多种方式使用 GitHub Copilot 生成单元测试:

  • 生成测试用例:可使用 GitHub Copilot 为代码生成测试用例。 Copilot 可根据你已编写的代码来建议测试用例。 你随后可使用这些测试用例为代码创建单元测试。
  • 生成测试方法:Copilot 还可为代码生成测试方法。 你可使用这些测试方法为代码创建单元测试。
  • 生成测试断言:Copilot 可推荐你可在单元测试中使用的断言。 这些断言可帮助你验证代码的行为。
  • 生成测试模拟:Copilot 可生成你可在单元测试中使用的模拟对象。 这些模拟对象有助于将要测试的代码与其依赖项隔离。
  • 生成测试数据:Copilot 可生成你可在单元测试中使用的测试数据。 此测试数据有助于使用不同的输入测试代码。
  • 生成测试设置代码:Copilot 可为单元测试生成设置代码。 此代码有助于在运行测试之前设置测试环境。
  • 生成测试清理代码:Copilot 可为单元测试生成清理代码。 此代码有助于在运行测试之后清理测试环境。

现有解决方案包括名为 UnitTests 的单元测试项目。 此项目已包含部分涵盖 ApplicationCore 项目中的服务类的单元测试。

在本练习中,使用 GitHub Copilot 完成以下任务:

  1. 检查 UnitTests 项目实现的单元测试方法。

  2. 扩展 UnitTests 项目以开始测试 Library.Infrastructure 项目中的数据访问类。

检查 UnitTests 项目实现的单元测试方法

现有代码库包括实现以下文件夹结构的 UnitTests 项目:

  • UnitTests\
    • ApplicationCore\
      • LoanService\
        • ExtendLoan.cs
        • ReturnLoan.cs
      • PatronService\
        • RenewMembership.cs
    • LoanFactory.cs
    • PatronFactory.cs

此结构反映并支持 Services 项目的 ApplicationCore 部分。

  • ApplicationCore\
    • Services\
      • LoanService.cs:包含 ExtendLoanReturnLoan 方法。
      • PatronService.cs:包含 RenewMembership 方法。

在练习的本节中,使用 GitHub Copilot 检查 UnitTests 项目实现的单元测试方法。

请使用以下步骤完成本练习的这一部分:

  1. 打开“聊天”视图。

  2. 将以下文件添加到聊天上下文:LoanFactory.csPatronFactory.csExtendLoan.csReturnLoan.csRenewMembership.csLoanService.csPatronService.cs

    文件位于 tests/UnitTeststests/UnitTests/ApplicationCore/LoanServicetests/UnitTests/ApplicationCore/PatronServicesrc/Library.ApplicationCore/Services 文件夹中。

  3. 输入以下提示:

    @workspace Explain the approach to unit testing that's been implemented in this workspace.
    

    此提示要求 GitHub Copilot 说明工作区上下文中的单元测试方法。 提示中包含多个文件,以提供响应的上下文。

  4. 花点时间查看 GitHub Copilot 的响应。

    你应会看到类似于以下描述的说明:

    • 模拟依赖项:测试使用 NSubstitute 创建接口的模拟实现。
    • 测试组织:测试根据要测试的方法被组织到单独的文件中。 例如,ExtendLoanReturnLoan 方法在 LoanService 文件夹下的单独文件中进行测试。
    • 断言:断言用于验证每个测试的预期结果。
    • 测试场景:测试涵盖每种方法的各种场景,例如基于输入返回不同的状态。
    • 隔离性:每个测试都与其他测试相隔离,以防止副作用。
    • 工厂方法:工厂方法用于一致地创建测试数据。
  5. 输入以下提示:

    @workspace What are the benefits of this approach to unit testing?
    
  6. 花点时间查看 GitHub Copilot 的响应。

    你应会看到一个说明,它标识了类似于以下列表的优势列表:

    • 依赖项的隔离:使用模拟可将工作单元与其依赖项隔离开来。
    • 可重复性和一致性:工厂方法提供了创建测试数据的一致方法。
    • 可读性和可维护性:描述性测试方法和明确的测试步骤安排提高了测试的可读性。
    • 全面覆盖:测试各种场景可确保代码可靠并处理不同的边缘情况。
    • 分离关注点:测试数据创建与测试逻辑本身的分离使测试保持干净,并专注于要测试的行为。
    • 即时反馈:运行单元测试提供了关于代码正确性的即时反馈。
  7. 将以下文件添加到聊天上下文:ExtendLoan.csReturnLoan.csRenewMembership.csLoanFactory.csPatronFactory.csLoanService.csPatronService.cs

  8. 输入以下提示:

    @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 项目中测试方法的过程概述。 提示中包含多个文件,以提供响应的上下文。

  9. 花点时间查看 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 类编写单元测试。

请使用以下步骤完成本练习的这一部分:

  1. 将以下文件添加到聊天上下文:UnitTests.csproj

  2. 在“聊天”视图中输入以下提示:

    @workspace Explain how to add a reference to the Library.Infrastructure project inside `UnitTests.csproj`.
    

    此提示要求 GitHub Copilot 说明如何在 Library.Infrastructure 文件中添加对 UnitTests.csproj 项目的引用。

  3. 使用 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>
    
  4. 打开 JsonLoanRepository.cs 文件。

    JsonLoanRepository.cs 位于 src/Library.Infrastructure/Data/ 文件夹中。

  5. 花点时间查看 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();
            }
        }
    }
    
  6. 注意以下有关 JsonLoanRepository 类的详细信息:

    • JsonLoanRepository 类包含两种方法:GetLoanUpdateLoan
    • JsonLoanRepository 类使用 JsonData 对象来加载和保存贷款数据。

    首先,为 GetLoan 方法编写单元测试。

  7. UnitTests 项目下创建以下文件夹结构。

    • Infrastructure\
      • JsonLoanRepository\

    此文件夹结构反映用于 Library.ApplicationCore 单元测试的方法。

  8. GetLoan 文件夹中创建名为 JsonLoanRepository 的类文件。

  9. 花点时间考虑 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 接口创建一个对象。

  10. 将以下文件添加到聊天上下文:JsonLoanRepository.csReturnLoan.csLoanService.csLoanFactory.csJsonData.cs

  11. 输入以下提示:

    @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 推荐字段和类构造函数。

  12. 花点时间查看 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
    }
    
  13. 使用 GitHub Copilot 提供的代码建议更新 GetLoan.cs

  14. 注意以下问题:

    • 命名空间与代码中 JsonLoanRepository 的使用之间存在冲突。 应更新 GetLoans.cs 中的命名空间,以遵循 ReturnLoan.csRenewMembership.cs 文件中使用的模式。

    • 如果代码中无法识别 ILoanRepository,则可能需要向文件顶部添加 usingLibrary.ApplicationCore 指令。

    • 如果 _configuration 对象未正确实例化,则可能需要更新包含 ConfigurationBuilder 的代码行。 可以简化代码以使用 _configuration = new ConfigurationBuilder().Build();

    • 如果 GitHub Copilot 建议 using Library.ApplicationCore.Interfaces,则可以从文件顶部将其删除。

  15. 更新 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);
        }
    
    }
    
  16. 将以下文件添加到聊天上下文:JsonLoanRepository.csReturnLoan.csLoanService.csLoanFactory.csJsonData.csLoans.json

  17. 选择 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 文件中。

  18. 花点时间查看 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);
        }
    }
    
  19. 使用 GitHub Copilot 提供的代码建议更新 GetLoan.cs

    JsonLoanRepository.GetLoan 方法调用 _jsonData.EnsureDataLoaded 以确保加载 JSON 数据。 可删除测试方法中调用 _jsonData.EnsureDataLoaded 的代码行(如包含)。

    如果代码中无法识别 Loan 类,请确保 GetLoan.cs 文件的顶部有 using Library.ApplicationCore.Entities 语句。 Loan 类位于 Library.ApplicationCore.Entities 命名空间中。

  20. 生成 AccelerateDevGitHubCopilot 解决方案以确保不会出现错误。

  21. 对于找不到贷款 ID 的情况,使用 GitHub Copilot 的自动完成功能创建测试。

    GetLoan_ReturnsLoanWhenLoanIdIsFound 方法后面创建一个空白行。

    接受自动完成建议以创建新的测试方法。

  22. 花点时间查看 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);
    }
    
    
  23. 接受自动完成建议,该建议会指定一个数据集中没有的 loanId 值。

    如果没有任何建议分配不在数据集中的 loanId 号码,则可以使用 Ctrl+Enter 键盘快捷方式列出其他建议

  24. 注意,单元测试需要对 JSON 数据文件的访问权限。

    JsonLoanRepository.GetLoan 方法使用 JsonData 对象来加载和保存贷款数据。

    JSON 数据文件位于 Library.Console\Json 文件夹中。 需要更新 UnitTests.csproj 文件以在测试项目中包括这些文件。

  25. 将以下 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 命令。

请使用以下步骤完成本练习的这一部分:

  1. 确保已在编辑器中打开 GetLoans.cs 文件。

  2. 生成解决方案并确保没有错误。

    右键单击“AccelerateDevGitHubCopilot”,然后选择“生成”

  3. 注意测试方法左侧的“绿色播放按钮”。

  4. 打开 Visual Studio Code 的“测试资源管理器”视图。

    若要打开“测试资源管理器”视图,请选择左侧活动栏上的烧杯状图标。 测试资源管理器在用户界面中标记为“正在测试”。

    测试资源管理器是一个树状视图,显示了工作区中的所有测试用例。 可使用测试资源管理器运行/调试测试用例并查看测试结果。

  5. 展开 UnitTests 和基础节点以查找 GetLoanTest

  6. 运行“JsonLoanRepository.GetLoan:找到贷款 ID 时返回贷款”测试用例。

  7. 注意“测试资源管理器”视图和编辑器中的测试结果。

    你应会看到一个绿色复选标记,指示测试已通过。

  8. 使用编辑器运行“JsonLoanRepository.GetLoan:找不到贷款 ID 时返回 null”测试用例。

  9. 注意“测试资源管理器”视图和编辑器中的测试结果。

    若要从编辑器运行测试,请选择测试方法左侧的绿色“开始”按钮。

  10. 确保“JsonLoanRepository.GetLoan:找不到贷款 ID 时返回 null”测试通过。

    你应会在两个测试的左侧看到一个绿色的复选标记。