اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
چهار دقیقه
تا اینجا روش آزمایش تولید کنندههای کد، صرفا بر اساس کامپایل برنامه و مشاهدهی خروجی نهایی آن بود و یا حتی با ترفندهایی امکان دیباگ آنها نیز وجود دارد که البته هنوز در تمام IDEها پشتیبانی نمیشود. در این قسمت میخواهیم این وضعیت را بهبود بخشیده و برای تولید کنندههای کد، آزمون واحد بنویسیم که یکی از مزایای آن، فراهم بودن امکان دیباگ یک چنین پروژههایی در تمام IDEهای موجود است و برای انجام اینکار، نیاز به هیچ ترفند خاصی وجود ندارد و پروسهی کاری آن یکدست و هماهنگ با سایر آزمونهای واحد است.
آماده سازی مقدمات پروژهی آزمون واحد
در ادامهی مثال این سری، پروژهی جدید NotifyPropertyChangedGenerator.Tests را از نوع class library با تنظیمات فایل csproj. زیر ایجاد میکنیم:
در اینجا وابستگیهای مورد نیاز برای دسترسی به امکانات Roslyn و همچنین برای نمونه MSTest را مشاهده میکنید. به علاوه مسیر پروژهی Source Generator مورد استفاده به نحو متداولی تعریف شدهاست.
ایجاد یک کلاس کمکی برای اجرای Source Generators در پروژههای آزمون واحد
در اینجا میخواهیم همان کاری را که کامپایلر سیشارپ در پشت صحنه انجام میدهد، شبیه سازی کنیم تا بتوانیم یک تولید کنندهی کد را به مراحل کامپایل کد، معرفی و سپس آنرا اجرا کنیم:
این متد، یک قطعه کد ابتدایی را دریافت کرده و سپس آنرا به همراه Source Generatorهای مدنظر، به کامپایلر سیشارپ معرفی میکند، تا کامپایلر تمام این موارد را در کنار هم پردازش کرده و اسمبلی درون حافظهای را به نام compilation تولید کند. خروجیهای این متد، اطلاعات غنی هستند از نحوهی کامپایل دادههای ارسالی به کامپایلر که در ادامه میتوان از آنها جهت نوشتن آزمونهای واحد متکی به خودی استفاده کرد.
نوشتن اولین آزمون واحد مخصوص یک تولید کنندهی کد
پس از تهیهی متدی که میتواند یک قطعه کد و تعدادی Source Generator را به کامپایلر سیشارپ، جهت پردازش معرفی کند، یک نمونه نحوهی استفادهی از آن جهت نوشتن آزمونهای واحد کاملا مستقل و متکی به خود، به صورت زیر است:
- در این مثال ابتدا یک قطعه کد سیشارپ را که قرار است کدهای آن توسط تولید کنندهی کد توسعه داده شده تکمیل شوند، تعریف کردهایم.
- سپس این قطعه کد و نمونهای از تولید کنندهی کد را به کامپایلر ارسال و اجرا کردهایم.
- اکنون بر اساس خروجی کامپایلر برای مثال میتوان به فایل تولید شده و SyntaxTrees آن دسترسی پیدا کرد و یا با کمک متد GetText، به کل محتوای این فایل تولید شده دسترسی یافت و برای مثال آنرا با مقداری که انتظار داریم مقایسه کرد تا به این ترتیب بتوان از صحت عملکرد تولید کنندهی کد، اطمینان حاصل نمود.
- همانطور که عنوان شد، اکنون قرار دادن break-point در قسمتهای مختلف آزمون واحد تهیه شده بسیار سادهاست و به این ترتیب میتوان یک چنین پروژههایی را در تمام IDEها دیباگ کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: SourceGeneratorTests-part5.zip
آماده سازی مقدمات پروژهی آزمون واحد
در ادامهی مثال این سری، پروژهی جدید NotifyPropertyChangedGenerator.Tests را از نوع class library با تنظیمات فایل csproj. زیر ایجاد میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.2.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> <PackageReference Include="MSTest.TestFramework" Version="2.2.10" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj" /> </ItemGroup> </Project>
ایجاد یک کلاس کمکی برای اجرای Source Generators در پروژههای آزمون واحد
در اینجا میخواهیم همان کاری را که کامپایلر سیشارپ در پشت صحنه انجام میدهد، شبیه سازی کنیم تا بتوانیم یک تولید کنندهی کد را به مراحل کامپایل کد، معرفی و سپس آنرا اجرا کنیم:
internal static class SourceGeneratorTestsExtensions { public static (GeneratorDriver Driver, Compilation OutputCompilation, ImmutableArray<Diagnostic> Diagnostics) RunGenerators(this string source, params ISourceGenerator[] generators) { var references = AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => !assembly.IsDynamic) .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) .Cast<MetadataReference>(); var inputCompilation = CSharpCompilation.Create("compilation", new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest)) }, references, new CSharpCompilationOptions(OutputKind.ConsoleApplication)); GeneratorDriver driver = CSharpGeneratorDriver.Create(generators); driver = driver.RunGeneratorsAndUpdateCompilation( inputCompilation, out var outputCompilation, out var diagnostics); return (driver, outputCompilation, diagnostics); } }
نوشتن اولین آزمون واحد مخصوص یک تولید کنندهی کد
پس از تهیهی متدی که میتواند یک قطعه کد و تعدادی Source Generator را به کامپایلر سیشارپ، جهت پردازش معرفی کند، یک نمونه نحوهی استفادهی از آن جهت نوشتن آزمونهای واحد کاملا مستقل و متکی به خود، به صورت زیر است:
using Microsoft.VisualStudio.TestTools.UnitTesting; using PropertyChangedGenerator = NotifyPropertyChangedGenerator.NotifyPropertyChangedGenerator; namespace NotifyPropertyChangedGenerator.Tests; [TestClass] public class GeneratorTest { [TestMethod] public void SimpleGeneratorTest() { var userSource = @" using System; using System.ComponentModel; namespace NotifyPropertyChangedDemo { public class Test : INotifyPropertyChanged { private int regularField; private int IndexBackingField; } } "; var (driver, outputCompilation, diagnostics) = userSource.RunGenerators(new PropertyChangedGenerator()); var newFile = outputCompilation.SyntaxTrees .Single(x => Path.GetFileName(x.FilePath).EndsWith(".Test.cs")); Assert.IsNotNull(newFile); Assert.IsTrue(newFile.FilePath.EndsWith("Test.Notify.Test.cs")); var generatedSource = newFile.GetText().ToString(); Assert.IsTrue(generatedSource.Contains("namespace NotifyPropertyChangedDemo")); // We can now assert things about the resulting compilation: Assert.IsTrue(diagnostics.IsEmpty); // there were no diagnostics created by the generators // we have two syntax trees, the original 'user' provided one, and the one added by the generator Assert.IsTrue(outputCompilation.SyntaxTrees.Count() == 2); // verify the compilation with the added source has no diagnostics Assert.IsTrue(outputCompilation.GetDiagnostics().IsEmpty); } }
- سپس این قطعه کد و نمونهای از تولید کنندهی کد را به کامپایلر ارسال و اجرا کردهایم.
- اکنون بر اساس خروجی کامپایلر برای مثال میتوان به فایل تولید شده و SyntaxTrees آن دسترسی پیدا کرد و یا با کمک متد GetText، به کل محتوای این فایل تولید شده دسترسی یافت و برای مثال آنرا با مقداری که انتظار داریم مقایسه کرد تا به این ترتیب بتوان از صحت عملکرد تولید کنندهی کد، اطمینان حاصل نمود.
- همانطور که عنوان شد، اکنون قرار دادن break-point در قسمتهای مختلف آزمون واحد تهیه شده بسیار سادهاست و به این ترتیب میتوان یک چنین پروژههایی را در تمام IDEها دیباگ کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: SourceGeneratorTests-part5.zip