繁体   English   中英

asp.net core 数据库集成测试

[英]asp.net core Integration test for database

我正在尝试在 asp.netcore 项目中设置数据库的集成测试。 我使用代码优先的方法来创建数据库。
对于使用 nuget 包 XUnit、FluentAssertions 和 NUnitestApadter3 的测试 iam。 当我第一次运行测试时,测试通过。

[Collection("Integration test collection")]
public class BookServiceTest : IntegrationTestBase
{
    [Fact]
    public void CanCreateUser()
    {
        using (var context = GivenBPDContext())
        {
            var Book = new BookService(context);

            Data.Database.Entities.Book book = Book.AddNewBook("test");
            context.SaveChanges();

            book.Id.Should().NotBe(0);
            book.Name.Should().Be("test");
        }
    }
}

public class IntegrationTestBase
{
    protected static BPDContext GivenBPDContext()
    {

        var context = new BPDContext(new DbContextOptionsBuilder().Options);

        return context;
    }
    // i tried dropping the database here and it do not work
}

一个非常基本的逻辑测试

public class BookService
{
    private BPDContext _context;

    public BookService(BPDContext context)
    {
        _context = context;
    }

    public Book AddNewBook(string name)
    {
        var book = _context.Books
            .FirstOrDefault(x => x.Name == name);

        if (book == null)
        {
            book = _context.Books.Add(new Data.Database.Entities.Book
            {
                Name = name,
            }).Entity;
        }

        return book;
    }
}

我第二次运行测试并更改正在测试的值时失败。 我需要一种在每次测试后删除数据库的方法,然后运行迁移以使数据库升级到正确的版本。

下面是我如何设置数据库。 启动文件

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddTransient<IBPDRepository, BPDRepository>();
    services.AddDbContext<BPDContext>();
}

public class BPDContext:DbContext
{
    public DbSet<Entities.Book> Books { get; set; }
    public DbSet<Entities.User> User { get; set; }
    public DbSet<Entities.Reviewer> Reviewer { get; set; }

    public BPDContext(DbContextOptions options):base(options)
    {

    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        //maybe put something in if its in testing mode
        optionsBuilder.UseSqlServer("Server = (localdb)\\mssqllocaldb;Database = BookProjectDatabase;Trusted_Connection = True; ", options => options.MaxBatchSize(30));
        optionsBuilder.EnableSensitiveDataLogging();
    }

}

总之,我需要在每次测试运行之前删除数据库,然后使用迁移更新数据库,最后执行单元。

看看Respawn 避免迁移的另一种方法是执行数据库快照/还原。 最后,您可以在每次测试之前启动一个新的TransactionScope ,然后在事务之后调用其Dispose()方法,而无需调用其Complete()方法。 这将中止事务,并将数据库回滚到运行测试之前的状态。

删除数据库有些繁琐,可能会增加运行测试所需的时间。

您可以使用InMemoryDbContext进行测试操作。 通过InMemoryDbContext ,您不必创建物理数据库。 此外,您可以轻松处置它。

[Collection("Integration test collection")]
public class BookServiceTest : IDisposible
{
    private BDPContext _context;
    public BookServiceTest()
    {
        DbContextOptions<BPDContext> options = new DbContextOptionsBuilder<BPDContext>()
            .UseInMemoryDatabase(GetType().Name)
            .Options;

        _context = new BPDContext(options);
    }

    [Fact]
    public void CanCreateUser()
    {
        var Book = new BookService(context);

        Data.Database.Entities.Book book = Book.AddNewBook("test");
        context.SaveChanges();

        book.Id.Should().NotBe(0);
        book.Name.Should().Be("test");
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

您想使用真实的db(集成测试)进行测试,修改测试可能是简单的解决方案。

[Fact]
public void CanCreateUser()
{
    string bookName = DateTime.Now + "test book";
    var Book = new BookService(context);


    Data.Database.Entities.Book book = Book.AddNewBook(bookName);
    context.SaveChanges();

    book.Id.Should().NotBe(0);
    book.Name.Should().Be(bookName);
}

在共享状态方面,将集成测试彼此隔离的好方法并不多。

以下是一些:

  1. 使测试负责恢复他们更改的数据;

    即每个测试更改数据,都应该实现一些额外的清理逻辑,以将更改的数据恢复到以前的状态。 在每次测试中可能会涉及大量噪音。

  2. 准备测试本身所需的数据,并仅对这些数据以某种智能方式进行断言;

    这意味着测试既创建了他们需要的数据,又只对这些数据进行断言。 即没有一个测试应该能够改变其他测试数据。 可能不是那么容易实现,并且可能需要对应用程序本身进行调整。

  3. 为每个测试从头开始创建和初始化新数据库;

    肯定会工作,但可能会很慢。

  4. 使用数据库备份将数据库恢复到您需要的点;

    仍然很可能很慢。

  5. 使用数据库快照进行还原;

    这是一个值得考虑的问题,因为它既易于实现又足够快。

  6. 将每个测试包装在事务中,然后将其还原;

    由于事务不能嵌套,因此一般不会工作,但可以用作应用程序逻辑的此类部分的优化,这些部分不会自行创建事务。 需要 TransactionScope 支持或某种方式将测试中创建的 DbTransaction 实例传递给应用程序数据层。

  7. 执行脚本删除所有数据并重新插入;

    也是不错的选择。 RespawnReseed等库可以帮助您轻松生成脚本。

然后为了优化测试性能,您可能会使用一个数据库池而不是唯一的,这样您就可以并行运行测试。 您可以手动准备一个专用测试数据库池,也可以使用 Docker 和TestContainers等按需动态创建它们。

另一种优化方法是将测试分成两组:一组进行只读访问,另一组进行数据变异。 如果您为每组使用一个数据库,您可能会跳过数据恢复阶段,而且可以安全地将并行化用于只读测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM