C# 如何正确地使用相互依赖的单元测试方法?
考虑以下代码:C# 如何正确地使用相互依赖的单元测试方法?,c#,unit-testing,C#,Unit Testing,考虑以下代码: private readonly Dictionary<Type, Component> _components = new Dictionary<Type, Component>(); public Component this [Type type] { get { Component component; _components.TryGetVa
private readonly Dictionary<Type, Component> _components = new Dictionary<Type, Component>();
public Component this [Type type]
{
get
{
Component component;
_components.TryGetValue(type, out component);
return component;
}
}
public void AddComponent(Component component)
{
_components.Add(component.GetType(), component);
}
private readonly Dictionary\u components=new Dictionary();
公共组件此[类型]
{
得到
{
组分;
_组件。TryGetValue(类型,输出组件);
返回组件;
}
}
公共void AddComponent(组件组件)
{
_添加(component.GetType(),component);
}
如您所见,AddComponent
添加到私有\u components
变量中。但是测试这种情况的唯一方法是使用索引器。这很好,但是为了测试索引器,我也必须调用AddComponent
换句话说,在索引器和AddComponent
的单元测试中,每个测试都必须调用这两种方法。这似乎是在制造不必要的耦合。如果索引器中存在错误,则我的TestAddComponent
没有理由失败
这里的最佳实践是什么?我是否使用反射来获取组件?嘲笑?还有别的吗?在我看来,单元测试不应该通过反射来强制实现它的目标。我认为在这种测试中,两者应该在同一个测试中一起测试。但这只是一个观点 但是,您可以进行多个测试,更改指令的顺序。尝试添加多个,然后访问第一个,然后是最后一个,然后从中间添加一个。每个测试都是一个场景,具有不同的顺序和插入次数。您甚至可以测试必须发生的异常状态。。。例如,如果您试图获取未插入的内容
我认为,单元测试的存在是为了模拟使用,或者是为了执行规范。我不想看到程序的每一位是否都是正确的,因为这会扼杀灵活性。我建议使用接口和/或虚拟方法以及MOQ。这样,您就可以停止对不想测试的方法的调用,并使它们返回您想要的结果。当您使用Microsoft单元测试框架时,框架会生成一个私有访问器类。这应该允许您访问私有类型。有关更多信息,请查看Microsoft提供的此页面:
特别是本节:创建可以访问内部、私有和朋友方法的单元测试。您有两个选项:
- 如果我使用
,那么添加的组件应该可以通过索引器访问AddComponent
- 如果我使用索引器,我应该能够访问通过
AddComponent
作为替代方案,假设我们选择选项1并使用私有反射来检查
\u组件的状态。在这种情况下,我们实际测试的时间是:
- 如果我使用
AddComponent
,则添加的组件应添加到\u components
- 如果我使用索引器,我应该能够访问
\u components
我们现在不仅在测试类的内部行为(因此,如果实现发生变化,即使类按预期工作,测试也会失败),而且我们正在编写的测试数量刚刚增加了一倍
最重要的是,通过增加我们测试的复杂性,我们增加了测试本身存在bug的可能性——例如,如果我们在测试2中犯了错误会怎么样。我们查了一些完全不同的私人领域?在这种情况下,我们不仅为自己做了更多的工作,而且我们甚至没有测试我们想要测试的实际行为 这里有两种选择
如前所述,一起测试功能
修改类,以便可以将其包装在测试线束中
测试线束应与单元测试一起定义,并公开验证功能是否正常工作所需的元素。您应该在单元测试中使用测试线束,而不是直接使用类
public class MyClass
{
protected readonly Dictionary<Type, Component> _components = new Dictionary<Type, Component>();
public Component this [Type type]
{
get
{
Component component;
_components.TryGetValue(type, out component);
return component;
}
}
public void AddComponent(Component component)
{
_components.Add(component.GetType(), component);
}
}
public class MyClassTestHarness : MyClass
{
public Dictionary<Type, Component> Components
{
get
{
return _components;
}
}
}
如果您想这样做,请移出类进入构造函数(暂时忽略IoC框架),并使用接口引用依赖项:
class ComponentManager
{
private readonly IDictionary<Type, Component> _components;
public ComponentManager()
: this(new Dictionary<Type, Component>())
{ }
public ComponentManager(IDictionary<Type, Component> components)
{
_components = components;
}
public Component this[Type type]
{
get
{
Component component;
_components.TryGetValue(type, out component);
return component;
}
}
public void AddComponent(Component component)
{
_components.Add(component.GetType(), component);
}
}
类组件管理器
{
专用只读IDictionary组件;
公共组件管理器()
:此(新词典())
{ }
公共组件管理器(IDictionary组件)
{
_组件=组件;
}
公共组件此[类型]
{
得到
{
组分;
_组件。TryGetValue(类型,输出组件);
返回组件;
}
}
公共void AddComponent(组件组件)
{
_添加(component.GetType(),component);
}
}
现在您可以模拟依赖关系并验证交互
然而,由于缺乏额外的行为,我相信真正实用的方法是直接和通过
class ComponentManager
{
private readonly IDictionary<Type, Component> _components;
public ComponentManager()
: this(new Dictionary<Type, Component>())
{ }
public ComponentManager(IDictionary<Type, Component> components)
{
_components = components;
}
public Component this[Type type]
{
get
{
Component component;
_components.TryGetValue(type, out component);
return component;
}
}
public void AddComponent(Component component)
{
_components.Add(component.GetType(), component);
}
}
class ComponentManager
{
public Dictionary<Type, Component> Components { get; private set; }
public ComponentManager()
{
Components = new Dictionary<Type, Component>();
}
}