单元测试 vs 集成测试:Entity Framework Core内存中的应用

5
我目前正在开发一个使用EF Core 2.0的ASP.NET Core 2.0 Web API。我计划实现仓储+工作单元模式,并已经创建了抽象、接口等。由于我采用TDD方法,我想在实施之前先编写这些接口的测试。
我面临的主要问题与语义有关。我的解决方案中创建了两个项目,一个是单元测试项目,另一个是集成测试项目。对我来说,显然一个还测试数据库的测试不是单元测试,因此应该放在集成测试项目中。但问题是,我计划使用EntityFrameworkCore.InMemory提供程序为每个测试启动虚假内存数据库。
所以,每个测试都应该具有以下结构:
[TestClass]
public class GamesRepositoryTest
{
    AppDbContext _context;
    IGamesRepository _repository;

    public GamesRepositoryTest()
    {

    }

    [TestInitialize]
    public void Initialize()
    {
        DbContextOptionsBuilder<AppDbContext> builder = new DbContextOptionsBuilder<AppDbContext>().UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString());
        _context = new AppDbContext(builder.Options);
        _repository = new GamesRepository(_context);
    }

    [TestCleanup]
    public void Cleanup()
    {
        _context.Database.EnsureDeleted();
    }
}

所以我的问题是,EntityFrameworkCore.InMemory是否提供足够的抽象层次,以便上述类可以被视为单元测试,还是应将其放置在IntegrationTests项目中?

2
EFCore InMemory数据库不是关系型数据库,因此它不支持强制执行约束的关系型数据库功能,例如外键关系等。它仅用于集成测试。如果您需要更接近关系型数据库的东西,您应该使用带有内存SQLite数据库的SqLite提供程序。请参阅文档获取更多信息。 - Tseng
@Tseng 我不想测试约束条件。我只对测试存储库中发生的查询感兴趣,并且正在寻找快速替换持久性层。我的问题可以描述为“涉及EntityFramework的每个测试都是集成测试吗?” - dimlucas
1
是的,确实如此。因为EF Core不能被模拟,所以涉及它的测试的唯一方法就是将其注入到您测试的类(存储库、服务、命令处理程序等)中。 - Tseng
1
@dimlucas,您的存储库实现似乎也与实现相关问题紧密耦合。如果它依赖于抽象,则关于内存中的这个和那个的讨论将不是一个问题,如果目标是进行实际的单元测试 - Nkosi
1
@dimlucas 不,你误解了。如果你的代码库松耦合,你可以使用特定的模拟依赖项来单独对它们进行单元测试。 - Nkosi
显示剩余3条评论
2个回答

22
新的内存提供程序已经让所有人感到困惑。首先,让我们搞清楚它的目的:它是一个测试数据提供程序。仅此而已。你完全可以模拟DbContext并返回静态列表或其他东西。内存提供程序只是为您提供了一种更简单的方式来设置测试框架。它不适用于集成测试,因为您实际上不会在生产中使用它。适当的集成测试应该命中您的生产设置的真实模拟。
话虽如此,Entity Framework Core的新内存提供程序的主要问题在于现在人们似乎滥用它来测试不应该测试的东西。EF Core已经经过充分测试,因此您应该只测试您的应用程序代码。再次强调,只需要将其视为一个mock即可,而无需实际设置一个mock。只要做到这一点,您就应该没问题。
根据所有这些,回答您的问题,如果您实际上正在创建集成测试,您不应该使用内存提供程序,因此您的测试应该是单元测试。只要确保您确实在进行单元测试即可。

我支持这个思路。 - Nkosi

4
如果您正在使用真实的dbContext进行测试,无论它是连接到RDBMS还是InMemory提供程序,这都是集成测试。为什么呢?因为您正在测试框架的实现,而不是在您的方法中运行的代码。单元测试只测试正在运行的“单元”代码,通常是单个方法(而不是它的协作者)。
EF Core的InMemory提供程序与使用真实数据库进行测试并不完全相同。实际上,它们之间存在相当多的差异。在我的经验中,它主要用于开发时间,以显示系统如何使用一些种子数据,并用于功能测试,可以验证整个系统是否按预期工作(例如使用TestServer)。
如果您的单元测试项目只包含单元测试,则不应具有任何基础设施特定代码的依赖项,例如EF Core。它通常只应该依赖于您正在测试的自己的项目和您用于测试的任何工具(例如xUnit、Moq等)。如果您发现正在引入基础设施依赖项,则很可能正在编写集成测试。

它部分错误。单元测试可以使用InMemory提供程序,同时您正在测试方法或类的行为,并且您的实现不包括特定的SQL指令或事务。 InMemory提供程序的另一种选择是sqllite。 - Eldar
1
在这种情况下,您不仅要测试您的代码,还要测试您的代码和EF Core以及它们如何协同工作。单元测试应只依赖于您自己的代码-其他依赖项应该受到控制。对EF Core工作方式的更新可能会破坏使用您的方法的测试。这样的更新不会影响单元测试(使用我的定义)。事实上,在使用EF Core InMemory时存在奇怪的行为。例如,您可以将一个实体添加到EF Core中进行跟踪,并在另一个实体的导航属性中神奇地出现,即使您从未在那里明确添加它。 - ssmith
我建议观看YouTube视频“使用Entity Framework Core InMemory提供程序进行简化单元测试| Jason Taylor [LATEST]”。我没有足够的空间和语言知识来提供足够的论据来描述单元测试和集成测试之间的边界。 - Eldar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接