1

假设我使用 Moq 对 Entity Framework 6 进行了以下单元测试:

    public void Save_Employee_via_context()
    {
        var MockContext = new Mock<DcmDataContext>();

        var MockSet = new Mock<DbSet<Employee>>();

        MockContext.Setup(m => m.Employees).Returns(MockSet.Object);

        var service = new GeneralService(MockContext.Object);

        //Test valid inputs

        for (int i = 0; i < TestData.ValidEmployees.Count; i++)
        {
            service.AddEmployee(TestData.ValidEmployees[i]);
            //veryfy that it was properly inserted
            Assert.AreEqual(TestData.ValidEmployees[i],MockSet.Object.Find(TestData.ValidEmployees[i].EmployeeID));
        }
        //Ensure that the proper methods were called each time.  It is implied that this happened if the above 
        //Assert methods passed, but double checking never killed anybody
        MockSet.Verify(m => m.Add(It.IsAny<Employee>()), Times.Exactly(TestData.ValidEmployees.Count));
        MockContext.Verify(m => m.SaveChanges(), Times.Exactly(TestData.ValidEmployees.Count));

        //Test invalid Inputs

        MockSet = new Mock<DbSet<Employee>>();
        //InvalidEmployees is a Dictionary<Employee,Type>, where Type is the type of exeption that should eb thrown if 
        //You attempt to add that Employee
        foreach (var pair in TestData.InvalidEmployees)
        {
            try
            {
                service.AddEmployee(pair.Key);
                //AddEmployee *SHOULD* throw an exception here here.. if not...
                Assert.Fail();
            }
            catch (Exception ex)
            {
                //Was it the exception that I was expecting to catch?
                Assert.Equals(ex.GetType(), pair.Value);
            }
        }
        //ensure that nothing new has been added (redundant, I know, but it doesn't hurt)
        MockSet.Verify(m => m.Add(It.IsAny<Employee>()), Times.Never);
        MockContext.Verify(m => m.SaveChanges(), Times.Exactly(TestData.ValidEmployees.Count));

    }

TestData是我拥有的静态类,它包含我要测试的每种模型类型的列表,以及每种模型类型的几个测试用例,包括有效和无效输入。

我这样创建我的测试是因为我的对象可能相当大(Employee例如,有大约 15 个属性),因此我想要运行大量的测试用例以使每个测试都彻底。我不想复制/粘贴每个需要它的方法的每个测试样本数据数组,所以我想将它存储在一个静态容器中。

但是,我觉得这会带来问题。例如, 的属性之一EmployeePosition。你知道,他们有什么工作。它是必需的属性,如果该位置为空或数据库中尚不存在,则应引发异常。这意味着为了使上述测试有效,我还需要一些模拟位置。哦,但是每个职位都有一个Department属性……所以也需要设置……

你知道我要去哪里吗?如果没有一整套测试数据来测试它,我如何正确测试我的代码?那么,我想我将不得不编写一整套测试数据。我做到了。

问题是,我把它放在哪里?我决定将所有内容都放入 TestData 类中。
然而,这提出了一系列问题。初始化是最大的一项,因为我觉得我必须对我的测试数据进行中性处理才能使初始化更加可行。例如,我所有的导航属性可能都必须是null. 我怎么能让我的ValidEmployees每个人都有一个List<Clients>,并且每个Client人都有一个分配Employee,而没有再次将每个 Employee 复制为每个人都将拥有的属性Client,并且List<Employee>每个Position人都将拥有。在 ValidClientsClients = {ValidClients[0],ValidClients[1]内部ValidEmployees和内部拥有不是很好吗?SalesRepresentative = ValidEmployees[0]

我也觉得我需要导航数据。将要

 Assert.AreEqual
 (
     TestData.ValidEmployees[i],
     MockSet.Object.Find(TestData.ValidEmployees[i].EmployeeID
 ) 

如果ValidEmployees 中没有navigationData,仍然返回true?这是否意味着我应该找到另一种确保状态的方法?

无论如何,这些都是我遇到的问题。我只是设置我的单元测试完全错误吗?我还应该如何获得健壮、独立、干燥和准确的单元测试?我在这里想念什么?

感谢任何帮助,即使这意味着以不同的心态从头开始。这是我第一个非常认真地对待测试的项目,但我觉得它进展得不太顺利。因此,对于文字墙感到抱歉。有时我觉得我没有问正确的问题来到达我想去的地方。

4

2 回答 2

2

回答问题的第二部分(关于测试数据)。我不会为我的测试使用测试数据类,这会使测试变得脆弱并可能引入细微的错误,因为对测试数据类的更改可能会影响不同测试类中的许多不相关的测试,google Object Mother Anti-Pattern。

我之前走的是 Object Mother 路线,最终得到了一个包含我所有测试数据的整个项目。当我需要测试数据的新变体时,我不断更改/添加到对象母亲。不用说,这个项目很快就变得臃肿且无法维护。此外,一些单元测试由于这些更改而开始失败(因为依赖于这些共享数据),以及花费额外的时间来修复它们,这确实令人烦恼。因此,我决定更好的方法是让单元测试类拥有它们使用的数据(换句话说,测试数据包含在测试夹具中)。为了实现这一点,我引入了一个测试数据构建器项目,但这意味着我在编写单元测试时需要维护和更改(在需要时)另一个项目。老实说,我 我宁愿专注于单元测试本身,而不是担心管道问题,这就是我开始使用测试数据生成器的原因。我在用着NBuilder但我也听说过关于AutoFixture的好消息。这些将允许您构建测试数据,专注于与您正在测试的行为相关的部分,并让构建器为不相关的部分生成随机数据(您可以控制/覆盖随机生成器)。我认为,为了提高单元测试的可读性,你应该只显示(或强调)影响你正在测试的行为的数据,而不是用不相关的信息来膨胀你的单元测试。

例子:

var validEmployees = Builder<Employees>.CreateListOfSize(10)
                                       .All()
                                       .With(x => x.IsActive = true)
                                       .And(x => x.LeaveDate = null)
                                       .Build();
于 2013-11-26T09:08:37.087 回答
0

查看您的测试,您的 service.AddEmployee 似乎做得太多:)。您可以拆分验证逻辑等。

考虑这样的测试(引导您的设计)。这使用FakeDbSet

[Test]
public void Should_add_any_valid_employees_and_save_them()
{
   //arrange
   var validator = new Mock<IEmployeeValidator>();
   validator.Setup(v => v.Validate(It.IsAny<Employee>())).Returns(true);

   // ... setup the context and the dbset

   var service = new MyService(validator.Object, mockContext.Object)

   var newData = new List<EmployeeDto>
      {
         new EmployeeDto{Id = 1},
         new EmployeeDto{Id = 2}
      }

   // act
   service.AddEmployees(newData);

   // assert
   mockContext.Verify(c => c.SaveChanges(), Times.Once());
   Assert.True(fakeDbSet.Count == newData.Count);
   CollectionAssert.AreEquivalent( newData.Select(e=>e.Id), mockData.Select(e=>e.Id));
}

[Test]
public void Should_not_add_any_invalid_employees()
{
   //arrange
   var validator = new Mock<IEmployeeValidator>();
   validator.Setup(v => v.Validate(It.IsAny<Employee>())).Returns(false);

   // ... setup the context and the dbset

   var service = new MyService(validator.Object, mockContext.Object)

   var newData = new List<EmployeeDto>
      {
         new EmployeeDto{Id = 1},
         new EmployeeDto{Id = 2}
      }

   // act
   service.AddEmployees(newData);

   // assert
   mockContext.Verify(c => c.SaveChanges(), Times.Never());
   Assert.True(fakeDbSet.Count == 0);
   CollectionAssert.IsEmpty( mockData );
}

您也可以加入混合 IEmployeeDtoToEmployee 映射器,因此您也可以抽象这部分功能。

于 2013-09-24T23:19:46.540 回答