Как выполнить модульное тестирование SourceGenerator?

Я написал SourceGenerator, но как его протестировать?

Главный вопрос - как имитировать GeneratorExecutionContext (или просто Compilation внутри него) какой генератор попадает в Executeметод. Я думаю, что есть правильный способ создать поддельные SyntaxTrees для модульного тестирования, но я не могу его найти. О самих генераторах исходников написано много статей, но ни одна из них не объясняет, как тестировать генераторы.

3 ответа

Решение

Вам следует взглянуть на официальную поваренную книгу генераторов исходного кода.

Вот пример из этого:

      using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace GeneratorTests.Tests
{
    [TestClass]
    public class GeneratorTests
    {
        [TestMethod]
        public void SimpleGeneratorTest()
        {
            // Create the 'input' compilation that the generator will act on
            Compilation inputCompilation = CreateCompilation(@"
namespace MyCode
{
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    }
}
");

            // directly create an instance of the generator
            // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime)
            CustomGenerator generator = new CustomGenerator();

            // Create the driver that will control the generation, passing in our generator
            GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);

            // Run the generation pass
            // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls)
            driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);

            // We can now assert things about the resulting compilation:
            Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators
            Debug.Assert(outputCompilation.SyntaxTrees.Count() == 2); // we have two syntax trees, the original 'user' provided one, and the one added by the generator
            Debug.Assert(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics

            // Or we can look at the results directly:
            GeneratorDriverRunResult runResult = driver.GetRunResult();

            // The runResult contains the combined results of all generators passed to the driver
            Debug.Assert(runResult.GeneratedTrees.Length == 1);
            Debug.Assert(runResult.Diagnostics.IsEmpty);

            // Or you can access the individual results on a by-generator basis
            GeneratorRunResult generatorResult = runResult.Results[0];
            Debug.Assert(generatorResult.Generator == generator);
            Debug.Assert(generatorResult.Diagnostics.IsEmpty);
            Debug.Assert(generatorResult.GeneratedSources.Length == 1);
            Debug.Assert(generatorResult.Exception is null);
        }

        private static Compilation CreateCompilation(string source)
            => CSharpCompilation.Create("compilation",
                new[] { CSharpSyntaxTree.ParseText(source) },
                new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
                new CSharpCompilationOptions(OutputKind.ConsoleApplication));
    }
}

В дополнение к Поваренной книге генераторов источников , упомянутой в другом ответе:

Решение Cookbook позволяет вам сгенерировать некоторый код, а затем сравнить ваши результаты с ожидаемыми, а также проверить наличие предупреждений, исключений компиляции и т. д.

Теперь вы можете дополнительно ВЫПОЛНИТЬ сгенерированный код, чтобы убедиться, что он работает правильно. Для этого измените ссылку на проект в тестовом проекте следующим образом:

      <ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
    ReferenceOutputAssembly="true"
    OutputItemType="Analyzer" />

А затем просто вызовите сгенерированный код из ваших модульных тестов, как в потребительском проекте.

Я обнаружил, что проще писать тесты, проверяющие сгенерированный код.

Предположим, ваш генератор создает классы, и наш пример сценария заключается в том, что он сгенерирует класс.GeneratedClass. Тестовый проект будет ссылаться на проект/пакет генератора, который генерирует класс в тестовом проекте, и тесты подтверждают ожидаемые свойства/поведения этого класса.

      [Fact]
public void GeneratedClass_Should_DoSomething()
{
   var generatedClass = new GeneratedClass();

   // Assert behavior of GeneratedClass
}

Очевидно, что это не охватывает такие сценарии, как обработка исключений, но эффективно тестирует счастливые пути.

Другие вопросы по тегам