{"id":337091,"date":"2022-08-16T03:00:23","date_gmt":"2022-08-16T03:00:23","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=337091"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=337091","title":{"rendered":"<span>\u041c\u0435\u0447\u0442\u0430\u044e\u0442 \u043b\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043e \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/3j\/eb\/ch\/3jebchajpsf7zjaylulv_ydnod4.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/3j\/eb\/ch\/3jebchajpsf7zjaylulv_ydnod4.png\"\/><br \/>  \u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0430\u0434 <a href=\"https:\/\/habr.com\/ru\/post\/662109\/\">\u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0435\u0439<\/a> (\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u044d\u0442\u043e\u0439 \u0441\u043e\u0432\u0441\u0435\u043c \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e) \u043f\u0440\u0438\u043d\u0435\u0441\u043b\u043e \u043c\u043d\u0435 \u043d\u0435 \u043c\u0438\u0440, \u043d\u043e <s>\u043c\u0435\u0447<\/s> \u043c\u0435\u0447\u0442\u0443 \u043e \u043c\u0438\u0440\u0435. \u041c\u0438\u0440\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0432\u044b\u0440\u0430\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u0433\u043e \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0438 \u0432\u043c\u0435\u0441\u0442\u043e<\/p>\n<pre><code class=\"cs\">[TestCase(typeof(Impl), \"command\")] public void Test(Type impl, string cmd) =>     ((I)Activator.CreateInstance(impl)).Do(cmd); <\/code><\/pre>\n<p>  \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c<\/p>\n<pre><code class=\"cs\">[TestCase&lt;Impl>(\"command\")] public void Test&lt;TImpl>(string cmd) where TImpl : I, new() =>     new TImpl().Do(cmd); <\/code><\/pre>\n<p>  \u0418 \u043e\u043d \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0431\u043b\u0438\u0436\u0435, \u0447\u0435\u043c \u044f \u043c\u043e\u0433 \u043f\u043e\u0434\u0443\u043c\u0430\u0442\u044c. \u0410 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u0448\u043b\u043e-\u043f\u043e\u0435\u0445\u0430\u043b\u043e\u2026<br \/>  <a name=\"habracut\"><\/a><br \/>  <b>\u0413\u043b\u0430\u0432\u0430 1. \u0421\u0443\u0440\u043e\u0432\u0430\u044f \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/b><\/p>\n<p>  \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 Visual Studio (\u044f \u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0441\u044c 2022) \u043f\u0440\u043e\u0435\u043a\u0442 Class Library \u0441\u043e \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438 \u043d\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">TestingDreams.csproj<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"xml\">&lt;Project Sdk=\"Microsoft.NET.Sdk\">   &lt;PropertyGroup>     &lt;TargetFramework>net6.0&lt;\/TargetFramework>   &lt;\/PropertyGroup>   &lt;ItemGroup>     &lt;PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.2.0\" \/>     &lt;PackageReference Include=\"NUnit\" Version=\"3.13.3\" \/>     &lt;PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.2.1\" \/>   &lt;\/ItemGroup> &lt;\/Project> <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0442\u0435\u0441\u0442 \u0438 \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442:<\/p>\n<pre><code class=\"cs\">using NUnit.Framework; using System;  public class A { } public class B : A { } public class C : A { } public class D : A { }  [TestFixture] public class SampleTests {     [TestCase(typeof(B), typeof(A), true)]     [TestCase(typeof(C), typeof(A), true)]     [TestCase(typeof(C), typeof(B), false)]     public void Test(Type tSub, Type tSuper, bool expected)     {         var actual = tSub.IsAssignableTo(tSuper);         Assert.AreEqual(expected, actual);     } } <\/code><\/pre>\n<p>  \u0417\u0430\u0442\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0442\u0435\u0441\u0442 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"cs\">[TestCase(typeof(B), typeof(A), true)] [TestCase(typeof(C), typeof(A), true)] [TestCase(typeof(C), typeof(B), false)] public void Test&lt;TSub, TSuper>(bool expected)     where TSub : A     where TSuper : A {     var actual = typeof(TSub).IsAssignableTo(typeof(TSuper));     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0412\u0441\u0435 \u043a\u0435\u0439\u0441\u044b \u043f\u0430\u0434\u0430\u044e\u0442<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/bi\/g8\/vo\/big8vompk1kiwdosn2zwifs87oy.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/bi\/g8\/vo\/big8vompk1kiwdosn2zwifs87oy.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442 <code>GenericCaseAttribute<\/code>, \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0432 \u0435\u0433\u043e \u043e\u0442 <code>TestCaseAttribute<\/code> \u0438 \u0437\u0430\u043d\u043e\u0432\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>ITestBuilder<\/code>:<\/p>\n<pre><code class=\"cs\">using NUnit.Framework; using NUnit.Framework.Interfaces; using NUnit.Framework.Internal; using System; using System.Collections.Generic; using System.Linq;  public class GenericCaseAttribute : TestCaseAttribute, ITestBuilder {     private readonly IReadOnlyCollection&lt;Type> typeArguments;      public GenericCaseAttribute(Type[] typeArguments, params object[] arguments)         : base(arguments) => this.typeArguments = typeArguments.ToArray();      public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) =>         base.BuildFrom(             method.IsGenericMethodDefinition || typeArguments.Any() ?             method.MakeGenericMethod(typeArguments.ToArray()) :             method,             suite); } <\/code><\/pre>\n<p>  \u0418 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432\u043c\u0435\u0441\u0442\u043e <code>TestCaseAttribute<\/code> \u0432 \u043f\u0430\u0434\u0430\u044e\u0449\u0435\u043c \u0442\u0435\u0441\u0442\u0435:<\/p>\n<pre><code class=\"cs\">[GenericCase(new[] { typeof(B), typeof(A) }, true)] [GenericCase(new[] { typeof(C), typeof(A) }, true)] [GenericCase(new[] { typeof(C), typeof(B) }, false)] public void Test&lt;TSub, TSuper>(bool expected)     where TSub : A     where TSuper : A {     var actual = typeof(TSub).IsAssignableTo(typeof(TSuper));     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0422\u0435\u0441\u0442\u044b \u0441\u043d\u043e\u0432\u0430 \u0437\u0435\u043b\u0451\u043d\u044b\u0435 (\u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u043a\u0430\u043a \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u0438\u0445 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 Test Explorer)<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vw\/zb\/q8\/vwzbq8oupjyrabhi1rwjvbin5nw.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/vw\/zb\/q8\/vwzbq8oupjyrabhi1rwjvbin5nw.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0437\u043d\u043e\u043e\u0431\u0440\u0430\u0437\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u0442\u0430\u043a:<\/p>\n<pre><code class=\"cs\">public interface I1 { } public interface I2 { } public interface I3 { } public interface I4 { } public class A : I1, I2 { } public class B : A, I3 { } public class C : A, I3 { } public class D : A, I4 { }  [TestFixture] public class SampleTests {     [GenericCase(new Type[] { }, false)]     [GenericCase(new[] { typeof(A) }, false)]     [GenericCase(new[] { typeof(C), typeof(B), typeof(A) }, false)]     [GenericCase(new[] { typeof(B), typeof(A) }, true)]     [GenericCase(new[] { typeof(C), typeof(B) }, false)]     [GenericCase(new[] { typeof(D), typeof(A) }, false)]     public void Test&lt;TSub, TSuper>(bool expected)         where TSub : A, I3         where TSuper : I1, I2     {         var actual = typeof(TSub).IsAssignableTo(typeof(TSuper));         Assert.AreEqual(expected, actual);     }      [GenericCase(new Type[] { })]     [GenericCase(new[] { typeof(object) })]     public void Test() { } } <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0412\u043d\u0435\u0437\u0430\u043f\u043d\u043e, \u043e\u043d\u0438 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f, \u043d\u043e \u0434\u0430\u0436\u0435 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/yz\/vb\/x2\/yzvbx2yqffpdec6tdcspjttq4qg.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/yz\/vb\/x2\/yzvbx2yqffpdec6tdcspjttq4qg.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  \u042d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437-\u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0435\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c\u044e \u0442\u0438\u043f\u043e\u0432-\u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u0438 \u0442\u0438\u043f\u043e\u0432-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430. \u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0448\u0438\u0442\u044c \u043a\u0430\u043a-\u0442\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     if (IsIncompatible(method))     {         \/\/ return ...     }      return base.BuildFrom(         method.IsGenericMethodDefinition || typeArguments.Any() ?         method.MakeGenericMethod(typeArguments.ToArray()) :         method,         suite); } <\/code><\/pre>\n<p>  \u041d\u043e \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 \u043b\u0435\u0433\u043a\u043e \u0443\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u043d\u044e\u0430\u043d\u0441 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u0440\u0438\u0432\u043e\u0439 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 (\u044f \u0432\u043e\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u043f\u0430\u0440\u0443 \u0440\u0430\u0437). \u0420\u0430\u0437 \u0443\u0436 <code>IMethodInfo IMethodInfo.MakeGenericMethod(params Type[])<\/code> \u0441\u0430\u043c \u0432\u0441\u0451 \u043b\u0443\u0447\u0448\u0435 \u043d\u0430\u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043e\u0441\u0442\u0430\u0432\u0438\u043c \u044d\u0442\u043e \u0435\u043c\u0443:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     try     {         return base.BuildFrom(             method.IsGenericMethodDefinition || typeArguments.Any() ?             method.MakeGenericMethod(typeArguments.ToArray()) :             method,             suite);     }     catch (Exception ex)     {         return base.BuildFrom(method, suite).SetNotRunnable(ex.Message);     } } <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">TestMethodExtensions<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">using NUnit.Framework.Interfaces; using NUnit.Framework.Internal; using System.Collections.Generic;  public static class TestMethodExtensions {     public static IEnumerable&lt;TestMethod> SetNotRunnable(this IEnumerable&lt;TestMethod> tests, string message)     {         foreach(var test in tests)             yield return test.SetNotRunnable(message);     }      public static TestMethod SetNotRunnable(this TestMethod test, string message)     {         test.RunState = RunState.NotRunnable;         test.Properties.Set(PropertyNames.SkipReason, message);         return test;     } } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0440\u0443\u0433\u043e\u0435 \u0434\u0435\u043b\u043e.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0421\u043c\u0443\u0449\u0430\u0435\u0442, \u0447\u0442\u043e Test Explorer \u0432\u0432\u043e\u0434\u0438\u0442 \u0432 \u0437\u0430\u0431\u043b\u0443\u0436\u0434\u0435\u043d\u0438\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u0435\u0441\u0442\u043e\u0432<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vb\/bp\/pn\/vbbppnt_uz4wxz7yjabcr_izoku.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/vb\/bp\/pn\/vbbppnt_uz4wxz7yjabcr_izoku.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0425\u043e\u0442\u044f, \u0435\u0441\u043b\u0438 \u043a\u043e\u043f\u043d\u0443\u0442\u044c \u0433\u043b\u0443\u0431\u0436\u0435, \u0440\u0430\u0441\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043a\u0430\u0440\u0442\u044b<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/w4\/gd\/ky\/w4gdkyozvo9sk5szphgrgworovi.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/w4\/gd\/ky\/w4gdkyozvo9sk5szphgrgworovi.png\"\/><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ut\/p1\/bs\/utp1bs_wznqptgtn5nivvcwjbkq.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/ut\/p1\/bs\/utp1bs_wznqptgtn5nivvcwjbkq.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0417\u0430\u0442\u043e Output > Tests \u0438 \u043d\u0435 \u043f\u043e\u043c\u044b\u0448\u043b\u044f\u0435\u0442 \u043e\u0431 \u043e\u0431\u043c\u0430\u043d\u0435<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/jo\/uf\/zu\/joufzu4fcv0hvjvhi7rwqappadm.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/jo\/uf\/zu\/joufzu4fcv0hvjvhi7rwqappadm.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  <b>\u0413\u043b\u0430\u0432\u0430 2. \u041c\u0435\u0447\u0442\u0430 \u0432\u0438\u0442\u0430\u0435\u0442 \u0432 \u0432\u043e\u0437\u0434\u0443\u0445\u0435<\/b><\/p>\n<p>  \u0422\u0430\u043d\u0446\u0443\u0439, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u0432\u0438\u0434\u0438\u0442. \u041f\u043e\u0439, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u0441\u043b\u044b\u0448\u0438\u0442. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 preview \u0444\u0438\u0447\u0438, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043e\u043d\u0438 \u0443\u0436\u0435 \u0432 \u0440\u0435\u043b\u0438\u0437\u0435. \u041a\u0430\u0436\u0435\u0442\u0441\u044f, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u043e\u0432\u0435\u0442 \u043d\u0435 \u0441\u0430\u043c\u044b\u0439 \u043b\u0443\u0447\u0448\u0438\u0439, \u043d\u043e \u0440\u0443\u043a\u0438 \u0447\u0435\u0448\u0443\u0442\u0441\u044f, \u0442\u0430\u043a \u0447\u0442\u043e \u0441\u0442\u0430\u0432\u0438\u043c \u0433\u0430\u043b\u043a\u0443 \u0432 <code>Tool > Options > Environment > Preview Features > Use previews of the .NET SDK (requires restart)<\/code> \u0438 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0435\u0440\u0441\u0438\u044e \u044f\u0437\u044b\u043a\u0430 <code>preview<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">TestingDreams.csproj<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"xml\">&lt;Project Sdk=\"Microsoft.NET.Sdk\"> &lt;PropertyGroup> &lt;TargetFramework>net6.0&lt;\/TargetFramework> &lt;LangVersion>preview&lt;\/LangVersion> &lt;!--enable generic attributes--> &lt;\/PropertyGroup> &lt;ItemGroup> &lt;PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.2.0\" \/> &lt;PackageReference Include=\"NUnit\" Version=\"3.13.3\" \/> &lt;PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.2.1\" \/> &lt;\/ItemGroup> &lt;\/Project> <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0412 <code>GenericCaseAttribute<\/code> \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u0435\u043d\u044f\u0435\u043c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440:<\/p>\n<pre><code class=\"cs\">public GenericCaseAttribute(params object[] arguments)     : base(arguments) => typeArguments = GetType().GetGenericArguments(); <\/code><\/pre>\n<p>  \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043e\u0431\u043e\u0431\u0449\u0435\u043d\u043d\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b:<\/p>\n<pre><code class=\"cs\">public class GenericCaseAttribute&lt;T> : GenericCaseAttribute {     public GenericCaseAttribute(params object[] arguments)         : base(arguments) { } }  public class GenericCaseAttribute&lt;T1, T2> : GenericCaseAttribute {     public GenericCaseAttribute(params object[] arguments)         : base(arguments) { } }  public class GenericCaseAttribute&lt;T1, T2, T3> : GenericCaseAttribute {     public GenericCaseAttribute(params object[] arguments)         : base(arguments) { } } <\/code><\/pre>\n<p>  \u0418 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u0445 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445:<\/p>\n<pre><code class=\"cs\">[GenericCase(false)] [GenericCase&lt;A>(false)] [GenericCase&lt;C, B, A>(false)] [GenericCase&lt;B, A>(true)] [GenericCase&lt;C, B>(false)] [GenericCase&lt;D, A>(false)] public void Test&lt;TSub, TSuper>(bool expected)     where TSub : A, I3     where TSuper : I1, I2 {     var actual = typeof(TSub).IsAssignableTo(typeof(TSuper));     Assert.AreEqual(expected, actual); }  [GenericCase] [GenericCase&lt;object>] public void Test() { } <\/code><\/pre>\n<p>  \u0423\u0440\u0430! \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442!<br \/>  \u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440. \u0410\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u0443\u044f\u0441\u044c \u043e\u0442 \u0434\u0435\u0442\u0430\u043b\u0435\u0439, \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 (\u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438 \u043d\u0430\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0430 \u043c\u0435\u043d\u044f \u043d\u0430 \u044d\u0442\u0438 \u0444\u0430\u043d\u0442\u0430\u0437\u0438\u0438) \u0431\u044b\u043b\u043e \u0447\u0442\u043e-\u0442\u043e \u043f\u0440\u043e \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b <code>IScript<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">IScript<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public interface IScript {     void Execute(); } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u041a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u0430\u043c\u0438 <code>IValidator<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">IValidator<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public interface IValidator {     void Validate(IScript script); } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u041f\u0435\u0440\u0435\u0434 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c \u0432\u043d\u0443\u0442\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044f <code>Executor<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">Executor<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public class Executor {     readonly IValidator validator;      public Executor(IValidator validator) =>         this.validator = validator;      public void Execute(IScript script)     {         validator.Validate(script);         script.Execute();     } } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0432\u0430\u0436\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 <code>Data<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">Data<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public class Data {     public bool IsChanged { get; private set; }      public void Change() =>         IsChanged = true; } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0432 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 <code>Store<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">Store<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public static class Store {     private static readonly Dictionary&lt;string, Data> store = new();      public static Data GetData(string id) =>         store.TryGetValue(id, out var data) ? data : (store[id] = new()); } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b <code>HarmlessScript<\/code> \u043d\u0435 \u043f\u044b\u0442\u0430\u044e\u0442\u0441\u044f \u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">HarmlessScript<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public class HarmlessScript : IScript {     void IScript.Execute() { } } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0430\u0442\u0430\u043a <code>Attack<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u044b\u0432\u0430\u044e\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 <code>OrdinaryAttack<\/code>, \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 <code>AdvancedAttack<\/code> \u0438 \u043f\u0440\u0435\u0432\u043e\u0441\u0445\u043e\u0434\u043d\u044b\u0435 <code>SuperiorAttack<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">Attack, SuperiorAttack, AdvancedAttack, SuperiorAttack<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public abstract class Attack : IScript {     void IScript.Execute() =>         Store.GetData($\"{GetHashCode()}\").Change(); }  public class OrdinaryAttack : Attack { }  public class AdvancedAttack : OrdinaryAttack { }  public class SuperiorAttack : AdvancedAttack { } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u041f\u0440\u043e\u0442\u0438\u0432\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u043c \u043f\u0440\u0438\u0437\u0432\u0430\u043d\u044b \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440 <code>OrdinaryValidator<\/code>, \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b\u0439 \u043e\u0442\u0440\u0430\u0437\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u044b\u0447\u043d\u0443\u044e \u0430\u0442\u0430\u043a\u0443, \u0438 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0439 <code>AdvancedValidator<\/code>, \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b\u0439 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0441\u0435\u0447\u044c \u0434\u0430\u0436\u0435 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u0443\u044e.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">OrdinaryValidator, AdvancedValidator<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public class OrdinaryValidator : IValidator {     void IValidator.Validate(IScript script)     {         if (script is Attack &amp;&amp; script is not AdvancedAttack)             throw new Exception(\"Attack detected.\");     } }  public class AdvancedValidator : IValidator {     void IValidator.Validate(IScript script)     {         if (script is Attack &amp;&amp; script is not SuperiorAttack)             throw new Exception(\"Attack detected.\");     } } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0412\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u044d\u0442\u0438\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u043b\u043e\u0441\u044c \u0442\u0435\u0441\u0442\u0430\u043c\u0438:<\/p>\n<pre><code class=\"cs\">using NUnit.Framework; using System;  [TestFixture] public class DemoTests {     [TestCase(typeof(OrdinaryValidator), typeof(HarmlessScript), true, false)]     [TestCase(typeof(AdvancedValidator), typeof(HarmlessScript), true, false)]     [TestCase(typeof(OrdinaryValidator), typeof(OrdinaryAttack), false, false)]     [TestCase(typeof(AdvancedValidator), typeof(OrdinaryAttack), false, false)]     [TestCase(typeof(OrdinaryValidator), typeof(AdvancedAttack), true, true)]     [TestCase(typeof(AdvancedValidator), typeof(AdvancedAttack), false, false)]     [TestCase(typeof(OrdinaryValidator), typeof(SuperiorAttack), true, true)]     [TestCase(typeof(AdvancedValidator), typeof(SuperiorAttack), true, true)]     public void Test(Type validatorType, Type scriptType, bool hasExecuted, bool dataChanged)     {         \/\/ Arrange         IValidator validator = (IValidator)Activator.CreateInstance(validatorType);         IScript script = (IScript)Activator.CreateInstance(scriptType);          \/\/ Act         Exception exception = default;         try         {             new Executor(validator).Execute(script);         }         catch (Exception e)         {             exception = e;         }          \/\/ Asert         Assert.AreEqual(hasExecuted, exception is null);         Assert.AreEqual(dataChanged, Store.GetData($\"{script.GetHashCode()}\").IsChanged);     } } <\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c <code>ICheck<\/code>, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u043a\u0443 \u0444\u0430\u043a\u0442\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u043a\u0440\u0438\u043f\u0442\u0430 <code>HasExecuted<\/code> \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 <code>DataChanged<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">ICheck, HasExecuted, DataChanged<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public interface ICheck {     bool Check(IValidator validator, IScript script); }  public class HasExecuted : ICheck {     public bool Check(IValidator validator, IScript script)     {         try         {             new Executor(validator).Execute(script);             return true;         }         catch         {             return false;         }     } }  public class DataChanged : ICheck {     public bool Check(IValidator validator, IScript script)     {         try         {             new Executor(validator).Execute(script);         }         catch         {         }          return Store.GetData($\"{script.GetHashCode()}\").IsChanged;     } } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u0418 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0451, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b:<\/p>\n<pre><code class=\"cs\">[TestCase(typeof(OrdinaryValidator), typeof(HarmlessScript), typeof(HasExecuted), true)] [TestCase(typeof(OrdinaryValidator), typeof(HarmlessScript), typeof(DataChanged), false)] [TestCase(typeof(AdvancedValidator), typeof(HarmlessScript), typeof(HasExecuted), true)] [TestCase(typeof(AdvancedValidator), typeof(HarmlessScript), typeof(DataChanged), false)] [TestCase(typeof(OrdinaryValidator), typeof(OrdinaryAttack), typeof(HasExecuted), false)] [TestCase(typeof(OrdinaryValidator), typeof(OrdinaryAttack), typeof(DataChanged), false)] [TestCase(typeof(AdvancedValidator), typeof(OrdinaryAttack), typeof(HasExecuted), false)] [TestCase(typeof(AdvancedValidator), typeof(OrdinaryAttack), typeof(DataChanged), false)] [TestCase(typeof(OrdinaryValidator), typeof(AdvancedAttack), typeof(HasExecuted), true)] [TestCase(typeof(OrdinaryValidator), typeof(AdvancedAttack), typeof(DataChanged), true)] [TestCase(typeof(AdvancedValidator), typeof(AdvancedAttack), typeof(HasExecuted), false)] [TestCase(typeof(AdvancedValidator), typeof(AdvancedAttack), typeof(DataChanged), false)] [TestCase(typeof(OrdinaryValidator), typeof(SuperiorAttack), typeof(HasExecuted), true)] [TestCase(typeof(OrdinaryValidator), typeof(SuperiorAttack), typeof(DataChanged), true)] [TestCase(typeof(AdvancedValidator), typeof(SuperiorAttack), typeof(HasExecuted), true)] [TestCase(typeof(AdvancedValidator), typeof(SuperiorAttack), typeof(DataChanged), true)] public void Test(Type validatorType, Type scriptType, Type checkType, bool expected) {     \/\/ Arrange     IValidator validator = (IValidator)Activator.CreateInstance(validatorType);     IScript script = (IScript)Activator.CreateInstance(scriptType);     ICheck check = (ICheck)Activator.CreateInstance(checkType);      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  \u0410 \u0434\u0430\u043b\u0435\u0435 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f <code>GenericCaseAttribute<\/code>:<\/p>\n<pre><code class=\"cs\">[GenericCase&lt;OrdinaryValidator, HarmlessScript, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, HarmlessScript, DataChanged>(false)] [GenericCase&lt;AdvancedValidator, HarmlessScript, HasExecuted>(true)] [GenericCase&lt;AdvancedValidator, HarmlessScript, DataChanged>(false)] [GenericCase&lt;OrdinaryValidator, OrdinaryAttack, HasExecuted>(false)] [GenericCase&lt;OrdinaryValidator, OrdinaryAttack, DataChanged>(false)] [GenericCase&lt;AdvancedValidator, OrdinaryAttack, HasExecuted>(false)] [GenericCase&lt;AdvancedValidator, OrdinaryAttack, DataChanged>(false)] [GenericCase&lt;OrdinaryValidator, AdvancedAttack, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, AdvancedAttack, DataChanged>(true)] [GenericCase&lt;AdvancedValidator, AdvancedAttack, HasExecuted>(false)] [GenericCase&lt;AdvancedValidator, AdvancedAttack, DataChanged>(false)] [GenericCase&lt;OrdinaryValidator, SuperiorAttack, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, SuperiorAttack, DataChanged>(true)] [GenericCase&lt;AdvancedValidator, SuperiorAttack, HasExecuted>(true)] [GenericCase&lt;AdvancedValidator, SuperiorAttack, DataChanged>(true)] public void Test&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  \u041f\u043e-\u043c\u043e\u0435\u043c\u0443, \u0441\u0438\u043c\u043f\u0430\u0442\u0438\u0447\u043d\u043e \u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0444\u043e\u0440\u043c\u0435, \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u0439 \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u0442\u0435\u043b\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u043c\u043e\u0436\u043d\u043e \u0434\u0430\u0436\u0435 \u043a \u043e\u0434\u043d\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u0438\u043a\u0443 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">public void Test&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() =>     Assert.AreEqual(expected, new TCheck().Check(new TValidator(), new TScript())); <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  <b>\u0413\u043b\u0430\u0432\u0430 3. \u041a\u0443\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442 \u043c\u0435\u0447\u0442\u044b<\/b><\/p>\n<p>  <b><i>\u041e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e!!! \u0414\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0435 \u0438\u0437\u044b\u0441\u043a\u0430\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u0440\u0430 \u043c\u043e\u0433\u0443\u0442 \u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u0438\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435\u043c!<\/i><\/b><\/p>\n<p>  \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0438\u043c, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043f\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 <code>IScript<\/code>.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a \u0433\u0440\u043e\u043c\u043e\u0437\u0434\u043a\u043e, \u0447\u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u043e\u0434 \u0441\u043f\u043e\u0439\u043b\u0435\u0440 \u0441\u043f\u0440\u044f\u0442\u0430\u0442\u044c<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">[GenericCase&lt;OrdinaryValidator, HarmlessScript, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, HarmlessScript, DataChanged>(false)] [GenericCase&lt;AdvancedValidator, HarmlessScript, HasExecuted>(true)] [GenericCase&lt;AdvancedValidator, HarmlessScript, DataChanged>(false)] public void TestHarmlessScript&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); }  [GenericCase&lt;OrdinaryValidator, OrdinaryAttack, HasExecuted>(false)] [GenericCase&lt;OrdinaryValidator, OrdinaryAttack, DataChanged>(false)] [GenericCase&lt;AdvancedValidator, OrdinaryAttack, HasExecuted>(false)] [GenericCase&lt;AdvancedValidator, OrdinaryAttack, DataChanged>(false)] public void TestOrdinaryAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); }  [GenericCase&lt;OrdinaryValidator, AdvancedAttack, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, AdvancedAttack, DataChanged>(true)] [GenericCase&lt;AdvancedValidator, AdvancedAttack, HasExecuted>(false)] [GenericCase&lt;AdvancedValidator, AdvancedAttack, DataChanged>(false)] public void TestAdvancedAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); }  [GenericCase&lt;OrdinaryValidator, SuperiorAttack, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, SuperiorAttack, DataChanged>(true)] [GenericCase&lt;AdvancedValidator, SuperiorAttack, HasExecuted>(true)] [GenericCase&lt;AdvancedValidator, SuperiorAttack, DataChanged>(true)] public void TestSuperiorAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  \u041d\u043e \u043c\u043e\u0436\u043d\u043e \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c, \u0432\u044b\u0434\u0435\u043b\u0438\u0432 \u043c\u0435\u0442\u043e\u0434 <code>void Test&lt;TValidator, TScript, TCheck>(bool)<\/code>:<\/p>\n<pre><code class=\"cs\">[GenericCase&lt;OrdinaryValidator, HarmlessScript, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, HarmlessScript, DataChanged>(false)] [GenericCase&lt;AdvancedValidator, HarmlessScript, HasExecuted>(true)] [GenericCase&lt;AdvancedValidator, HarmlessScript, DataChanged>(false)] public void TestHarmlessScript&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     Test&lt;TValidator, TScript, TCheck>(expected); }  [GenericCase&lt;OrdinaryValidator, OrdinaryAttack, HasExecuted>(false)] [GenericCase&lt;OrdinaryValidator, OrdinaryAttack, DataChanged>(false)] [GenericCase&lt;AdvancedValidator, OrdinaryAttack, HasExecuted>(false)] [GenericCase&lt;AdvancedValidator, OrdinaryAttack, DataChanged>(false)] public void TestOrdinaryAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     Test&lt;TValidator, TScript, TCheck>(expected); }  [GenericCase&lt;OrdinaryValidator, AdvancedAttack, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, AdvancedAttack, DataChanged>(true)] [GenericCase&lt;AdvancedValidator, AdvancedAttack, HasExecuted>(false)] [GenericCase&lt;AdvancedValidator, AdvancedAttack, DataChanged>(false)] public void TestAdvancedAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     Test&lt;TValidator, TScript, TCheck>(expected); }  [GenericCase&lt;OrdinaryValidator, SuperiorAttack, HasExecuted>(true)] [GenericCase&lt;OrdinaryValidator, SuperiorAttack, DataChanged>(true)] [GenericCase&lt;AdvancedValidator, SuperiorAttack, HasExecuted>(true)] [GenericCase&lt;AdvancedValidator, SuperiorAttack, DataChanged>(true)] public void TestSuperiorAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     Test&lt;TValidator, TScript, TCheck>(expected); }  private void Test&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  \u0410 \u043c\u043e\u0436\u043d\u043e \u043b\u0438 \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u0438 \u043e\u0442 \u0435\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430?<br \/>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442 <code>DeclarativeCaseAttribute&lt;TValidator, TScript, TCheck><\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u043d\u043e\u0432\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c <code>ITestBuilder<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0435\u043c \u0432 \u043d\u0435\u0433\u043e <code>void TestSuperiorAttack&lt;TValidator, TScript, TCheck>(bool)<\/code> \u0438\u0437 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430:<\/p>\n<pre><code class=\"cs\">using NUnit.Framework; using NUnit.Framework.Interfaces; using NUnit.Framework.Internal; using System.Collections.Generic;  public class DeclarativeCaseAttribute&lt;TValidator, TScript, TCheck>     : GenericCaseAttribute&lt;TValidator, TScript, TCheck>, ITestBuilder         where TValidator : IValidator, new()         where TScript : IScript, new()         where TCheck : ICheck, new() {     public DeclarativeCaseAttribute(params object[] arguments)         : base(arguments) { }      public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite)     {         return base.BuildFrom(method, suite);     }      private void Test&lt;TValidator, TScript, TCheck>(bool expected)         where TValidator : IValidator, new()         where TScript : IScript, new()         where TCheck : ICheck, new()     {         \/\/ Arrange         IValidator validator = new TValidator();         IScript script = new TScript();         ICheck check = new TCheck();          \/\/ Act         bool actual = check.Check(validator, script);          \/\/ Assert         Assert.AreEqual(expected, actual);     } } <\/code><\/pre>\n<p>  \u0422\u0435\u0441\u0442\u044b \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0434\u0435\u043b\u0430\u044e\u0442 \u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"cs\">[DeclarativeCase&lt;OrdinaryValidator, HarmlessScript, HasExecuted>(true)] [DeclarativeCase&lt;OrdinaryValidator, HarmlessScript, DataChanged>(false)] [DeclarativeCase&lt;AdvancedValidator, HarmlessScript, HasExecuted>(true)] [DeclarativeCase&lt;AdvancedValidator, HarmlessScript, DataChanged>(false)] public void TestHarmlessScript&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() { }  [DeclarativeCase&lt;OrdinaryValidator, OrdinaryAttack, HasExecuted>(false)] [DeclarativeCase&lt;OrdinaryValidator, OrdinaryAttack, DataChanged>(false)] [DeclarativeCase&lt;AdvancedValidator, OrdinaryAttack, HasExecuted>(false)] [DeclarativeCase&lt;AdvancedValidator, OrdinaryAttack, DataChanged>(false)] public void TestOrdinaryAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() { }  [DeclarativeCase&lt;OrdinaryValidator, AdvancedAttack, HasExecuted>(true)] [DeclarativeCase&lt;OrdinaryValidator, AdvancedAttack, DataChanged>(true)] [DeclarativeCase&lt;AdvancedValidator, AdvancedAttack, HasExecuted>(false)] [DeclarativeCase&lt;AdvancedValidator, AdvancedAttack, DataChanged>(false)] public void TestAdvancedAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() { }  [DeclarativeCase&lt;OrdinaryValidator, SuperiorAttack, HasExecuted>(true)] [DeclarativeCase&lt;OrdinaryValidator, SuperiorAttack, DataChanged>(true)] [DeclarativeCase&lt;AdvancedValidator, SuperiorAttack, HasExecuted>(true)] [DeclarativeCase&lt;AdvancedValidator, SuperiorAttack, DataChanged>(true)] public void TestSuperiorAttack&lt;TValidator, TScript, TCheck>(bool expected)     where TValidator : IValidator, new()     where TScript : IScript, new()     where TCheck : ICheck, new() { } <\/code><\/pre>\n<p>  \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043c <code>void Test&lt;TValidator, TScript, TCheck>(bool)<\/code> \u0434\u043e:<\/p>\n<pre><code class=\"cs\">private void Test(bool expected) {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  \u0418 \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u043c\u0443. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0432 <code>DeclarativeCaseAttribute&lt;TValidator, TScript, TCheck><\/code> \u043f\u043e\u0434\u043c\u0435\u043d\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0442\u0430\u043a\u0438\u043c \u043d\u0435\u0445\u0438\u0442\u0440\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     var @base = this as TestCaseAttribute;     var type = GetType();     var test = type.GetMethod(nameof(Test), BindingFlags.NonPublic | BindingFlags.Instance);      return @base.BuildFrom(         new MethodWrapper(type, test),         new TestFixture(new TypeWrapper(type), Arguments)); } <\/code><\/pre>\n<p>  \u0423 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u043e\u043c\u0435\u043d\u044f\u043b\u0438\u0441\u044c \u0438\u043c\u0435\u043d\u0430, \u0438 \u0432\u0441\u0435 \u043e\u043d\u0438 \u043f\u0430\u0434\u0430\u044e\u0442 \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c <b><i>\u00abMethod is not public\u00bb<\/i><\/b>, \u0430 \u043f\u043e \u0434\u0432\u043e\u0439\u043d\u043e\u043c\u0443 \u043a\u043b\u0438\u043a\u0443 \u043d\u0430 \u0438\u043c\u0435\u043d\u0438 \u0442\u0435\u0441\u0442\u0430 \u0432 Test Explorer \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043a\u0443\u0434\u0430-\u0442\u043e \u043d\u0435 \u0442\u0443\u0434\u0430. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e \u043f\u043e\u044f\u0432\u0438\u043b\u0438\u0441\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u043b\u0438\u0448\u043d\u0438\u0435 \u043d\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b. \u041d\u043e Output > Tests \u0432\u0441\u0451 \u0436\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0438\u0445 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e.<\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0412\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a-\u0442\u043e \u0442\u0430\u043a<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/sa\/y6\/mu\/say6muqekazc35drvfh9zw7ktag.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/sa\/y6\/mu\/say6muqekazc35drvfh9zw7ktag.png\"\/><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/hi\/dd\/jz\/hiddjzpq54t1ua0rn53tdus6xcw.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/hi\/dd\/jz\/hiddjzpq54t1ua0rn53tdus6xcw.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  \u0427\u0442\u043e \u0436, \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c. \u0417\u0430\u043e\u0434\u043d\u043e \u0432\u043d\u0435\u0441\u0435\u043c \u0435\u0449\u0435 \u043e\u0434\u043d\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     var @base = this as TestCaseAttribute;     var type = GetType();      return @base.BuildFrom(         new MethodWrapper(type, nameof(Test)),         new TestFixture(new TypeWrapper(type), Arguments)); }  public void Test(bool expected) {     \/\/ Arrange     IValidator validator = new TValidator();     IScript script = new TScript();     ICheck check = new TCheck();      \/\/ Act     bool actual = check.Check(validator, script);      \/\/ Assert     Assert.AreEqual(expected, actual); } <\/code><\/pre>\n<p>  \u0422\u0435\u0441\u0442\u044b \u0441\u043d\u043e\u0432\u0430 \u043f\u0430\u0434\u0430\u044e\u0442, \u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u043d\u0430:<\/p>\n<pre><code class=\"tex\">Message:\u2009     System.Reflection.TargetException : Object does not match target type.  Stack Trace:\u2009     RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)     MethodBase.Invoke(Object obj, Object[] parameters)     Reflect.InvokeMethod(MethodInfo method, Object fixture, Object[] args) <\/code><\/pre>\n<p>  \u041a\u0430\u0436\u0435\u0442\u0441\u044f, \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435. \u041d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0443\u043b\u0438\u0447\u043d\u043e\u0439 \u043c\u0430\u0433\u0438\u0438 \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443. \u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c <code>void Test(bool)<\/code> \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c, \u0438 \u0442\u0435\u0441\u0442\u044b \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442. <b><i>\u0414\u043b\u044f \u043c\u0435\u043d\u044f \u0442\u0430\u043a\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0442\u0430\u043a\u0436\u0435 \u043d\u0435 \u0443\u0432\u0435\u0440\u0435\u043d, \u0447\u0442\u043e \u043e\u043d\u043e \u0433\u0434\u0435-\u0442\u043e \u0432\u043d\u044f\u0442\u043d\u043e \u0437\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e, \u0442\u0430\u043a \u0447\u0442\u043e \u043a \u044d\u0442\u043e\u043c\u0443 \u043c\u0435\u0441\u0442\u0443 \u043c\u044b \u0435\u0449\u0435 \u0432\u0435\u0440\u043d\u0435\u043c\u0441\u044f<\/i><\/b>. \u0410 \u043f\u043e\u043a\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 <code>DeclarativeCaseAttribute&lt;TValidator, TScript, TCheck><\/code> \u043c\u0435\u0442\u043e\u0434 <code>string CreateName(TestMethod, Test, IMethodInfo, Func&lt;Test, string>, Func&lt;Type, string>)<\/code> \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0433\u043e:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     var @base = this as TestCaseAttribute;     var type = GetType();      return @base.BuildFrom(         new MethodWrapper(type, nameof(Test)),         new TestFixture(new TypeWrapper(type), Arguments))         .Select(test =>         {             test.FullName = CreateName(test, suite, method,                 suite => suite.FullName, type => type.FullName);             test.Name = CreateName(test, suite, method,                 suite => suite.Name, type => type.Name);             return test;         }); }  private static readonly IReadOnlyCollection&lt;Type> types = new[] {     typeof(TValidator),     typeof(TScript),     typeof(TCheck) };  private static string CreateName(     TestMethod test,     Test suite,     IMethodInfo method,     Func&lt;Test, string> suitNameSelector,     Func&lt;Type, string> typeNameSelector) =>     $\"{suitNameSelector(suite)}.{method.Name}&lt;{         string.Join(\",\", types.Select(typeNameSelector))}>({         string.Join(',', test.Arguments)})\"; <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0422\u0435\u043f\u0435\u0440\u044c Test Explorer \u0432\u0435\u0434\u0435\u0442 \u0441\u0435\u0431\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e<\/b>                         <\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ow\/bb\/fl\/owbbfl4vmcn1t88ui7f75jycbik.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/ow\/bb\/fl\/owbbfl4vmcn1t88ui7f75jycbik.png\"\/>  <\/div>\n<\/p><\/div>\n<p>  \u0421\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u0435\u0437\u0431\u043e\u043b\u0435\u0437\u043d\u0435\u043d\u043d\u043e \u0440\u0435\u0434\u0443\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e:<\/p>\n<pre><code class=\"cs\">[DeclarativeCase&lt;OrdinaryValidator, HarmlessScript, HasExecuted>(true)] [DeclarativeCase&lt;OrdinaryValidator, HarmlessScript, DataChanged>(false)] [DeclarativeCase&lt;AdvancedValidator, HarmlessScript, HasExecuted>(true)] [DeclarativeCase&lt;AdvancedValidator, HarmlessScript, DataChanged>(false)] public void TestHarmlessScript() { }  [DeclarativeCase&lt;OrdinaryValidator, OrdinaryAttack, HasExecuted>(false)] [DeclarativeCase&lt;OrdinaryValidator, OrdinaryAttack, DataChanged>(false)] [DeclarativeCase&lt;AdvancedValidator, OrdinaryAttack, HasExecuted>(false)] [DeclarativeCase&lt;AdvancedValidator, OrdinaryAttack, DataChanged>(false)] public void TestOrdinaryAttack() { }  [DeclarativeCase&lt;OrdinaryValidator, AdvancedAttack, HasExecuted>(true)] [DeclarativeCase&lt;OrdinaryValidator, AdvancedAttack, DataChanged>(true)] [DeclarativeCase&lt;AdvancedValidator, AdvancedAttack, HasExecuted>(false)] [DeclarativeCase&lt;AdvancedValidator, AdvancedAttack, DataChanged>(false)] public void TestAdvancedAttack() { }  [DeclarativeCase&lt;OrdinaryValidator, SuperiorAttack, HasExecuted>(true)] [DeclarativeCase&lt;OrdinaryValidator, SuperiorAttack, DataChanged>(true)] [DeclarativeCase&lt;AdvancedValidator, SuperiorAttack, HasExecuted>(true)] [DeclarativeCase&lt;AdvancedValidator, SuperiorAttack, DataChanged>(true)] public void TestSuperiorAttack() { } <\/code><\/pre>\n<p>  \u0428\u0430\u043b\u043e\u0441\u0442\u044c \u0443\u0434\u0430\u043b\u0430\u0441\u044c! \u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0431\u0435\u0437 \u0442\u0435\u043b\u0430 \u043c\u0435\u0442\u043e\u0434\u0430, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u044b\u0435 \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u043c \u0438 \u0432 \u043d\u0435\u043c \u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0435\u0441\u044f. \u0421\u043b\u0430\u0431\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e, \u0433\u0434\u0435 \u044d\u0442\u043e \u0432 \u0436\u0438\u0437\u043d\u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u044c\u0441\u044f, \u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>  <b>\u042d\u043f\u0438\u043b\u043e\u0433<\/b><\/p>\n<p>  \u0412\u0435\u0440\u043d\u0435\u043c\u0441\u044f \u043a \u0433\u0440\u044f\u0437\u043d\u043e\u043c\u0443 \u0445\u0430\u043a\u0443 \u0441\u043e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043f\u0440\u0438 \u043f\u043e\u0434\u043c\u0435\u043d\u0435 \u0442\u0435\u0441\u0442\u0430 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0435\u0433\u043e \u0434\u0440\u0443\u0433\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c.<\/p>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>IDeclarativeTest<\/code>:<\/p>\n<pre><code class=\"cs\">public interface IDeclarativeTest {     void Test&lt;TValidator, TScript, TCheck>(bool expected)         where TValidator : IValidator, new()         where TScript : IScript, new()         where TCheck : ICheck, new(); } <\/code><\/pre>\n<p>  \u0418 \u0432 <code>DeclarativeCaseAttribute&lt;TValidator, TScript, TCheck><\/code> \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u043c \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u043f\u043e\u0434\u043c\u0435\u043d\u0435 \u0442\u0435\u0441\u0442\u0430 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     IEnumerable&lt;TestMethod> tests;     var type = suite.TypeInfo.Type;      if (!typeof(IDeclarativeTest).IsAssignableFrom(type))         tests = base.BuildFrom(method, suite)             .SetNotRunnable($\"{type} does not implement {typeof(IDeclarativeTest)}.\");     else         tests = base.BuildFrom(new MethodWrapper(type, nameof(IDeclarativeTest.Test)), suite);      return tests.Select(test =>     {         test.FullName = CreateName(test, suite, method,             suite => suite.FullName, type => type.FullName);         test.Name = CreateName(test, suite, method,             suite => suite.Name, type => type.Name);         return test;     }); } <\/code><\/pre>\n<p>  \u0414\u043b\u044f <code>IDeclarativeTest<\/code> \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e <code>DefaultDeclarativeTest<\/code>:<\/p>\n<pre><code class=\"cs\">using NUnit.Framework;  public class DefaultDeclarativeTest : IDeclarativeTest {     public void Test&lt;TValidator, TScript, TCheck>(bool expected)         where TValidator : IValidator, new()         where TScript : IScript, new()         where TCheck : ICheck, new()     {         \/\/ Arrange         IValidator validator = new TValidator();         IScript script = new TScript();         ICheck check = new TCheck();          \/\/ Act         bool actual = check.Check(validator, script);          \/\/ Assert         Assert.AreEqual(expected, actual);     } } <\/code><\/pre>\n<p>  \u0418 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0435 \u043f\u0440\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 <code>IDeclarativeTest<\/code> \u0441\u0430\u043c\u0438\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c:<\/p>\n<pre><code class=\"cs\">using NUnit.Framework;  [TestFixture] public class DemoTests : IDeclarativeTest {     public void Test&lt;TValidator, TScript, TCheck>(bool expected)         where TValidator : IValidator, new()         where TScript : IScript, new()         where TCheck : ICheck, new() =>         new DefaultDeclarativeTest().Test&lt;TValidator, TScript, TCheck>(expected);      \/\/ Tests... } <\/code><\/pre>\n<p>  \u0418 \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043c\u043e\u043c\u0435\u043d\u0442. \u0415\u0441\u043b\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043d\u0435 \u043f\u0443\u0441\u0442, \u0442\u043e \u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0441\u044f. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043e \u0438\u0437\u0431\u0435\u0436\u0430\u043d\u0438\u0435 \u043a\u043e\u0433\u043d\u0438\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u0441\u043e\u043d\u0430\u043d\u0441\u0430 \u0432 <code>DeclarativeCaseAttribute&lt;TValidator, TScript, TCheck><\/code> \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0440\u0435\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u043a \u043d\u0435\u043f\u0443\u0441\u0442\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u0430\u043c:<\/p>\n<pre><code class=\"cs\">public new IEnumerable&lt;TestMethod> BuildFrom(IMethodInfo method, Test suite) {     IEnumerable&lt;TestMethod> tests;     var type = suite.TypeInfo.Type;      if (!typeof(IDeclarativeTest).IsAssignableFrom(type))         tests = base.BuildFrom(method, suite)             .SetNotRunnable($\"{type} does not implement {typeof(IDeclarativeTest)}.\");     else if (!method.MethodInfo.IsIdle())         tests = base.BuildFrom(method, suite)             .SetNotRunnable(\"Method is not idle, i.e. does something.\");     else         tests = base.BuildFrom(new MethodWrapper(type, nameof(IDeclarativeTest.Test)), suite);          return tests.Select(test =>     {         test.FullName = CreateName(test, suite, method,             suite => suite.FullName, type => type.FullName);         test.Name = CreateName(test, suite, method,             suite => suite.Name, type => type.Name);         return test;     }); } <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">MethodInfoExtensions<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"cs\">using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Reflection; using System.Runtime.CompilerServices;  public static class MethodInfoExtensions {     private static readonly IReadOnlyCollection&lt;byte> idle = new[]     {         OpCodes.Nop,         OpCodes.Ret     }.Select(code => (byte)code.Value).ToArray();      public static bool IsIdle(this MethodInfo method)     {         var body = method.GetMethodBody();          if (body.LocalVariables.Any())             return false;          if (body.GetILAsByteArray().Except(idle).Any())             return false;          if (method.DeclaringType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)             .Any(candidate => IsLocalMethod(candidate, method)))             return false;          return true;     }      private static bool IsLocalMethod(MethodInfo method, MethodInfo container) =>         method.Name.StartsWith($\"&lt;{container.Name}>\") &amp;&amp;         method.GetCustomAttributes&lt;CompilerGeneratedAttribute>().Any(); } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/681886\/\"> https:\/\/habr.com\/ru\/post\/681886\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/3j\/eb\/ch\/3jebchajpsf7zjaylulv_ydnod4.png\" alt=\"image\" data-src=\"https:\/\/habrastorage.org\/webt\/3j\/eb\/ch\/3jebchajpsf7zjaylulv_ydnod4.png\"\/><br \/>  \u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0430\u0434 <a href=\"https:\/\/habr.com\/ru\/post\/662109\/\">\u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0435\u0439<\/a> (\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u044d\u0442\u043e\u0439 \u0441\u043e\u0432\u0441\u0435\u043c \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e) \u043f\u0440\u0438\u043d\u0435\u0441\u043b\u043e \u043c\u043d\u0435 \u043d\u0435 \u043c\u0438\u0440, \u043d\u043e <s>\u043c\u0435\u0447<\/s> \u043c\u0435\u0447\u0442\u0443 \u043e \u043c\u0438\u0440\u0435. \u041c\u0438\u0440\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0432\u044b\u0440\u0430\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u0433\u043e \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0438 \u0432\u043c\u0435\u0441\u0442\u043e<\/p>\n<pre><code class=\"cs\">[TestCase(typeof(Impl), \"command\")] public void Test(Type impl, string cmd) =>     ((I)Activator.CreateInstance(impl)).Do(cmd); <\/code><\/pre>\n<p>  \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c<\/p>\n<pre><code class=\"cs\">[TestCase&lt;Impl>(\"command\")] public void Test&lt;TImpl>(string cmd) where TImpl : I, new() =>     new TImpl().Do(cmd); <\/code><\/pre>\n<p>  \u0418 \u043e\u043d \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0431\u043b\u0438\u0436\u0435, \u0447\u0435\u043c \u044f \u043c\u043e\u0433 \u043f\u043e\u0434\u0443\u043c\u0430\u0442\u044c. \u0410 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u0448\u043b\u043e-\u043f\u043e\u0435\u0445\u0430\u043b\u043e\u2026  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-337091","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/337091","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=337091"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/337091\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=337091"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=337091"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=337091"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}