【发布时间】:2013-09-24 14:52:36
【问题描述】:
假设我使用 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 个属性),因此我想为每个测试用例按顺序运行测试要彻底。我不想复制/粘贴每个需要它的方法的每个测试样本数据数组,所以我想将它存储在一个静态容器中。
不过,我觉得这会带来问题。例如,Employee 的属性之一是Position。你知道,他们有什么工作。它是必需的属性,如果该位置为空或数据库中尚不存在,则应引发异常。这意味着为了使上述测试有效,我还需要一些模拟位置。哦,但是每个职位都有一个Department 属性...所以也需要设置...
你知道我的意思吗?如果没有一整套测试数据来测试它,我如何正确测试我的代码?那么,我想我将不得不编写一整套测试数据。我做到了。
问题是,我把它放在哪里?我决定将所有内容都放入 TestData 类中。
然而,这提出了一系列问题。初始化是最大的一项,因为我觉得我必须对我的测试数据进行中性处理才能使初始化更加可行。例如,我所有的导航属性可能都必须是null。我怎么能让我的ValidEmployees 每个都有一个List<Clients>,每个Client 都有一个分配的Employee,而无需再次将每个员工硬复制为Client 的属性,并且在List<Employee> 中每个Position 都会有。在 ValidClients 中包含 Clients = {ValidClients[0],ValidClients[1] 和 ValidEmployees 和 SalesRepresentative = ValidEmployees[0] 不是很好吗?
我也觉得我需要导航数据。会
Assert.AreEqual
(
TestData.ValidEmployees[i],
MockSet.Object.Find(TestData.ValidEmployees[i].EmployeeID
)
如果 ValidEmployees 中没有 navigationData,仍然返回 true?这是否意味着我应该找到另一种确保状态的方法?
无论如何,这些都是我遇到的问题。我只是设置我的单元测试完全错误吗?我还应该如何获得健壮、独立、干燥和准确的单元测试?我在这里错过了什么?
感谢任何帮助,即使这意味着以不同的心态从头开始。这是我第一个非常认真地对待测试的项目,但我觉得它进展得不太顺利。因此,对于文字墙感到抱歉。有时我觉得我没有问正确的问题来到达我想去的地方。
【问题讨论】:
-
只是略过您的测试方法,它违反了单元测试的基本规则,即每个测试只应尝试测试一件事(因此称为单元测试)。作为开始,我将该方法分解为一系列单元测试,每个测试谨慎的功能片段。您在这篇文章中有很多问题,但作为开始,我建议您阅读 Mark Seeman 的关于单元测试中 AAA 模式的博客blog.ploeh.dk/2013/06/24/…
-
@Damon 我以前确实看过那个博客,我完全明白了。真的我这样做,但我只是一个人在一个我有如此相互依赖的对象的世界中,拥有一个简单的
new BasketItem("Chocolate", 50, 3),排列几乎感觉就像在嘲笑我(不是双关语)。我是否应该注定在我的每个单元 500 单元测试中使用 40 行长的 Arrange 语句?正是这样的数字几乎让我想忘记测试,如果需要的话,以后再拿我的肿块。为什么即使是中等复杂度我也找不到任何东西?
标签: c# entity-framework unit-testing tdd moq