人们如何单位测试与实体框架6,你应该打扰吗? [英] How are people unit testing with Entity Framework 6, should you bother?

查看:100
本文介绍了人们如何单位测试与实体框架6,你应该打扰吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚开始使用单元测试和TDD。我以前一直在玩,但现在我决定将它添加到我的工作流程中并编写更好的软件。

I am just starting out with Unit testings and TDD in general. I have dabbled before but now I am determined to add it to my workflow and write better software.

我昨天问了一个问题,但是似乎自己成为一个问题。我已经坐下来开始实施一个服务类,我将使用它来从控制器中抽取业务逻辑,并使用EF6映射到特定的模型和数据交互。

I asked a question yesterday that kind of included this, but it seems to be a question on its own. I have sat down to start implementing a service class that I will use to abstract away the business logic from the controllers and map to specific models and data interactions using EF6.

问题是我已经打扰了自己,因为我不想在存储库中抽取EF(它将在特定查询等服务之外提供),并且想要测试我的服务(将使用EF Context)。

The issue is I have roadblocked myself already because I didn't want to abstract EF away in a repository (it will still be available outside the services for specific queries, etc) and would like to test my services (EF Context will be used).

这里我猜是个问题,有没有办法呢?如果是这样,人们如何在野外从事IQueryable引起的漏洞抽象以及 Ladislav Mrnka关于单元测试的问题不是直截了当的,因为Linq提供商在处理特定数据库时使用内存实现方面的差异。

Here I guess is the question, is there a point to doing this? If so, how are people doing it in the wild in light of the leaky abstractions caused by IQueryable and the many great posts by Ladislav Mrnka on the subject of unit testing not being straightforward because of the differences in Linq providers when working with an in memory implementation as apposed to a specific database.

我想测试的代码似乎很简单。 (这只是虚拟代码来尝试和理解我在做什么,我想使用TDD驱动创建)

The code I want to test seems pretty simple. (this is just dummy code to try and understand what i am doing, I want to drive the creation using TDD)

上下文

public interface IContext
{
    IDbSet<Product> Products { get; set; }
    IDbSet<Category> Categories { get; set; }
    int SaveChanges();
}

public class DataContext : DbContext, IContext
{
    public IDbSet<Product> Products { get; set; }
    public IDbSet<Category> Categories { get; set; }

    public DataContext(string connectionString)
                : base(connectionString)
    {

    }
}

服务

public class ProductService : IProductService
{
    private IContext _context;

    public ProductService(IContext dbContext)
    {
        _context = dbContext;
    }

    public IEnumerable<Product> GetAll()
    {
        var query = from p in _context.Products
                    select p;

        return query;
    }
}

目前我在做一些事情的心态:

Currently I am in the mindset of doing a few things:


  1. 使用这种方法模拟EF上下文 - 在单元测试时模拟EF或直接使用嘲笑框架在像moq这样的界面上,单位测试可能会经历的痛苦,但是不一定能够结束工作,并且可以通过集成测试进行备份。

  2. 也许使用类似努力来模拟EF - 我从来没有使用过它,不知道其他人是否在野外使用它?

  3. 不要麻烦测试任何简单的回调EF - 所以基本上直接调用EF(getAll等)的服务方法不是单元测试,只是集成测试?

  1. Mocking EF Context with something like this approach- Mocking EF When Unit Testing or directly using a mocking framework on the interface like moq - taking the pain that the unit tests may pass but not necessarily work end to end and back them up with Integration tests?
  2. Maybe using something like Effort to mock EF - I have never used it and not sure if anyone else is using it in the wild?
  3. Not bother testing anything that simply calls back to EF - so essentially service methods that call EF directly (getAll etc) are not unit tested but just integration tested?

任何人在那里实际上都在做t他在那里没有一个Repo并且有成功?

Anyone out there actually doing this out there without a Repo and having success?

推荐答案

这是一个我非常感兴趣的话题。谁说你不应该测试像EF和NHibernate这样的技术。他们是正确的,他们已经非常严格的测试,并且如之前的答案所说,花费大量的时间测试你不拥有的东西往往毫无意义。

This is a topic I'm very interested in. There are many purists who say that you shouldn't test technologies such as EF and NHibernate. They are right, they're already very stringently tested and as a previous answer stated it's often pointless to spend vast amounts of time testing what you don't own.

然而,您确实拥有下面的数据库!这是我认为这种方法发生故障的地方,您不需要测试EF / NH正确地完成他们的工作。您需要测试您的映射/实现正在与您的数据库一起使用。在我看来,这是您可以测试的系统中最重要的部分之一。

However, you do own the database underneath! This is where this approach in my opinion breaks down, you don't need to test that EF/NH are doing their jobs correctly. You need to test that your mappings/implementations are working with your database. In my opinion this is one of the most important parts of a system you can test.

严格来说,我们正在走出单元测试领域并进入集成测试,但校长保持不变。

Strictly speaking however we're moving out of the domain of unit testing and into integration testing but the principals remain the same.

您需要做的第一件事是能够模拟您的DAL,以便您的BLL可以独立于EF和SQL进行测试。 这些是您的单元测试。接下来,您需要设计集成测试以证明您的DAL,在我看来,这些都是重要的。

The first thing you need to do is to be able to mock your DAL so your BLL can be tested independently of EF and SQL. These are your unit tests. Next you need to design your Integration Tests to prove your DAL, in my opinion these are every bit as important.

有几件事要考虑:


  1. 您的数据库需要处于已知状态,每个测试。大多数系统使用备份或创建脚本。

  2. 每个测试必须是可重复的

  3. 每个测试必须是原子

设置数据库有两种主要方法,第一种是运行UnitTest创建DB脚本。这确保您的单元测试数据库在每次测试开始时始终处于相同的状态(您可以重置此操作或在事务中运行每个测试以确保这一点)。

There are two main approaches to setting up your database, the first is to run a UnitTest create DB script. This ensures that your unit test database will always be in the same state at the beginning of each test (you may either reset this or run each test in a transaction to ensure this).

您的其他选项是我所做的,为每个单独的测试运行特定的设置。我认为这是最好的方法,主要有两个原因:

Your other option is what I do, run specific setups for each individual test. I believe this is the best approach for two main reasons:


  • 您的数据库更简单,您不需要每个测试的完整模式

  • 每个测试都更安全,如果您在创建脚本中更改一个值,则不会使几十个其他测试无效。

不幸的是,这里的妥协是速度。运行所有这些测试需要花费一些时间来运行所有这些设置/拆卸脚本。

Unfortunately your compromise here is speed. It takes time to run all these tests, to run all these setup/tear down scripts.

最后一点,编写这么大的数量是非常困难的的SQL来测试你的ORM。这是我采取非常讨厌的方法(纯粹主义者在这里不同意我的看法)。我使用我的ORM来创建我的测试!而不是为系统中的每个DAL测试单独使用一个脚本,我有一个测试设置阶段来创建对象,将它们附加到上下文并保存它们。我然后运行我的测试。

One final point, it can be very hard work to write such a large amount of SQL to test your ORM. This is where I take a very nasty approach (the purists here will disagree with me). I use my ORM to create my test! Rather than having a separate script for every DAL test in my system I have a test setup phase which creates the objects, attaches them to the context and saves them. I then run my test.

这是远非理想的解决方案,但在实践中,我发现它很容易管理(特别是当你有几千个测试),否则你正在创建大量的脚本。纯粹的实用性。

This is far from the ideal solution however in practice I find it's a LOT easier to manage (especially when you have several thousand tests), otherwise you're creating massive numbers of scripts. Practicality over purity.

我无疑会在几年(几个月/天)回顾这个答案,不同意我自己的方法,但是这是我现在的方法。

I will no doubt look back at this answer in a few years (months/days) and disagree with myself as my approaches have changed - however this is my current approach.

要尝试总结我上面所说的一切,这是我典型的DB集成测试:

To try and sum up everything I've said above this is my typical DB integration test:

[Test]
public void LoadUser()
{
  this.RunTest(session => // the NH/EF session to attach the objects to
  {
    var user = new UserAccount("Mr", "Joe", "Bloggs");
    session.Save(user);
    return user.UserID;
  }, id => // the ID of the entity we need to load
  {
     var user = LoadMyUser(id); // load the entity
     Assert.AreEqual("Mr", user.Title); // test your properties
     Assert.AreEqual("Joe", user.Firstname);
     Assert.AreEqual("Bloggs", user.Lastname);
  }
}

这里要注意的关键是,两个循环是完全独立的。在执行RunTest时,您必须确保上下文被提交和销毁,您的数据只能来自第二部分的数据库。

The key thing to notice here is that the sessions of the two loops are completely independent. In your implementation of RunTest you must ensure that the context is committed and destroyed and your data can only come from your database for the second part.

编辑13 / 10/2014

我确实说过,我可能会在未来几个月修改这个模型。虽然我在很大程度上支持我上面提到的方法,但我稍微更新了我的测试机制。我现在倾向于在TestSetup和TestTearDown中创建实体。

I did say that I'd probably revise this model over the upcoming months. While I largely stand by the approach I advocated above I've updated my testing mechanism slightly. I now tend to create the entities in in the TestSetup and TestTearDown.

[SetUp]
public void Setup()
{
  this.SetupTest(session => // the NH/EF session to attach the objects to
  {
    var user = new UserAccount("Mr", "Joe", "Bloggs");
    session.Save(user);
    this.UserID =  user.UserID;
  });
}

[TearDown]
public void TearDown()
{
   this.TearDownDatabase();
}

然后单独测试每个属性

[Test]
public void TestTitle()
{
     var user = LoadMyUser(this.UserID); // load the entity
     Assert.AreEqual("Mr", user.Title);
}

[Test]
public void TestFirstname()
{
     var user = LoadMyUser(this.UserID);
     Assert.AreEqual("Joe", user.Firstname);
}

[Test]
public void TestLastname()
{
     var user = LoadMyUser(this.UserID);
     Assert.AreEqual("Bloggs", user.Lastname);
}

这种方法有几个原因:


  • 没有额外的数据库调用(一个设置,一个拆除)

  • 测试更细致,每个测试验证一个属性

  • 设置/ TearDown逻辑从测试方法本身中删除

我觉得这样测试类更简单,测试更细致(

I feel this makes the test class simpler and the tests more granular (single asserts are good)

编辑5/3/2015

这种方法的另一个修订。虽然类级别设置对于诸如加载属性的测试非常有帮助,但是在需要不同设置的情况下它们不太有用。在这种情况下,为每种情况设置一个新的类都是过度的。

Another revision on this approach. While class level setups are very helpful for tests such as loading properties they are less useful where the different setups are required. In this case setting up a new class for each case is overkill.

为了帮助这个,我现在倾向于有两个基类 SetupPerTest SingleSetup 。这两个类根据需要暴露框架。

To help with this I now tend to have two base classes SetupPerTest and SingleSetup. These two classes expose the framework as required.

SingleSetup 中,我们有一个非常相似的机制,如我的首先编辑一个例子是

In the SingleSetup we have a very similar mechanism as described in my first edit. An example would be

public TestProperties : SingleSetup
{
  public int UserID {get;set;}

  public override DoSetup(ISession session)
  {
    var user = new User("Joe", "Bloggs");
    session.Save(user);
    this.UserID = user.UserID;
  }

  [Test]
  public void TestLastname()
  {
     var user = LoadMyUser(this.UserID); // load the entity
     Assert.AreEqual("Bloggs", user.Lastname);
  }

  [Test]
  public void TestFirstname()
  {
       var user = LoadMyUser(this.UserID);
       Assert.AreEqual("Joe", user.Firstname);
  }
}

但是确保只有正确的内容被加载的引用可能使用SetupPerTest方法

However references which ensure that only the correct entites are loaded may use a SetupPerTest approach

public TestProperties : SetupPerTest
{
   [Test]
   public void EnsureCorrectReferenceIsLoaded()
   {
      int friendID = 0;
      this.RunTest(session =>
      {
         var user = CreateUserWithFriend();
         session.Save(user);
         friendID = user.Friends.Single().FriendID;
      } () =>
      {
         var user = GetUser();
         Assert.AreEqual(friendID, user.Friends.Single().FriendID);
      });
   }
   [Test]
   public void EnsureOnlyCorrectFriendsAreLoaded()
   {
      int userID = 0;
      this.RunTest(session =>
      {
         var user = CreateUserWithFriends(2);
         var user2 = CreateUserWithFriends(5);
         session.Save(user);
         session.Save(user2);
         userID = user.UserID;
      } () =>
      {
         var user = GetUser(userID);
         Assert.AreEqual(2, user.Friends.Count());
      });
   }
}

总而言之,这两种方法的工作取决于你正在尝试测试。

In summary both approaches work depending on what you are trying to test.

这篇关于人们如何单位测试与实体框架6,你应该打扰吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆