如何在C#语言中实施架构规则

语言: CN / TW / HK

译者 | 李睿

审校 | 梁策 孙淑娟

开发人员为了确保他们编写的代码达到预期的目的,需要进行单元测试。有一些开源框架可用于对.NET应用程序进行单元测试,即NUnit和xUnit.Net。开发人员应该始终在软件开发工作流程中加入单元测试,以减少或消除应用程序中的错误。

还可以利用ArchUnit或NetArchTest等框架来编写有助于实施架构规则的单元测试。受基于Java的ArchUnit启发,Ben Morris开发的NetArchTest是一个简单的框架,可用于在.NET Framework或.NET Core以及.NET6项目中实施架构规则。

本文讨论了在C#语言中执行架构规则的重要性以及如何利用NetArchTest来实现这一点。要使用本文中提供的代码示例,需要在系统中安装Visual Studio 2022。

1.强制执行架构规则的必要性

有大量静态代码分析框架和工具可用于检查.NET、.NET Core或.NET 6中的代码质量。对于初学者来说,SonarQube和NDepend是两个流行的工具。静态代码分析也可作为Visual Studio的一部分。

然而,这些工具很少能帮助开发人员在源代码中保留架构设计模式或强制执行架构规则。如果不定期验证或执行这些规则,其应用程序的设计或架构将随着时间的推移而退化。最终会发现维护代码库已成为一项艰巨的任务。

虽然静态代码分析工具可以帮助开发人员验证或强制执行通用最佳实践,但可以利用NArchTest创建单元测试,以在其.NET、.NET Core和.NET 6应用程序中强制执行架构规则。这些包括代码库中的类设计、命名和依赖关系的约定。

开发人员可以在单元测试方法中使用NArchTest,然后将这些测试方法合并到构建和发布管道中,以便在每次签入时自动验证架构规则。

2.在Visual Studio2022中创建单元测试项目

首先,使用xUnit测试项目模板在Visual Studio 2022中创建单元测试项目。以下步骤将在Visual Studio 2022中创建一个新的单元测试项目:

(1)启动Visual Studio 2022 IDE。 (2)点击“创建新项目”。 (3)在“创建新项目”窗口中,从显示的模板列表中选择“xUnit测试项目”。 (4)单击下一步。 (5)在“配置新项目”窗口中,指定新项目的名称和位置。 (6)根据开发人员的偏好,选择“将解决方案和项目放在同一目录”复选框。 (7)单击下一步。 (8)在接下来显示的“附加信息”窗口中,从顶部的下拉列表中选择.NET 6.0作为目标框架。将“身份验证类型”保留为“无”(默认)。 (9)确保未选中“启用Docker”、“为HTTPS配置”和“启用开放API支持”复选框,因为不会在此处使用这些功能。 (10)单击创建。

这将在Visual Studio 2022中创建一个新的xUnit项目。将在本文的后续部分中使用该项目。

3.在Visual Studio2022中创建类库项目

现在在Visual Studio 2022中创建一个类库项目。按照以下步骤将在Visual Studio 2022中创建一个新的类库项目:

(1)启动Visual Studio 2022 IDE。

(2)点击“创建新项目”。

(3)在“创建新项目”窗口中,从显示的模板列表中选择“类库”。

(4)单击下一步。

(5)在“配置新项目”窗口中,指定新项目的名称和位置。

(6)单击下一步。

(7)在接下来显示的“附加信息”窗口中,从顶部的下拉列表中选择.NET 6.0作为目标框架。

(8)单击创建。

这将在Visual Studio 2022中创建一个新的类库项目。将在本文的后续部分中使用该项目。

4.在.NET 6中创建模型类

假设类库项目的名称是Core.Infrastructure。在解决方案资源管理器窗口中,选择此项目,然后单击“添加->新建文件夹”以将新的解决方案文件夹添加到项目中。模型应与其解决方案文件夹同名。

现在在Models解决方案文件夹中创建一个名为BaseModel的类并插入以下代码:

public abstract class BaseModel
    {
        public int Id { get; set; }
    }

再创建两个名为Product和Customer的模型类。这两个类中的每一个都应该扩展BaseModel类,如下所示:

public class Product: BaseModel
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Customer: BaseModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

5.在.NET 6中创建服务类

在同一个项目中创建另一个解决方案文件夹,并将其命名为Services。在此解决方案文件夹中创建一个名为IBaseService的接口,并为其提供以下代码:

public interface IBaseService
{
    public void Initialize();
}

Initialize方法必须由实现此接口的所有类实现。Product Service和Customer Service类实现了IBaseService接口,如下面的代码片段所示:

//ProductService.cs
using Core.Infrastructure.Models;
namespace Core.Infrastructure.Services
{
    public sealed class ProductService: IBaseService
    {
        public void Initialize()
        {
            //Write your implementation here
        }
        public List<Product> GetProducts()
        {
            return new List<Product>();
        }
    }
}
//CustomerService.cs
using Core.Infrastructure.Models;
namespace Core.Infrastructure.Services
{
    public sealed class CustomerService: IBaseService
    {
        public void Initialize()
        {
            //Write your implementation here
        }
        public List<Customer> GetCustomers()
        {
            return new List<Customer>();
        }
    }
}

需要注意的是,出于这个简单实现的目的,ProductService类和CustomerService类的Initialize方法都保留为空。开发人员可以为这些编写自己的实现。

6.安装NetArchTest.RulesNuGet包

现在将NetArchTest.RulesNuGet包添加到项目中。为此,需要在解决方案资源管理器窗口中选择项目,然后右键单击并选择“管理NuGet包”。在NuGet包管理器窗口中,搜索NetArchTest.Rules包并安装它。

或者,可以通过NuGet包管理器控制台输入下面显示的行来安装包。

PM> Install-Package NetArchTest.Rules

7.在.NET6中编写架构单元测试

最后,应该编写架构单元测试来检查被测源代码是否符合标准。需要注意,此处的“标准”一词是相对的,可以假设这些标准将由您定义。

以下测试方法验证服务类的名称是否带有服务后缀。

public void ServiceClassesShouldHaveNameEndingWithService()
{
    var result = Types.InCurrentDomain()
                 .That().ResideInNamespace(("Core.Infrastructure.Services"))
                 .And().AreClasses()
                 .Should().HaveNameEndingWith("Service")
                 .GetResult();
    Assert.True(result.IsSuccessful);
}

可以使用另一条规则来验证所有服务类是否都实现了IBaseService接口。以下测试方法说明了如何实现这一点。

public void ServiceClassesShouldImplementIBaseServiceInterface()
{
   var result = Types.InCurrentDomain()
                .That().ResideInNamespace(("Core.Infrastructure.Services"))
                .And().AreClasses()
                .Should().ImplementInterface(typeof(IBaseService))
                .GetResult();
   Assert.True(result.IsSuccessful);
}

还可以有一个规则来验证服务类是公共的而不是密封的。如果这些类是密封的,将无法进一步扩展它们。

public void ServiceClassesShouldBePublicAndNotSealed ()
{
    var result = Types.InCurrentDomain()
                .That().ResideInNamespace(("Core.Infrastructure.Services"))
                .Should().BePublic().And().NotBeSealed()
                .GetResult();
    Assert.True(result.IsSuccessful);
}

当运行这些测试方法时,应该会发现它们都通过了测试,也就是取得了成功。尝试更改代码并重新运行测试,以检查是否符合讨论的规则。

正在运行NetArchTest单元测试

需要记住的是,在较新版本的C#语言中,可以在接口中拥有成员的默认实现。因此,如果有一个接口由一个或多个类实现,可以在接口中编写默认实现。如果编写的代码在接口的所有实现中都是通用的,就是正确的。

原文链接:https://www.infoworld.com/article/3656703/how-to-enforce-architecture-rules-in-csharp.html