Тестовый модуль AWS .NET Core загружает нестандартный профиль - PullRequest
2 голосов
/ 03 мая 2019

Для разработки у меня есть несколько профилей AWS, я использую раздел профиля AWS в appsettings.json , чтобы определить профиль, который я хочу использовать:

"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 не существует вне локальной среды.

Для полноты здесь приведен блоктест, из которого я звоню:

1 Ответ

5 голосов
/ 07 мая 2019

Это проблема дизайна.Вы тесно связываете свой код с проблемами реализации, которые затрудняют тестирование вашего кода изолированно.

Сначала вам нужно реорганизовать создание клиента (проблема реализации) и явно внедрить его абстракцию в зависимый класс.

На самом деле нет необходимости внедрять проблемы инфраструктуры, такие как 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
}

Ссылка Настройка AWS SDKдля .NET с .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, созданный вручную со значениями, необходимыми для тестирования.

Ваш выбор теперь увеличен и стал более гибким.

...