【问题标题】:asp.net core Integration test for databaseasp.net core 数据库集成测试
【发布时间】:2018-05-09 13:52:56
【问题描述】:

我正在尝试在 asp.netcore 项目中设置数据库的集成测试。我正在使用代码优先的方法来创建数据库。
对于使用 nudget 包 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;
    }
}

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

以下是我如何设置数据库。 启动.cs

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();
    }

}

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

【问题讨论】:

    标签: c# database integration-testing


    【解决方案1】:

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

    删除数据库有点笨拙,可能会增加运行测试所需的时间。

    【讨论】:

    • 感谢您的建议。 Respawn 运行良好。
    【解决方案2】:

    您可以使用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();
        }
    }
    

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

    [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);
    }
    

    【讨论】:

    • InMemoryDbContext 是否被更多地视为单元测试而不是集成测试。我在想同时使用这两种方法,因为 InMemoryDbContext 会更快地运行测试,但可能无法捕捉到与其他方式一样多的问题。我想我将不得不阅读更多关于此的内容。感谢您的帮助。
    • InMemoryDbContext 在单元测试期间很好,但是;在集成测试期间,真实数据库应该更好。在这种情况下,我为您提供可以更改测试方法。您可以使用日期值创建书名,因此您可以确保它不会进入数据库。当您想确保AddNewBook 不保存书籍已经在数据库中时,您可以创建相同的书籍并将其保存在数据库中。保存更改后,您可以再尝试将其添加到数据库中。但是,@Kit 提供的Respawn 在我看来更有用。
    • 我想我会尝试实现这两种方法。再次感谢您的帮助。
    【解决方案3】:

    在共享状态方面,没有太多好的选择可以将您的集成测试相互隔离。

    这里有几个:

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

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

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

      这意味着测试都在创建他们需要的数据,然后只对这些数据进行断言。即没有一个测试应该能够改变其他测试数据。实施起来可能并不容易,并且可能需要对应用程序本身进行调整。

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

      肯定会起作用,但可能会很慢。

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

      仍然很可能很慢。

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

      这个值得考虑,因为它既容易实现又足够快。

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-01
      • 1970-01-01
      • 2019-09-12
      • 2020-09-14
      • 2018-04-10
      • 2020-06-16
      • 2010-09-08
      • 2018-05-24
      相关资源
      最近更新 更多