繁体   English   中英

单元测试跟踪状态的类

[英]Unit testing a class that tracks state

我正在抽象一类我的历史跟踪部分,所以它看起来像这样:

private readonly Stack<MyObject> _pastHistory = new Stack<MyObject>();

internal virtual Boolean IsAnyHistory { get { return _pastHistory.Any(); } }

internal virtual void AddObjectToHistory(MyObject myObject)
{
  if (myObject == null) throw new ArgumentNullException("myObject");
  _pastHistory.Push(myObject);
}

internal virtual MyObject RemoveLastObject()
{
  if(!IsAnyHistory) throw new InvalidOperationException("There is no previous history.");
  return _pastHistory.Pop();
}

我的问题是我想单元测试Remove会返回最后添加的对象。

  • AddObjectToHistory
  • RemoveObjectToHistory - >返回通过AddObjectToHistory放入的内容

但是,如果我必须先调用Add,它不是真正的单元测试吗? 但是,我能看到以真正的单元测试方式执行此操作的唯一方法是在构造函数中传入Stack对象或模拟出IsAnyHistory ......但是模拟我的SUT也很奇怪。 所以,我的问题是,从教条的角度来看,这是一个单元测试吗? 如果没有,我该如何清理它...是构造函数注入我唯一的方法? 它似乎只是需要传递一个简单的对象? 是否可以将这个简单的物体推出注入?

这些方案有两种方法:

  1. 干扰设计,比如使_pastHistory internal / protected或注入堆栈
  2. 使用其他(可能是单元测试的)方法来执行验证

与往常一样,没有黄金法则,虽然我会说你通常应该避免单元测试强制设计变更的情况(因为这些变化很可能会给代码消费者带来歧义/不必要的问题)。

尽管如此,最终你需要权衡单元测试代码对设计的干扰程度(第一种情况)或弯曲完美的单元测试定义(第二种情况)。

通常情况下,我发现第二种情况更具吸引力 - 它不会使原始类代码混乱,并且您很可能已经测试了Add - 依赖它是安全的

假设MyObject是一个简单的对象,我认为它仍然是一个单元测试。 我经常为单元测试方法构造输入参数。

我使用Michael Feather的单元测试标准

在以下情况下,测试不是单元测试:

  • 它与数据库进行对话
  • 它通过网络进行通信
  • 它触及文件系统
  • 它不能与任何其他单元测试同时运行
  • 您必须对您的环境执行特殊操作(例如编辑配置文件)才能运行它。

做这些事情的测试也不错。 通常它们值得写作,它们可以用单元测试工具编写。 但是,能够将它们与真正的单元测试分开是很重要的,这样我们就可以保留一组测试,每当我们进行更改时,我们都可以快速运行这些测试。

我的2美分......客户如何知道删除是否有效? “客户”应该如何与此对象进行交互? 客户是否会将堆栈推入历史跟踪器? 将测试视为测试对象的另一个用户/消费者/客户端...使用与实际生产中完全相同的交互。 我没有听说过任何规则,说明你不允许在被测对象上调用多个方法。

要模拟,堆栈不是空的。 我只是打电话给Add - 99%案例。 我不会破坏那个对象的封装。像人一样对待对象(我想我在对象思维中读过这个)。 告诉他们要做的事情......不要闯入并进入。

例如,如果你想让别人的钱包里有钱,

  • 简单的方法是给他们钱,让他们内部把钱放进他们的钱包里。
  • 扔掉他们的钱包,塞进口袋里的钱包里。

我喜欢Option1。 另请参阅它如何将您从实现细节中解放出来(这会导致测试中的脆弱性)。 让我们说明天该人决定使用在线钱包。 后一种方法将破坏您的测试 - 他们将需要更新以便现在推送在线钱包 - 即使对象行为没有被破坏。

我看到的另一个例子是测试Repository.GetX(),其中人们闯入数据库以在单元测试中注入带有SQL的记录..在那里它将更清晰,更容易调用Repository.AddX(x )首先。 需要隔离,但不能超越实用主义。

我希望我在这里不会变得过于强大......我只是觉得对象API被“扭曲为可测试性”,以至于它不再像“可以工作的最简单的东西”。

我认为你试图对单元测试的定义过于具体。 您应该测试类的公共行为,而不是分钟实现细节。

从你的代码片段看,你真正需要关心的是a)调用AddObjectToHistory是否导致IsAnyHistory返回true和b)RemoveLastObject最终导致IsAnyHistory返回false。

正如其他答案所述,我认为你的选择可以这样分解。

  1. 您对测试方法采用教条方法并为堆栈对象添加构造函数注入,以便您可以注入自己的假堆栈对象并测试方法。

  2. 您为add和remove编写了单独的测试,remove测试将使用add方法,但将其视为测试设置的一部分。 只要您的添加测试通过,您的删除也应该是。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM