AWS .NET Core 单元测试加载非默认配置文件
AWS .NET Core unit test load non-default profile
为了开发,我有许多 AWS 配置文件,我使用 appsettings.json 中的 AWS 配置文件部分来定义我想要使用的配置文件:
"AWS": {
"Profile": "CorpAccount",
"Region": "us-east-1"
}
因为这不是默认配置文件,所以我需要在调试和 运行 单元测试 (xunit) 时使用命名配置文件的上下文。我想知道配置配置文件的最佳做法是什么。
这里是 class 展示了三种方法(两种在本地有效):
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();
}
这是一个设计问题。您将代码与实现问题紧密耦合,这使得单独测试代码变得困难。
您首先需要重构客户端的创建(实现关注)并将其抽象显式注入依赖项 class。
确实没有必要将 IConfiguration
之类的框架问题注入到您的服务中。这可以看作是一种代码味道,您的 class 没有遵循 Explicit Dependencies Principle 并且误导了它实际依赖的内容。
这样,从属 class 简化为
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
。
您的选择现在更多了,也更灵活了。
为了开发,我有许多 AWS 配置文件,我使用 appsettings.json 中的 AWS 配置文件部分来定义我想要使用的配置文件:
"AWS": {
"Profile": "CorpAccount",
"Region": "us-east-1"
}
因为这不是默认配置文件,所以我需要在调试和 运行 单元测试 (xunit) 时使用命名配置文件的上下文。我想知道配置配置文件的最佳做法是什么。
这里是 class 展示了三种方法(两种在本地有效):
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();
}
这是一个设计问题。您将代码与实现问题紧密耦合,这使得单独测试代码变得困难。
您首先需要重构客户端的创建(实现关注)并将其抽象显式注入依赖项 class。
确实没有必要将 IConfiguration
之类的框架问题注入到您的服务中。这可以看作是一种代码味道,您的 class 没有遵循 Explicit Dependencies Principle 并且误导了它实际依赖的内容。
这样,从属 class 简化为
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
。
您的选择现在更多了,也更灵活了。