asp.net 数据库核心集成测试
asp.net core Integration test for database
我正在尝试在 asp.netcore 项目中设置数据库的集成测试。我正在使用代码优先方法来创建数据库。
对于测试,我使用小块包 XUnit、FluentAssertions 和 NUnitestApadter3。
当我 运行 第一次测试时,测试通过。
[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;
}
}
我第二次 运行 测试并更改正在测试的值但失败了。我需要一种在每次测试后删除数据库然后 运行ning
迁移以使数据库达到正确的版本。
下面是我如何设置数据库。
startup.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();
}
}
总而言之,我需要在每次测试前删除数据库 运行,然后使用迁移更新数据库,最后执行单元。
看看Respawn. Another option to avoid migrations is to do a database snapshot/restore. Finally, you could, prior to each test, start a new 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();
}
}
您想使用真实数据库进行测试(集成测试),修改您的测试可能是简单的解决方案。
[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);
}
就共享状态而言,将集成测试彼此隔离开来的好选择并不多。
这里有几个:
让测试负责恢复它们更改的数据;
即每个测试更改数据,都应该实施一些额外的清理逻辑,以将其更改的数据恢复到以前的状态。每次测试都可能涉及大量噪音。
准备测试本身所需的数据,并仅以某种智能方式对这些数据进行断言;
这意味着测试既创建了它们需要的数据,又仅对该数据进行断言。即 none 的测试应该能够更改其他测试数据。可能不太容易实施,可能需要对应用程序本身进行调整。
为每个测试从头开始创建和初始化新数据库;
肯定会工作,但可能会很慢。
使用数据库备份将数据库恢复到您需要的位置;
仍然很可能很慢。
使用数据库快照进行恢复;
这个值得考虑,因为它既容易实施又足够快。
将每个测试包装在事务中并在之后还原;
一般情况下不会工作,因为事务无法嵌套,但可以用作对应用逻辑的此类部分的优化,这些部分不会自行创建事务。需要 TransactionScope 支持或某种方式将测试中创建的 DbTransaction 实例传递到应用程序数据层。
执行脚本删除所有数据并重新插入;
然后为了优化测试性能,您可以使用数据库池而不是唯一的数据库池,这样您就可以运行 并行测试。您可以手动准备专用测试数据库池,也可以按需动态创建它们,例如 Docker 和 TestContainers.
另一种优化方法是将测试分成两组:一组执行只读访问,另一组更改数据。如果每组使用一个数据库,您可能会跳过数据恢复阶段,而且可以安全地使用并行化进行只读测试。
我正在尝试在 asp.netcore 项目中设置数据库的集成测试。我正在使用代码优先方法来创建数据库。
对于测试,我使用小块包 XUnit、FluentAssertions 和 NUnitestApadter3。
当我 运行 第一次测试时,测试通过。
[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;
}
}
我第二次 运行 测试并更改正在测试的值但失败了。我需要一种在每次测试后删除数据库然后 运行ning 迁移以使数据库达到正确的版本。
下面是我如何设置数据库。 startup.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();
}
}
总而言之,我需要在每次测试前删除数据库 运行,然后使用迁移更新数据库,最后执行单元。
看看Respawn. Another option to avoid migrations is to do a database snapshot/restore. Finally, you could, prior to each test, start a new 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();
}
}
您想使用真实数据库进行测试(集成测试),修改您的测试可能是简单的解决方案。
[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);
}
就共享状态而言,将集成测试彼此隔离开来的好选择并不多。
这里有几个:
让测试负责恢复它们更改的数据;
即每个测试更改数据,都应该实施一些额外的清理逻辑,以将其更改的数据恢复到以前的状态。每次测试都可能涉及大量噪音。
准备测试本身所需的数据,并仅以某种智能方式对这些数据进行断言;
这意味着测试既创建了它们需要的数据,又仅对该数据进行断言。即 none 的测试应该能够更改其他测试数据。可能不太容易实施,可能需要对应用程序本身进行调整。
为每个测试从头开始创建和初始化新数据库;
肯定会工作,但可能会很慢。
使用数据库备份将数据库恢复到您需要的位置;
仍然很可能很慢。
使用数据库快照进行恢复;
这个值得考虑,因为它既容易实施又足够快。
将每个测试包装在事务中并在之后还原;
一般情况下不会工作,因为事务无法嵌套,但可以用作对应用逻辑的此类部分的优化,这些部分不会自行创建事务。需要 TransactionScope 支持或某种方式将测试中创建的 DbTransaction 实例传递到应用程序数据层。
执行脚本删除所有数据并重新插入;
然后为了优化测试性能,您可以使用数据库池而不是唯一的数据库池,这样您就可以运行 并行测试。您可以手动准备专用测试数据库池,也可以按需动态创建它们,例如 Docker 和 TestContainers.
另一种优化方法是将测试分成两组:一组执行只读访问,另一组更改数据。如果每组使用一个数据库,您可能会跳过数据恢复阶段,而且可以安全地使用并行化进行只读测试。