3

对于开发,我有许多 AWS 配置文件,我使用appsettings.json中的 AWS 配置文件部分来定义我想要使用的配置文件:

"AWS": {
    "Profile": "CorpAccount",
    "Region": "us-east-1"
  }

由于这不是默认配置文件,因此在调试和运行单元测试 (xunit) 时,我需要使用命名配置文件的上下文。我想知道的是配置配置文件的最佳实践是什么。

这是一个展示三种方法的类(两种在本地工作):

public class EmailQueueService : IEmailQueueService
{
    private IConfiguration _configuration;
    private readonly ILogger _logger;

    public EmailQueueService(IConfiguration configuration, ILogger<EmailQueueService> logger)
    {
        _configuration = configuration;
        _logger = logger;
    }

    public async Task<bool> Add1Async(ContactFormModel contactForm)
    {
        var sqsClient = new AmazonSQSClient();

        var sendRequest = // removed for clarity

        var response = await sqsClient.SendMessageAsync(sendRequest);

        return response.HttpStatusCode == System.Net.HttpStatusCode.OK;
    }

    public async Task<bool> Add2Async(ContactFormModel contactForm)
    {
        var sqsClient = _configuration.GetAWSOptions().CreateServiceClient<IAmazonSQS>();

        var sendRequest = // removed for clarity

        var response = await sqsClient.SendMessageAsync(sendRequest);

        return response.HttpStatusCode == System.Net.HttpStatusCode.OK;
    }

    public async Task<bool> Add3Async(ContactFormModel contactForm)
    {
        var sqsClient = new AmazonSQSClient(credentials: Common.Credentials(_configuration));

        var sendRequest = // removed for clarity

        var response = await sqsClient.SendMessageAsync(sendRequest);

        return response.HttpStatusCode == System.Net.HttpStatusCode.OK;
    }

    public AWSCredentials Credentials(IConfiguration config)
    {
        var chain = new CredentialProfileStoreChain();

        if (!chain.TryGetAWSCredentials(config.GetAWSOptions().Profile, out AWSCredentials awsCredentials))
        {
            throw new Exception("Profile not found.");
        }

        return awsCredentials;
    }
}

结果:

  • Add1Async在本地失败,因为它使用默认配置文件而不是“CorpAccount”。
  • Add2Async在本地工作,但似乎是一种创建新实例的奇怪方式。
  • Add3Async在本地工作,但在部署时可能会失败,因为config.GetAWSOptions().Profile在本地环境之外不存在。

为了完整起见,这是一个单元测试,我从中调用它:

[Fact]
public async void AddAsyncTest()
{
    // Arrange 
    var configuration = TestConfigure.Getconfiguration();

    var service = new EmailQueueService(configuration, Mock.Of<ILogger<EmailQueueService>>());

    // Act
    var result = await service.AddAsync(ContactFormModelMock.GetNew());

    // Assert
    Assert.True(result);
}

public static IConfiguration Getconfiguration()
{
    var builder = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddEnvironmentVariables();

    return builder.Build();
}
4

1 回答 1

7

这是一个设计问题。您将代码与实现问题紧密耦合,这使得孤立地测试代码变得困难。

您首先需要重构客户端的创建(实施关注点)并将其抽象显式注入依赖类。

确实没有必要将框架问题注入IConfiguration到您的服务中。这可以看作是代码异味,您的类没有遵循显式依赖原则,并且误导了它实际依赖的内容。

这样,依赖类就简化为

public class EmailQueueService : IEmailQueueService {
    private readonly IAmazonSQS sqsClient 
    private readonly ILogger logger;

    public EmailQueueService(IAmazonSQS sqsClient, ILogger<EmailQueueService> logger) {
        this.sqsClient = sqsClient;
        this.logger = logger;
    }

    public async Task<bool> AddAsync(ContactFormModel contactForm) {

        var sendRequest = //...removed for clarity

        var response = await sqsClient.SendMessageAsync(sendRequest);

        return response.HttpStatusCode == System.Net.HttpStatusCode.OK;
    }
}

现在将客户端的创建及其对选项的依赖项移至组合根目录,这将在您的启动中。

public Startup(IHostingEnvironment env) {
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
    Configuration = builder.Build();
}

IConfiguration Configuration;

public void ConfigureServices(IServiceCollection services) {
    // Add framework services.
    services.AddMvc();

    // Add AWS services
    var options = Configuration.GetAWSOptions();
    services.AddDefaultAWSOptions(options);
    services.AddAWSService<IAmazonSQS>();
    services.AddAWSService<IAmazonDynamoDB>();

    services.AddSingleton<IEmailQueueService, EmailQueueService>();

    //...omitted for brevity
}

参考使用 .NET Core 配置适用于 .NET 的 AWS 开发工具包

这负责清理代码,以便它可以在本地、部署或测试时运行。

测试时,您可以在被测对象外部创建客户端,并根据需要专门针对测试进行配置

public class EmailQueueServiceTests {
    [Fact]
    public async Task Should_AddAsync() {
        // Arrange 
        var configuration = GetConfiguration();
        IAmazonSQS client = configuration.GetAWSOptions().CreateServiceClient<IAmazonSQS>();

        var subject = new EmailQueueService(client, Mock.Of<ILogger<EmailQueueService>>());

        // Act
        var result = await subject.AddAsync(ContactFormModelMock.GetNew());

        // Assert
        Assert.True(result);
    }

    static IConfiguration GetConfiguration() {
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();

        return builder.Build();
    }
}

IConfiguration如果需要,也可以完全模拟,或者使用AWSOptions测试所需的值手动创建。

您的可用选择现在增加且更加灵活。

于 2019-05-06T22:29:01.583 回答