【问题标题】:AWS .NET Core unit test load non-default profileAWS .NET Core 单元测试加载非默认配置文件
【发布时间】:2019-05-07 22:42:01
【问题描述】:

对于开发,我有许多 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();
}

【问题讨论】:

    标签: c# amazon-web-services asp.net-core .net-core aws-sdk-net


    【解决方案1】:

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

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

    确实没有必要将IConfiguration 之类的框架问题注入到您的服务中。这可以被视为一种代码异味,您的班级没有遵循Explicit Dependencies Principle,并且误导了它实际依赖的内容。

    这样,依赖类就简化为

    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
    }
    

    参考Configuring the AWS SDK for .NET with .NET Core

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

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

    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

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

    【讨论】:

    • 逻辑清晰,细节丰富。我已将 IConfiguration 添加到构造函数中,同时试图弄清楚如何设置获取非默认配置文件以正确加载。快速提问,我在服务配置示例中看到您将 IEmailQueueService 设置为 AddSingleton。我设置为 AddTransient 因为它是轻量级和无状态的。您是否在实现中看到应该将其视为 Singleton 的东西?对于 IAmazonSQS 的同样问题,最好设置为 Transient: services.AddAWSService(lifetime: ServiceLifetime.Transient);
    • 我今天试试你的方法。
    • @Josh 当我检查AddAWSService 的源代码时,我看到他们在后台默认将它们(服务)添加为单例,所以我就照做了。在这种情况下,这更多是个人喜好问题。我最初有范围,但注意到单例并记得它们不能很好地混合。
    • @Josh 虽然电子邮件服务可能是轻量级的,但它的依赖关系可能不是。如果每次解决服务时都必须增加严重依赖,这可能是一个性能问题。如果没有单一实例的影响,那么这就是我的建议。然而,这只是我根据我的经验得出的看法。
    • 你的解决方案奏效了,很高兴你看到了更高的层次。我必须等待 3 小时才能获得赏金。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-11
    • 1970-01-01
    • 1970-01-01
    • 2019-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多