Azure功能和Cosmos DB演练的集成测试
#dotnet #database #测试 #azurefunctions

长期阅读

使用简单的设置运行集成测试的优势很大。您可以在此过程中早些时候找到错误,并为您提供代码按预期工作的保险。按照我的指南进行下面的指南,以运行Azure功能和Cosmos DB的集成测试。

更多的

集成测试 - 是一种软件测试,其中将软件应用程序的不同单元,模块或组件作为合并实体测试。

如果可能的话,可以在本地旋转依赖项是一个很好的实力。您应该再次选择运行测试本地数据库,而不是远程数据库。这有几个优势:

  1. 测试在本地运行的速度更快,而不是针对远程数据库。
  2. 您独立于其他开发人员进行测试。来自其他机器的测试数据不会影响您的数据库。

测试的系统

Azure功能是按需提供的云服务,可提供运行应用程序所需的所有不断更新的基础架构和资源。您专注于对您最重要的代码,以最有生产力的语言对您来说,其余的功能都可以处理。

在此示例中,我将togther togher azure函数放置。它将端点暴露于 create 汽车:

public class CarFunction
{
    private readonly ICarRepository _carRepository;
    private readonly ILogger<CarFunction> _logger;

    public CarFunction(ICarRepository carRepository, ILogger<CarFunction> logger)
    {
        _carRepository = carRepository ?? throw new ArgumentNullException(nameof(carRepository));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    [FunctionName("CreateCar")]
    public async Task<IActionResult> CreateCar([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "cars")] CarRequest request, HttpRequest req)
    {
        try
        {
            if (string.IsNullOrWhiteSpace(request.Name))
                return new BadRequestObjectResult("Name is mandatory.");

            var createdCar = await _carService.CreateCar(request);

            return new CreatedResult("/cars/" + createdCar.Id, createdCar);
        }
        catch (Exception ex)
        {
            return new ObjectResult(ex.Message) { StatusCode = 500 };
        }
    }
}

我们将使用Cosmos DB存储汽车。这是存储库的外观:

public class CarRepository : ICarRepository
{
    private CosmosClient _cosmosClient;
    private Container _container;

   public CarRepository(IOptions<Configuration> configuration)
    {
        this._cosmosClient = new CosmosClient(configuration.Value.ConnectionString);
        this._container = _cosmosClient.GetContainer("CarDatabase", "Cars");
    }
    public async Task<Car> Create(Car car)
    {
        var itemResponse = await _container.CreateItemAsync(car, new PartitionKey(car.Id));
        return itemResponse.Resource;
    }
}

由于我们使用函数应用程序,我们必须具有Startup.cs

public class Startup : FunctionsStartup
{
    private const string configurationSection = "Cars:Database";

    protected virtual IConfigurationRoot GetConfigurationRoot(IFunctionsHostBuilder functionsHostBuilder)
    {
        var local = new ConfigurationBuilder()
            .AddJsonFile(Path.Combine(Environment.CurrentDirectory, "local.settings.json"), true, true)
            .AddEnvironmentVariables()
            .Build();
        return local;
    }

    public override void Configure(IFunctionsHostBuilder builder)
    {
        var local = GetConfigurationRoot(builder);
        var config = new ConfigurationBuilder().AddEnvironmentVariables();
        var configurationSection = local.GetSection(configurationSection);
        builder.Services.Configure<Configuration>(configurationSection);
        var configuration = config.Build();
        builder.Services.AddInfrastructure(configuration);
    }
}

Azure Cosmos DB模拟器

我们将针对Azure CosmosDB的局部实例运行我们的间测试。可以在此处下载可用。启动后,它的外观将如何:

Azure Cosmos DB Emulator in browser

依赖注射

集成测试设置的残酷部分是限制依赖注入。您需要设置以下类:

testStartup

我们将从Startup类派生到定义测试的依赖性注入。

public class TestStartup : Startup
{
    protected override IConfigurationRoot GetConfigurationRoot(IFunctionsHostBuilder functionsHostBuilder)
    {
        var currentDirectory = AppDomain.CurrentDomain.BaseDirectory;
        var configuration = new ConfigurationBuilder()
            .SetBasePath(currentDirectory)
            .AddJsonFile("appsettings.json", true, true)
            .AddJsonFile("local.settings.json", true, true)
            .AddEnvironmentVariables()
            .Build();

        return configuration;
      }

      public override void Configure(IFunctionsHostBuilder builder)
      {
          base.Configure(builder);
          builder.Services.AddTransient<CarsFunction>();
      }
  }

配置

不建议将钥匙和秘密存储在GIT存储库中。对于本地开发,我们可以使用local.settings.json存储配置。但是,我们可以使用appsettings.json来管理配置。例如,我们可以在Azure管道中使用pipeline variablesFileTransformHere是我们如何实现它的示例。

local.settings.json(不应离开您的本地机器):

{
  "Cars": {
    "Database": {
      "ConnectionString": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
    }
  }
}

appsettings.json(停留在源控制中,由CI/CD管道纳入)

{
  "Cars": {
    "Database": {
      "ConnectionString": ""
    }
  }
}

测试初始化​​器

我们想使用TestStartup使用测试主机进行集成测试

public class TestsInitializer
{
    public TestsInitializer()
    {
        var host = new HostBuilder()
            .ConfigureWebJobs(builder => builder.UseWebJobsStartup(typeof(TestStartup), new WebJobsBuilderContext(), NullLoggerFactory.Instance))
            .Build();

        ServiceProvider = host.Services;
    }

    public IServiceProvider ServiceProvider { get; }
}

我们还需要通过从ICollectionFixture类派生来包括收集定义

[CollectionDefinition(Name)]
public class IntegrationTestsCollection : ICollectionFixture<TestsInitializer>
{
    public const string Name = nameof(IntegrationTestsCollection);
}

集成测试

我们最终可以实现我们的integration test

[Collection(IntegrationTestsCollection.Name)]
public class CarFunctionTests : IClassFixture<TestStartup>, IAsyncLifetime
{
    private CarFunction _carFunction;
    private readonly TestsInitializer _testsInitializer;
    private readonly CosmosClient _cosmosClient;
    private Container _container;
    private readonly string _carId;

    public CarFunctionTests(TestsInitializer testsInitializer)
    {
        _testsInitializer = testsInitializer;

        var cosmosDatabaseConfiguration = testsInitializer.ServiceProvider.GetService<IOptions<CarConfiguration>>();
        _cosmosClient = new CosmosClient(cosmosDatabaseConfiguration.Value.EndpointUri, cosmosDatabaseConfiguration.Value.PrimaryKey);
        _carFunction = _testsInitializer.ServiceProvider.GetService<CarFunction>();
    }
    [Fact]
    public async void TestCreateCar()
    {
        // Arrange
        var carName = $"BMW - {Guid.NewGuid()}";
        var carRequest = new CarRequest { Name = carName };

        // Act
        var response = await _carFunction.CreateCar(, new DefaultHttpContext().Request);
        var createdResponse = (CreatedResult)_response;
        _carId = (createdResponse.Value as Car).Id;

        // Assert
        Assert.IsType<CreatedResult>(createdResponse);
        Assert.Equal(carName, (createdResponse.Value as Car).Name);
    }
    public async Task InitializeAsync()
    {
        var databaseResponse = await _cosmosClient.CreateDatabaseIfNotExistsAsync("CarDatabase");
        var database = databaseResponse.Database;

        var containerResponse = await database.CreateContainerIfNotExistsAsync("Cars", "/id");
        _container = containerResponse.Container;
    }
    public async Task DisposeAsync()
    {
        await _container.DeleteItemAsync<Car>(_carId, new PartitionKey(_carId));
    }
}

运行测试

我们可以与dotnet test命令进行集成测试。

Result of running dotnet test command

概括

我们为Azure函数和Cosmos DB编写了集成测试。我们还使用了可更换的配置和配置的依赖注入为我们工作。

资源

  1. Install and develop locally with Azure Cosmos DB Emulator | Microsoft Learn.
  2. File transformation for application config.