C#: 如何对私有静态成员进行单元测试?

10

我有一个类,构造函数如下:

private static Dictionary<Contract, IPriceHistoryManager> _historyManagers = new Dictionary<Contract, IPriceHistoryManager>();

假设有2个方法:

 public void AddSth()
 {
    _historManagers.Add(new Contract(), new PriceHistoryManager());
 }

 public int CountDic()
 {
    return _historyManagers.Count(); 
 }

问题: 当运行单元测试时,没有办法“重置”字典,如果我创建多个类实例的独立单元测试,则“CountDic”会给出不可预测的结果,我无法测试列表条目。

问题: 一般来说,这种方法是否被认为是“不好”的?如果是,应该如何更好/更易于进行单元测试? 如果不是,请问如何最好地进行单元测试?

谢谢。


为什么字典是静态的? - mtijn
因为它被用在一个WCF服务中,而我想要多个实例来使用它。 - David
你需要静态字典来做什么? - Yves M.
你是把静态字典当作一种缓存来使用吗? - Yves M.
@David,你可不可以通过创建一个自定义的ServiceHostFactory来解决多个实例的问题,并注入一个共享的历史管理器来创建ServiceHost实例,这样你就不需要为了测试而妥协你的设计了。 - Sam Holder
2个回答

21

不要害怕为测试目的公开操作。摘自Roy Osherove的《单元测试的艺术》:当丰田汽车制造一辆汽车时,会提供测试点。英特尔制造芯片时也有测试点。对于汽车或芯片存在仅用于测试的接口。为什么我们不这样做来编写软件?如果使用 ResetHistory() 方法会完全破坏您的API,那答案是 yes 吗?

如果答案是yes,那就创建该方法,但将其设置为internal。然后可以使用程序集InternalsVisibleTo将内部信息公开给单位测试库。您现在拥有一个完全为测试而创建的方法,但是对于您的公共API没有任何更改。


有时候是可以的,但在这里我更愿意使用持久化类,这样整体设计更加清晰。 - Jeremy McGee
1
《单元测试的艺术》中另一句经典语录是:“当我们为代码编写单元测试时,我们向对象模型添加了另一个最终用户(即测试)。该最终用户与原始用户同样重要,但在使用模型时具有不同的目标。”(第77页)换句话说,测试是系统的重要用户,并且它们证明了使应用程序的某些部分公开的必要性(当然,在编写可重用的类库时可能会有所不同)。 - Steven
我喜欢这个想法,会尝试一下! - David

3
在你的例子中,CountDic 不是不可预测的:它应该在调用 AddSth() 之后返回比之前多一个。

所以:
[Test]
public void Test()
{
    var item = new ClassUnderTest();
    int initialCount = item.CountDic();

    item.AddSth();

    int finalCount = item.CountDic();

    Assert.That(finalCount == initialCount + 1);
}

一般来说,测试维护状态的类可能会比较棘手。有时需要将维护状态的部分(在您的情况下是字典)拆分到另一个类中。然后,您可以模拟该“存储”类并通过构造函数传递它。


1
你所知道的(和你可以测试的)只是计数器的变化 - 这是对AddSth()函数调用次数的计数。 - Jeremy McGee
@David:如果你为每个单元测试创建一个实例,那么你需要重新考虑是否真的需要静态字典。在每个单元测试中,在调用AddSth()之前,先获取CountDic()。这样你就可以得到初始值来进行测试。 - Yves M.
如果你有3个Test()方法: Test1(), Test2(), Test3(),那么当它们被一个实例(例如TestRunner.exe)执行时,你将不能再获得可预测的结果,因为静态列表条目将保持在"测试边界"之上,也没有办法重置它们。 - David
你对列表中的结果做什么?只是计数吗?还是稍后存储它们? - Yves M.
离题:但他们应该考虑添加聊天功能;-) - Yves M.
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接