【问题标题】:Unit testing a two way EF relationship单元测试双向 EF 关系
【发布时间】:2012-12-17 16:40:36
【问题描述】:

我正在做一个小型实践项目来提高我的单元测试技能。我首先使用实体​​框架代码。

我正在使用 FakeDBSet,它适用于简单的实体列表。当返回实体树时,事情就不那么好了。特别是不维护两种方式的关系,因为这是实体框架魔法的一部分。

我有两个班级:

public class Book
{
    public virtual ICollection<Review> Reviews {get; set;}
}

public class Review
{
    public virtual Book Book { get; set;}
}

如果我将图书设置为评论,则评论不会添加到图书的评论集中。使用 EF 时会这样,但在我的假版本中不会。

有没有办法模拟这种行为,或者我不应该依赖 EF 来实现双向关系?还是模拟数据上下文只是浪费时间?

【问题讨论】:

    标签: entity-framework unit-testing


    【解决方案1】:

    这实际上是一个很常见的问题(并且没有一个很好的答案)。在 EF 内部发生了一个称为 fixups 的过程,该过程在检测更改循环内运行(在添加/删除和其他一些更改时触发的那个)。这会评估模型中的反向链接。当您开始模拟您的上下文时,您将丢失检测更改循环以及因此的修复。

    在过去,我通过了解我的模拟的这个特殊限制并确保我以正确的方式执行我的设置代码以使代码有意义(这让面对它非常不理想)来解决这个问题。这里的另一个选择是在单元测试中使用某种形式的真正轻量级数据库并继续使用 EF。

    【讨论】:

    • 我没有看到 mocking 和 EF 有很大的好处。 EF 的行为与 LINQ to objects 大不相同,典型业务应用程序中业务层中的大多数代码都是关于存储数据的。
    • @yonexbat 模拟的一个关键原因是性能。测试需要已知状态,这意味着理想情况下您应该为每个特定测试设置状态。如果您使用的是真实数据库,则为测试删除和创建数据库的时间可能在 1 秒左右。如果您将其扩展超过 1000 次左右的测试,它可能会变得非常难以管理。 IMO 边界测试非常重要,这意味着假设 EF 将按照您的预期执行您的应用程序内部逻辑将如何工作。无论如何,您正在使用模拟测试这些场景
    • 仅供参考:我有一个帖子正在编译differences between linq to objects and linq to entities。我添加了这个例子。
    • @GertArnold 很好的总结,我同意测试与 LTE 相比有点痛苦,但我仍然认为在您的测试中模拟 EF 有一些价值,但了解完整性真的很重要在这里击败了集成测试。我的感觉是,即使遇到所有困难,专门针对围绕数据库的逻辑位仍然有价值。
    • @LukeMcGregor 同意。例如,普通的单元测试非常适合测试代码是否被命中。我们甚至可以接受“正确”在两种环境中具有不同的含义。关键是要意识到差异。
    【解决方案2】:

    我发现的一个可能的解决方案是创建一个模拟 EF 修复代码的模拟对象。

    这是一个使用 Mocking 框架 NSubstitute 的示例:

    private static Book CreateMockBook()
    {
        var book = Substitute.For<Book>();
    
        // Mock EF fixup: Add a review to collection should also set book for the review
        book.Reviews.Add(Arg.Do<Review>((x) => { if(x.Book != book) x.Book = book; }));
    
        return book;
    }
    
    private static Review CreateMockReview()
    {
        var review = Substitute.For<Review>();
    
        // Mock EF fixup: Set a book for the review should also should add the review to book's review collection
        review.When(x  => x.Book = Arg.Any<Book>()).Do(x => review.Book.Review.Add(review));
    
        return review;
    }
    

    这很好用,但我不确定是否需要模拟这种行为是否表明我的测试过于复杂,或者我的代码正在使用它不应该使用的副作用。

    我很想知道其他人对此有何看法?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-28
      • 2011-02-09
      • 2013-12-28
      • 1970-01-01
      • 2011-08-10
      • 1970-01-01
      • 2014-07-08
      • 1970-01-01
      相关资源
      最近更新 更多