.NET 6 (ASP.NET Core 6.0) 从测试项目中获取 Startup 或 Program Assembly
.NET 6 (ASP.NET Core 6.0) get Startup or Program Assembly from Test project
在 .NET Core 3.1 和 .NET 5 中,我们进行了 Xunit
测试,如下例所示。它确保每个 Controller
都有一个 AuthorizeAttribute
以防止安全漏洞。
将我们的 Web 项目升级到 ASP.NET Core 6 的最小托管模型时,不再需要 Program
和 Startup
类。一切正常,但以下情况除外:
var types = typeof(Startup).Assembly.GetTypes();
查看命名空间 Example.Web
,我也看不到任何要从中加载程序集的 类。如何在 .NET 6 中加载 Program.cs
程序集?
.NET 5 中的示例:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Example.Web.Tests.ControllerTests
{
public class AuthorizeAttributeTest
{
[Fact]
public void ApiAndMVCControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ControllerBase>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute), true) as Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
Assert.NotNull(attribute);
}
}
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
}
}
快速回答是您可以在您的应用程序中引用 any(可访问)class 以获得对程序集的引用。它不必是 Program
或 Startup
class,也不必在根命名空间中。
您显然希望选择 class 您希望持久的,并且不会在以后的版本中被重命名或删除。从历史上看,Startup
class 符合该标准。然而,对于 ASP.NET Core 6 最小托管模型,这显然不再适用。
鉴于此,您可以采用两种方法。
选项 1:将您的 Assembly
引用锚定在应用程序之外 class
第一个选项是从您的应用程序中锚定任意 public
class。例如,您可以使用您的控制器之一。只要它被编译到同一个程序集中,Assembly.GetTypes()
调用就会产生相同的结果。这可能看起来像:
using Example.Web.Controllers;
var types = typeof(ExampleController).Assembly.GetTypes();
这种方法的主要缺点是 class 完全是任意的,将来可能会被移动或重命名。当然,如果 确实 发生了,您可能无论如何都需要更新您的单元测试,所以这没什么大不了的。
选项 2:将您的 Program
class 公开给您的测试程序集
另一种选择是将您的 Assembly
引用锚定在从您的 Program.cs
文件编译的 class 之外,这与您之前的方法非常相似。这需要了解编译器如何处理此文件。
当您使用 ASP.NET Core 6 最小托管模型时,您实际上是在利用 C# 9 的 top-level 语句。编译器自动将任何 top-level 语句放入名为 Program
的 class 中,没有名称空间。
Note: That happens to align with your use of Program.cs
, but that's completely incidental; you could rename Program.cs
to MyWebApplication.cs
, but the class will still be named Program
.
问题是这个Program
class被标记为internal
,因此您的单元测试程序集无法访问。
不过,您可以通过将 ASP.NET 核心程序集的内部标记为对单元测试程序集可见来解决这个问题。这可以通过将以下内容添加到例如你的 AssemblyInfo.cs
:
[assembly: InternalsVisibleTo("Example.Web.Tests")]
或者,正如 @kal 在评论中指出的那样,通过在 csproj
文件中设置以下内容:
<ItemGroup>
<InternalsVisibleTo Include="Example.Web.Tests" />
</ItemGroup>
完成后,您可以使用以下方式访问您的 Program
class:
var types = typeof(Program).Assembly.GetTypes();
我不太喜欢以这种方式公开我的程序集的内部结构,但这是单元测试中相当常见的做法,所以我将它作为一个选项包括在内,以防您已经这样做了。
最终,这与第一个选项并没有什么不同——你仍然将你的 Assembly
引用锚定在不同的 class 之外——但它的优点是锚定到我们知道的 class 将永远存在,而不是一些任意的 application-specific class。这样在阅读代码的时候可能也会感觉更直观。
在 .NET Core 3.1 和 .NET 5 中,我们进行了 Xunit
测试,如下例所示。它确保每个 Controller
都有一个 AuthorizeAttribute
以防止安全漏洞。
将我们的 Web 项目升级到 ASP.NET Core 6 的最小托管模型时,不再需要 Program
和 Startup
类。一切正常,但以下情况除外:
var types = typeof(Startup).Assembly.GetTypes();
查看命名空间 Example.Web
,我也看不到任何要从中加载程序集的 类。如何在 .NET 6 中加载 Program.cs
程序集?
.NET 5 中的示例:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Example.Web.Tests.ControllerTests
{
public class AuthorizeAttributeTest
{
[Fact]
public void ApiAndMVCControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ControllerBase>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute), true) as Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
Assert.NotNull(attribute);
}
}
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
}
}
快速回答是您可以在您的应用程序中引用 any(可访问)class 以获得对程序集的引用。它不必是 Program
或 Startup
class,也不必在根命名空间中。
您显然希望选择 class 您希望持久的,并且不会在以后的版本中被重命名或删除。从历史上看,Startup
class 符合该标准。然而,对于 ASP.NET Core 6 最小托管模型,这显然不再适用。
鉴于此,您可以采用两种方法。
选项 1:将您的 Assembly
引用锚定在应用程序之外 class
第一个选项是从您的应用程序中锚定任意 public
class。例如,您可以使用您的控制器之一。只要它被编译到同一个程序集中,Assembly.GetTypes()
调用就会产生相同的结果。这可能看起来像:
using Example.Web.Controllers;
var types = typeof(ExampleController).Assembly.GetTypes();
这种方法的主要缺点是 class 完全是任意的,将来可能会被移动或重命名。当然,如果 确实 发生了,您可能无论如何都需要更新您的单元测试,所以这没什么大不了的。
选项 2:将您的 Program
class 公开给您的测试程序集
另一种选择是将您的 Assembly
引用锚定在从您的 Program.cs
文件编译的 class 之外,这与您之前的方法非常相似。这需要了解编译器如何处理此文件。
当您使用 ASP.NET Core 6 最小托管模型时,您实际上是在利用 C# 9 的 top-level 语句。编译器自动将任何 top-level 语句放入名为 Program
的 class 中,没有名称空间。
Note: That happens to align with your use of
Program.cs
, but that's completely incidental; you could renameProgram.cs
toMyWebApplication.cs
, but the class will still be namedProgram
.
问题是这个Program
class被标记为internal
,因此您的单元测试程序集无法访问。
不过,您可以通过将 ASP.NET 核心程序集的内部标记为对单元测试程序集可见来解决这个问题。这可以通过将以下内容添加到例如你的 AssemblyInfo.cs
:
[assembly: InternalsVisibleTo("Example.Web.Tests")]
或者,正如 @kal 在评论中指出的那样,通过在 csproj
文件中设置以下内容:
<ItemGroup>
<InternalsVisibleTo Include="Example.Web.Tests" />
</ItemGroup>
完成后,您可以使用以下方式访问您的 Program
class:
var types = typeof(Program).Assembly.GetTypes();
我不太喜欢以这种方式公开我的程序集的内部结构,但这是单元测试中相当常见的做法,所以我将它作为一个选项包括在内,以防您已经这样做了。
最终,这与第一个选项并没有什么不同——你仍然将你的 Assembly
引用锚定在不同的 class 之外——但它的优点是锚定到我们知道的 class 将永远存在,而不是一些任意的 application-specific class。这样在阅读代码的时候可能也会感觉更直观。