소스 생성기 만들기 3부 - 통합 테스트 및 NuGet 패키징

이 글은 Andrew Lock님의 소스 생성기 만들기 연재를 번역한 글입니다.

소스 생성기 만들기 연재의 세 번째 게시물 입니다.

이 시리즈의 첫 번째 게시물에서는 .NET 6 증분 소스 생성기를 만드는 방법을 설명했고, 두 번째 게시물에서는 Verify와 함께 스냅샷 테스트를 사용하여 생성기를 단위 테스트하는 방법을 설명했습니다. 이것은 소스 생성기를 구축하는 데 필수적인 첫 번째 단계이며 스냅샷 테스트는 빠르고 쉽게 디버깅할 수 있는 테스트 접근 방식을 제공합니다.

패키지 테스트의 또 다른 필수 부분은 통합 테스트입니다. 즉, 프로젝트 컴파일 프로세스의 일부로 실제로 사용될 소스 생성기를 테스트하는 것을 의미합니다. 마찬가지로 소스 생성기를 NuGet 패키지로 제공하려는 경우 프로젝트를 사용할 때 NuGet 패키지가 올바르게 작동하는지 테스트해야 합니다.

이 게시물에서는 이 시리즈에서 만든 소스 생성기에 대해 3가지 작업을 수행할 것입니다:

  • 통합 테스트 프로젝트 만들기
  • NuGet 패키지 만들기
  • 통합 테스트 프로젝트에서 NuGet 패키지 테스트

이 게시물의 모든 내용은 이전 게시물의 작업을 기반으로 하므로 혼란스러운 부분을 찾으면 다시 참조하세요!

  1. 통합 테스트 프로젝트 생성
  2. 통합 테스트 추가
  3. NuGet 패키지 만들기
  4. 사용자 지정 NuGet 구성으로 로컬 NuGet 패키지 소스 만들기
  5. NuGet 패키지 테스트 프로젝트 추가
  6. NuGet 패키지 통합 테스트 실행

1. 통합 테스트 프로젝트 생성

첫 번째 단계는 통합 테스트 프로젝트를 만드는 것입니다. 다음 스크립트는 새 xunit 테스트 프로젝트를 만들고 솔루션에 추가하고 소스 생성기 프로젝트에 대한 참조를 추가합니다.

dotnet new xunit -o ./tests/NetEscapades.EnumGenerators.IntegrationTests
dotnet sln add ./tests/NetEscapades.EnumGenerators.IntegrationTests
dotnet add ./tests/NetEscapades.EnumGenerators.IntegrationTests reference ./src/NetEscapades.EnumGenerators

그러면 다음과 같이 테스트 프로젝트와 소스 생성기 프로젝트 간에 일반 프로젝트 참조가 생성됩니다.

<ProjectReference Include="..\..\src\NetEscapades.EnumGenerators\NetEscapades.EnumGenerators.csproj" />

불행히도 소스 생성기(또는 분석기) 프로젝트의 경우 올바르게 작동하도록 이 요소를 약간 조정해야 합니다. 특히 OutputItemTypeReferenceOutputAssembly 속성을 추가해야 합니다.

  • OutputItemType="Analyzer"는 컴파일 프로세스의 일부로 프로젝트를 로드하도록 컴파일러에 지시합니다.
  • ReferenceOutputAssembly="false"는 소스 생성기 프로젝트의 dll을 참조하지 않도록 프로젝트에 지시합니다.

그러면 다음과 유사한 프로젝트 참조가 제공됩니다.

<ProjectReference Include="..\..\src\NetEscapades.EnumGenerators\NetEscapades.EnumGenerators.csproj"
    OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

이러한 변경으로 인해 통합 테스트 프로젝트는 다음과 같아야 합니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <!-- 👇 이 프로젝트 참조는 스크립트에 의해 추가되었습니다. -->
    <ProjectReference Include="..\..\src\NetEscapades.EnumGenerators\NetEscapades.EnumGenerators.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
                <!-- 👆 하지만 이 속성은 직접 추가 해야 합니다. -->
  </ItemGroup>

  <!-- 👇 기본적으로 템플릿에 추가됨 -->
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.1.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>

프로젝트 파일이 정렬되면 소스 생성기가 올바르게 작동하는지 확인하기 위해 몇 가지 기본 테스트를 추가해 보겠습니다!

2. 통합 테스트 추가

테스트에 가장 먼저 필요한 것은 소스 생성기가 확장 클래스를 생성하기 위한 enum형입니다. 다음은 매우 기본적인 것이지만 추가 복잡성을 위해 [Flags] 속성을 지정했습니다. 이것은 필수는 아니지만 처리해야 하는 테스트에 대한 약간 더 복잡한 예입니다.

using System;

namespace NetEscapades.EnumGenerators.IntegrationTests;

[EnumExtensions]
[Flags]
public enum Colour
{
    Red = 1,
    Blue = 2,
    Green = 4,
}

초기 테스트에서 우리는 단순히 두 가지를 확인할 것입니다:

  • 소스 생성기는 enum[EnumExtensions] 특성으로 장식될 때 ToStringFast()라는 확장 메서드를 생성합니다.
  • ToStringFast() 호출 결과는 ToString() 호출과 동일합니다.

다음 테스트가 바로 그 작업을 수행합니다. 다음을 포함하여 Colour enum에 대해 5가지 다른 값을 테스트합니다:

  • 유요한 값 (Colour.Red)
  • 잘못된 값 ((Colour)15)
  • 합성 값 (Colour.Green | Colour.Blue)

이 테스트는 확장이 존재하고(그렇지 않으면 컴파일되지 않음) 위의 모든 값에 대해 예상되는 결과를 얻음을 확인합니다.

using Xunit;

namespace NetEscapades.EnumGenerators.IntegrationTests;

public class EnumExtensionsTests
{
    [Theory]
    [InlineData(Colour.Red)]
    [InlineData(Colour.Green)]
    [InlineData(Colour.Green | Colour.Blue)]
    [InlineData((Colour)15)]
    [InlineData((Colour)0)]
    public void FastToStringIsSameAsToString(Colour value)
    {
        var expected = value.ToString();
        var actual = value.ToStringFast();

        Assert.Equal(expected, actual);
    }
}

그리고 그게 전부. 솔루션에서 dotnet test를 실행하여 모든 테스트를 실행할 수 있습니다.

소스 생성기를 변경하는 경우 통합 테스트 프로젝트가 변경 사항을 반영하기 전에 IDE를 닫았다가 다시 열어야 할 수 있습니다.

특정 프로젝트에 대한 소스 생성기를 생성하는 경우 이 수준의 통합 테스트만 있으면 됩니다. 그러나 소스 생성기를 더 광범위하게 공유할 계획이라면 NuGet 패키지를 만드는 것이 좋습니다.

3. NuGet 패키지 만들기

소스 생성기에서 NuGet 패키지를 만드는 것은 표준 라이브러리용 NuGet 패키지와 유사하지만 NuGet 패키지의 내용은 다르게 배치됩니다. 구체적으로 다음을 수행해야 합니다.

  • 빌드 출력이 NuGet 패키지의 analyzers/dotnet/cs 폴더에 있는지 확인합니다.
  • dll이 NuGet 패키지의 “일반” 폴더로 끝나지 않는지 확인합니다.

첫 번째로 프로젝트에 다음 <ItemGroup>이 있는지 확인합니다.

<ItemGroup>
  <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" 
      PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

이렇게 하면 소스 생성기 어셈블리가 NuGet 패키지의 올바른 위치에 패킹되어 컴파일러가 이를 분석기/소스 생성기로 로드할 수 있습니다.

또한 IncludeBuildOutput 속성을 false로 설정하여 소비하는 프로젝트가 소스 생성기 dll 자체에 대한 참조를 얻지 않도록 합니다.

<PropertyGroup>
  <IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

이를 통해 프로젝트를 dotnet pack 할 수 있습니다. 다음 예에서는 버전 번호를 0.1.0-beta로 설정하고 출력이 ./artifacts 폴더에 있는지 확인합니다.

dotnet pack -c Release -o ./artifacts -p:Version=0.1.0-beta

이렇게 하면 다음과 같은 NuGet 패키지가 생성됩니다.

NetEscapades.EnumGenerators.0.1.0-beta.nupkg

NuGet 패키지 탐색기에서 패키지를 여는 경우 레이아웃은 다음 이미지와 같아야 하며, dll은 analyzers/dotnet/cs 폴더 안에 있고 다른 dll/폴더는 포함되어 있지 않습니다.

이제 이 패키지를 테스트하는 것이 까다로워지는 부분입니다. 테스트하기 전에 NuGet 패키지를 리포지토리에 푸시하고 싶지 않습니다. 또한 이 패키지로 로컬 NuGet 캐시를 "오염"시키고 싶지 않습니다. 몇 개의 골대를 뛰어넘어야 합니다.

4. 사용자 지정 NuGet 구성으로 로컬 NuGet 패키지 소스 만들기

먼저 로컬 NuGet 패키지 소스를 만들어야 합니다. 기본적으로 dotnet restore를 실행하면 nuget.org에서 패키지가 복원되지만 NuGet 테스트 프로젝트에서 로컬 테스트 패키지를 사용하도록 하려고 합니다. 즉, 사용자 지정 복원 소스를 구성해야 합니다.

이를 수행하는 일반적인 방법은 nuget.config 파일을 만들고 추가 원본을 나열하는 것입니다. 원격 소스(예: nuget.org 또는 myget.org와 같은 비공개 NuGet 피드)와 NuGet 패키지의 폴더일 뿐인 “로컬” 소스를 모두 포함할 수 있습니다. 후자의 옵션이 바로 우리가 원하는 것입니다.

그러나 테스트를 위해 “기본” nuget.config 이름으로 구성 파일을 만들 필요는 없습니다. 그렇지 않으면 해당 소스가 솔루션의 모든 항목을 복원하는 데 사용되기 때문입니다. 이상적으로는 이 NuGet 통합 테스트 프로젝트에 대해 베타 패키지와 함께 로컬 소스만 사용하는 것이 좋습니다. 이를 달성하기 위해 구성 파일에 다른 이름을 지정하여 자동으로 사용되지 않도록 하고 필요한 경우 이 이름을 명시적으로 지정합니다.

다음 스크립트는 nuget.config 파일을 만들고 이름을 nuget.integration-tests.config로 바꾸고 ./artifacts 디렉터리를 local-packages(테스트 NuGet 패키지를 패키징한 곳)라는 nuget 소스로 추가합니다:

dotnet new nugetconfig 
mv nuget.config nuget.integration-tests.config
dotnet nuget add source ./artifacts -n local-packages --configfile nuget.integration-tests.config

결과 nuget.integration-tests.config 파일은 다음과 같습니다:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!--전역 NuGet 패키지 소스를 상속하려면 아래의 <clear/> 줄을 제거하세요.  -->
    <clear />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
    <add key="local-packages" value="./artifacts" />
  </packageSources>
</configuration>

이제 구성 파일이 있으므로 NuGet 통합 테스트 프로젝트를 만들 차례입니다.

5. NuGet 패키지 테스트 프로젝트 추가

이 게시물의 첫 번째 부분에서는 컴파일러에서 "인라인"을 실행할 때 소스 생성기가 올바르게 작동하는지 확인하기 위해 통합 테스트 프로젝트를 만들었습니다. NuGet 패키지 테스트의 경우 NuGet 패키지 테스트에서 “정상” 통합 테스트와 정확히 동일한 테스트 파일을 사용하여 중복을 줄이고 일관성을 보장하기 위해 약간의 MSBuild 속임수를 사용하겠습니다.

다음 스크립트는 새 xunit 테스트 프로젝트를 만들고 테스트 NuGet 패키지에 대한 참조를 추가합니다:

dotnet new xunit -o ./tests/NetEscapades.EnumGenerators.NugetIntegrationTests
dotnet add ./tests/NetEscapades.EnumGenerators.NugetIntegrationTests package NetEscapades.EnumGenerators --version 0.1.0-beta

이 프로젝트를 솔루션 파일에 추가하지 않으므로 일반적인 restore/build/test 개발 주기의 일부가 아닙니다. 이것은 여러 가지를 단순화하고 이미 통합 테스트가 있으므로 큰 문제는 아닙니다. 이 프로젝트는 NuGet 패키지가 올바르게 생성되는지 테스트하지만 CI 프로세스의 일부로만 수행하는 것이 좋다고 생각합니다.

또 다른 옵션은 솔루션에 프로젝트를 추가하지만 모든 솔루션의 빌드 구성에서 프로젝트를 제거하는 것입니다.

위의 스크립트를 실행한 후 이 NuGet 통합 테스트 프로젝트에 “일반” 통합 테스트 프로젝트의 모든 C# 파일을 포함하도록 .csproj 파일을 수동으로 변경해야 합니다. 이를 위해 다른 프로젝트의 .cs 파일을 참조하는 Include 특성에 대한 와일드카드와 함께 <Compile> 요소를 사용할 수 있습니다. 결과 프로젝트 파일은 다음과 같아야 합니다:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <!-- 👇 임시 패키지 추가 -->
  <ItemGroup>
    <PackageReference Include="NetEscapades.EnumGenerators" Version="0.1.0-beta" />
  </ItemGroup>

  <!-- 👇 통합 테스트 프로젝트의 모든 파일을 링크하므로 동일 테스트 실행 -->
  <ItemGroup>
    <Compile Include="..\NetEscapades.EnumGenerators.IntegrationTests\*.cs" Link="%(Filename)%(Extension)"/>
  </ItemGroup>

  <!-- 템플릿의 표준 패키지 -->
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.1.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>

그리고 그게 다입니다. 이 프로젝트는 말 그대로 프로젝트 파일과 nuget.integration-tests.config 구성 파일로 구성됩니다. 이제 실행하는 일만 남았습니다.

6. NuGet 패키지 통합 테스트 실행

불행히도 프로젝트를 실행하는 것은 우리가 조심해야 하는 또 다른 경우입니다. 테스트 NuGet 패키지로 시스템 NuGet 캐시를 "오염"시키고 싶지 않으며 사용자 지정 nuget.config 파일을 사용해야 합니다. 이를 위해서는 restore/``build/test` 단계를 모두 별도로 실행하고 필요한 경우 필요한 명령 스위치를 전달해야 합니다.

테스트 패키지로 NuGet 캐시를 오염시키지 않도록 NuGet 패키지를 로컬 폴더인 ./packages에 복원합니다. 이렇게 하면 복원하는 동안 훨씬 더 많은 드라이브 공간과 네트워크가 사용됩니다(이 폴더의 다른 곳에 이미 캐시된 NuGet 패키지를 가져오기 때문에). 하지만 저를 믿으세요. 할 가치가 있습니다. 그렇지 않으면 테스트 패키지를 업데이트할 수 없거나 완전히 별도의 프로젝트가 테스트 패키지를 사용하기 시작할 때 혼란스러운 오류에 대한 설정을 하고 있는 것입니다!

다음 스크립트는 NuGet 통합 테스트 프로젝트에 대한 restore/build/test를 실행합니다. 3절에 설명된 대로 NuGet 패키지를 이미 빌드했다고 가정합니다.

# 사용자 지정 구성 파일을 사용하여 프로젝트를 복원하고 패키지를 로컬 폴더로 복원
dotnet restore ./tests/NetEscapades.EnumGenerators.NugetIntegrationTests --packages ./packages --configfile "nuget.integration-tests.config" 

# 로컬 폴더에 복원된 패키지를 사용하여 프로젝트 빌드 (복원 없음)
dotnet build ./tests/NetEscapades.EnumGenerators.NugetIntegrationTests -c Release --packages ./packages --no-restore

# 프로젝트 테스트 (빌드 또는 복원 없음)
dotnet test ./tests/NetEscapades.EnumGenerators.NugetIntegrationTests -c Release --no-build --no-restore 

모든 것이 잘 진행되면 테스트를 통과해야 하며 생성한 NuGet 패키지가 올바르게 빌드되어 배포할 준비가 되었음을 확신할 수 있습니다.

요약

이 게시물에서는 프로젝트 참조를 사용하여 소스 생성기에 대한 통합 테스트 프로젝트를 생성하고 일반 참조에서와 마찬가지로 컴파일러의 일부로 생성기를 실행하는 방법을 보여주었습니다. 또한 소스 생성기용 NuGet 패키지를 만드는 방법과 패키지에 대한 통합 테스트를 만들어 올바르게 생성되었는지 확인하는 방법을 보여주었습니다. 이 후자의 프로세스는 테스트 패키지로 로컬 NuGet 패키지 캐시를 오염시키지 않도록 주의해야 하므로 더 복잡합니다.

원문

Andrew Lock’s Series: Creating a source generator - Part 3 - Integration testing and NuGet packaging (this post)

좋아요 3
좋아요 1