Как выполнить модульное тестирование 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
}
Очевидно, что это не охватывает такие сценарии, как обработка исключений, но эффективно тестирует счастливые пути.