unit testing和检查私有variables值

我正在用C#,NUnit和Rhino Mocks编写unit testing。 以下是我正在testing的课程的相关部分:

public class ClassToBeTested { private IList<object> insertItems = new List<object>(); public bool OnSave(object entity, object id) { var auditable = entity as IAuditable; if (auditable != null) insertItems.Add(entity); return false; } } 

我想在调用OnSave之后在insertItems中testing值:

 [Test] public void OnSave_Adds_Object_To_InsertItems_Array() { Setup(); myClassToBeTested.OnSave(auditableObject, null); // Check auditableObject has been added to insertItems array } 

这个最好的做法是什么? 我已经考虑添加insertItems作为一个属性与公共得到,或注入到ClassToBeTested列表,但不知道我应该修改testing目的的代码。

我已经阅读了很多关于testing私有方法和重构的文章,但是这是一个很简单的类,我想知道什么是最好的select。

简单的答案是,你永远不应该从你的unit testing访问非公开成员。 这完全违背了testing套件的目的,因为它将您locking到内部实现细节中,您可能不想保持这种方式。

较长的答案涉及到什么呢? 在这种情况下,重要的是理解为什么实现是这样的(这就是TDD如此强大的原因,因为我们使用testing来指定预期的行为,但是我感觉你没有使用TDD)。

在你的情况下,想到的第一个问题是:“为什么IAuditable对象添加到内部列表? 或者换句话说,“这个实施的预期外部可见结果是什么?” 根据这些问题的答案, 就是你需要testing的。

如果将IAuditable对象添加到内部列表中,因为之后您想将它们写入审计日志(只是疯狂的猜测),则调用写入日志的方法并validation是否写入了期望的数据。

如果您将IAuditable对象添加到您的内部列表中,因为您希望收集某种以后的约束的证据,请尝试对其进行testing。

如果您添加的代码没有可衡量的原因,那么再次删除它:)

重要的是testing行为而不是实现是非常有益的。 这也是一个更强大和可维护的testingforms。

不要害怕修改您的被测系统(SUT),使其更具可测性。 只要您的添加在您的域中有意义并遵循面向对象的最佳实践,就没有任何问题 – 您只需遵循“打开/closures”原则 。

您不应该检查项目添加的列表。 如果你这样做,你将在列表中为Add方法编写一个unit testing,而不是为你的代码testing。 只需检查OnSave的返回值; 这真的是你想要testing的。

如果你真的关心这个Add,可以把它嘲笑一下。

编辑:

@TonE:在阅读你的评论之后,我想说你可能想要改变你当前的OnSave方法,让你知道失败。 如果转换失败,你可以select抛出一个exception,然后你可以编写一个期望和exception的unit testing,而另一个则不会。

我想说的“最佳实践”就是testing一些有意义的东西,即将它存储在列表中的实体。

换句话说,现在它存储了什么样的行为是不同的,并且testing了这个行为。 存储是一个实现细节。

这就是说,这并不总是可能的。

如果必须的话,你可以使用reflection 。

如果我没有弄错,你真正想testing的是它只能在列表中添加物品时才会被IAuditable 。 所以,你可以用方法名写几个testing:

  • NotIAuditableIsNotSaved
  • IAuditableInstanceIsSaved
  • IAuditableSubclassInstanceIsSaved

…等等。

问题是,正如你注意到的那样,给定你的问题中的代码,你只能通过间接方式来做到这一点 – 只有通过检查私有insertItems IList<object>成员(通过reflection或通过添加一个属性为testing的唯一目的)或者将该列表注入该类:

 public class ClassToBeTested { private IList _InsertItems = null; public ClassToBeTested(IList insertItems) { _InsertItems = insertItems; } } 

然后,testing很简单:

 [Test] public void OnSave_Adds_Object_To_InsertItems_Array() { Setup(); List<object> testList = new List<object>(); myClassToBeTested = new MyClassToBeTested(testList); // ... create audiableObject here, etc. myClassToBeTested.OnSave(auditableObject, null); // Check auditableObject has been added to testList } 

注入是最具前瞻性和不显眼的解决scheme,除非你有理由认为这个列表将是你的公共接口的一个有价值的部分(在这种情况下,添加一个属性可能会更好 – 当然,属性注入也是完全合法的)。 你甚至可以保留一个没有参数的构造函数来提供一个默认的实现(新的List())。

这确实是一个很好的做法。 考虑到这是一个简单的课程,可能会让你觉得有点过分,但单靠可testing性是值得的。 最重要的是,如果你find另外一个你想要使用这个课程的地方,这将是锦上添花,因为你不会被限制使用一个IList(不是要花费很多努力来做出改变)。

如果列表是一个内部的实现细节(似乎是),那么你不应该testing它。

一个很好的问题是,如果该项目被添加到列表中,那么预期的行为是什么? 这可能需要另一种方法来触发它。

  public void TestMyClass() { MyClass c = new MyClass(); MyOtherClass other = new MyOtherClass(); c.Save(other); var result = c.Retrieve(); Assert.IsTrue(result.Contains(other)); } 

在这种情况下,我断言正确的,外部可见的行为是保存对象后,它将被包含在检索到的集合。

如果结果是,将来传入的对象在某些情况下应该有一个调用,那么你可能有这样的东西(请原谅伪API):

  public void TestMyClass() { MyClass c = new MyClass(); IThing other = GetMock(); c.Save(other); c.DoSomething(); other.AssertWasCalled(o => o.SomeMethod()); } 

在这两种情况下,您都在testing类的外部可见行为,而不是内部实现。

您需要的testing数量取决于代码的复杂程度 – 大致有多less个决策点。 不同的algorithm在实现过程中可以达到相同的结果,而且复杂度不同 你如何编写一个独立于实现的testing,并确保你的决策点有足够的覆盖率?

现在,如果你正在devise更大的testing,比如说集成级别,那么,不,你不想写实现或者testing私有方法,但是问题是针对小的unit testing范围。