4th*_*ace 13 .net c# unit-testing mocking
在.NET Windows应用程序中,我有一个名为EmployeeManager的类.在实例化时,此类将员工加载到尚未完成注册的数据库中的List中.我想在单元测试中使用EmployeeManager.但是,我不想涉及数据库.
根据我对这种情况的理解,我需要一个IEmployeeManager接口,它仅用于测试目的.这似乎不对,因为界面没有其他用途.但是,它允许我创建一些EmployeeManager测试类,在不涉及数据库的情况下加载员工.这样,我可以分配原本来自数据库的值.
以上是否正确,我需要嘲笑吗?模拟(Moq框架)似乎只是使用大量代码来执行简单的操作,例如分配属性.我不明白这一点.为什么我可以从IEmployeeManager创建一个简单的测试类来提供我需要的东西?
Mic*_*ays 10
控制反转是你的解决方案,而不是模拟对象.原因如下:
您模拟界面以确保使用IEmployeeManager的某些代码正确使用它.您没有使用测试代码来证明IEmployeeManager的工作原理.因此,必须有另一个类采用IEmployeeManager,例如,您实际上将使用模拟对象进行测试.
如果您实际上只是在测试EmployeeManager,那么您可以做得更好.考虑依赖注入.通过这种方式,您将公开EmployeeManager的构造函数,该构造函数将至少使用一个参数作为接口.您的EmployeeManager代码将在内部使用此接口进行其需要进行的任何特定于实现的调用.
见战略模式
这将引导您进入一个完整的,令人兴奋的控制反转世界.当你深入研究它时,你会发现像这样的问题已经通过AutoCac,Ninject和Structure Map等IoC容器得到了有效的解决.
模拟接口非常棒,您可以模拟一个接口然后传递到IoC.但是你会发现IoC是一个更加强大的解决方案.是的,虽然您可能只是为了测试而实施第二种替代方案,但出于这个原因仍然很重要 - 从EmployeeManager的业务逻辑中分离测试中的策略.
根据我对这种情况的理解,我需要一个IEmployeeManager接口,它仅用于测试目的.这似乎不对,因为界面没有其他用途.
非常值得创建界面.另请注意,界面实际上有多种用途:
EmployeeManager.通过使用接口,您可以防止意外依赖某些特定于数据库的东西.EmployeeManager,您可以自由地交换其实现,而无需重新编译应用程序的其余部分.当然,这取决于项目结构,组件数量等,但它仍然允许这种类型的重用.但是,它将允许我创建一些EmployeeManager测试类,在不涉及数据库的情况下加载员工....我不明白这一点.为什么我可以从IEmployeeManager创建一个简单的测试类来提供我需要的东西?
正如一张海报指出的那样,听起来你正在谈论创建一个存根测试类.模拟框架可用于创建存根,但关于它们的最重要的功能之一是它们允许您测试行为而不是状态.现在让我们看一些例子.假设如下:
interface IEmployeeManager {
void AddEmployee(ProspectiveEmployee e);
void RemoveEmployee(Employee e);
}
class HiringOfficer {
private readonly IEmployeeManager manager
public HiringOfficer(IEmployeeManager manager) {
this.manager = manager;
}
public void HireProspect(ProspectiveEmployee e) {
manager.AddEmployee(e);
}
}
Run Code Online (Sandbox Code Playgroud)
当我们测试HiringOfficer的HireEmployee行为,我们感兴趣的验证,他正确地传达给员工的经理,这个观点的员工添加为雇员.你会经常看到这样的东西:
// you have an interface IEmployeeManager and a stub class
// called TestableEmployeeManager that implements IEmployeeManager
// that is pre-populated with test data
[Test]
public void HiringOfficerAddsProspectiveEmployeeToDatabase() {
var manager = new TestableEmployeeManager(); // Arrange
var officer = new HiringOfficer(manager); // BTW: poor example of real-world DI
var prospect = CreateProspect();
Assert.AreEqual(4, manager.EmployeeCount());
officer.HireProspect(prospect); // Act
Assert.AreEqual(5, manager.EmployeeCount()); // Assert
Assert.AreEqual("John", manager.Employees[4].FirstName);
Assert.AreEqual("Doe", manager.Employees[4].LastName);
//...
}
Run Code Online (Sandbox Code Playgroud)
上述测试合理......但不好.这是一项基于状态的测试.也就是说,它通过检查某个操作之前和之后的状态来验证行为.有时这是测试事物的唯一方法; 有时它是测试某些东西的最佳方式.
但是,测试行为往往更好,这就是模拟框架闪耀的地方:
// using Moq for mocking
[Test]
public void HiringOfficerCommunicatesAdditionOfNewEmployee() {
var mockEmployeeManager = new Mock<EmployeeManager>(); // Arrange
var officer = new HiringOfficer(mockEmployeeManager.Object);
var prospect = CreateProspect();
officer.HireProspect(prospect); // Act
mockEmployeeManager.Verify(m => m.AddEmployee(prospect), Times.Once); // Assert
}
Run Code Online (Sandbox Code Playgroud)
在上面我们测试了唯一真正重要的事情 - 招聘官员告诉员工经理需要添加新员工(一次,只有一次......虽然我实际上不会费心检查这个案例).不仅如此,我还证实了我要求招聘人员雇用的员工是由员工经理添加的.我测试了批评行为.我甚至不需要一个简单的测试存根.我的测试时间更短.实际行为更加明显 - 可以看到交互并验证对象之间的交互.
可以使您的存根测试类记录交互,但随后您将模拟模拟框架.如果您要测试行为 - 使用模拟框架.
正如另一张海报所提到的,依赖注入(DI)和控制反转(IoC)非常重要.我上面的例子不是一个很好的例子,但两者都应该仔细考虑并明智地使用.有很多的写在主题 可用.
1 - 是的,思考仍然是可选的,但我强烈推荐它;).