写点什么

编写综合的单元测试

  • 2012-06-22
  • 本文字数:10668 字

    阅读完需:约 35 分钟

每个用例编写一到二个断言是单元测试最佳实践的常见内容. 那些这么认为的是极少和只展示一个单元测试的人。因此如果你采纳他们的建议,为一个很小的运算你都需要大量的单元测试去保证质量。这篇文章意图通过例子展示,一个测试用例多个断言是有必要和有价值的。

Person 这个对象在数据绑定场景中经常出现,我们来看下。

测试 FirstName

第一个来测试 FirstName 这个属性的设置,开始如下:

复制代码
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set()
{
      <span color="#0000ff">var</span>person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Bob"</span>, person.FirstName);
}

接下来我们来测试 FirstName 的改变通知。

复制代码
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_PropertyChanged()
{
      <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      <span color="#0000ff">var</span> eventAssert = <span color="#0000ff">new</span> Granite.Testing.<span color="#4bacc6">PropertyChangedEventAssert</span>(person);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      eventAssert.Expect(<span color="#c0504d">"FirstName"</span>);
}

当我们执行这个测试时,会得到一个失败提示信息“期望的属性名‘FirstName’,但接收到的是’IsChanged’”。显然,设置 FirstName 的属性触发了“IsChanged”标记,我们需要把它考虑在内。因此我们把它加入:

复制代码
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_PropertyChanged()
{
      <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      <span color="#0000ff">var</span> eventAssert = <span color="#0000ff">new</span> Granite.Testing.<span color="#4bacc6">PropertyChangedEventAssert</span>(person);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      eventAssert.SkipEvent(); <span color="#008000">//this was IsChanged</span>
      eventAssert.Expect(<span color="#c0504d">"FirstName"</span>);
}

鉴于以上两个测试,我们考虑当 FirstName 被修改时还有其他什么属性会改变。查看 API,IsChanged 和 FullName 属性会变化。

[TestMethod]

复制代码
<span color="#0000ff">public void</span> Person_FullName_Changed_By_Setting_FirstName()
{
      <span color="#0000ff">var</span> person =<span color="#0000ff"> new</span><span color="#4bacc6"> Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Bob Smith"</span>, person.FullName);
}
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_IsChanged_Changed_By_Setting_FirstName()
{
      <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      <span color="#4bacc6">Assert</span>.IsTrue(person.IsChanged);
}

当然,如果这些属性改变了,我们需要获取到属性改变通知:

[TestMethod]

复制代码
<span color="#0000ff">public void</span> Person_IsChanged_Property_Change_Notification_By_Setting_FirstName()
{
      <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      <span color="#0000ff">var</span> eventAssert = <span color="#0000ff">new </span><span color="#4bacc6">PropertyChangedEventAssert</span>(person);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      eventAssert.Expect(<span color="#c0504d">"IsChanged"</span>);
}
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FullName_Property_Change_Notification_By_Setting_FirstName()
{
      <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
      <span color="#0000ff">var</span> eventAssert =<span color="#0000ff"> new</span> <span color="#4bacc6">PropertyChangedEventAssert</span>(person);
      person.FirstName = <span color="#c0504d">"Bob"</span>;
      eventAssert.SkipEvent(); <span color="#008000">//this was IsChanged</span>
      eventAssert.SkipEvent(); <span color="#008000">//this was FirstName</span>
      eventAssert.Expect(<span color="#c0504d">"FullName"</span>);
}<br></br>

接下来两个测试针对 HasErrors 这个属性和 ErrorsChanged 事件。

[TestMethod]

复制代码
<span color="#0000ff">public void</span> Person_FirstName_Set_HasErrorsIsFalse()
{
          <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
          person.FirstName = <span color="#c0504d">"Bob"</span>;
          <span color="#4bacc6">Assert</span>.IsFalse(person.HasErrors);
}
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_ErrorsChanged_Did_Not_Fire()
{
          <span color="#0000ff">var </span>person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
          <span color="#0000ff">var</span> errorsChangedAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ErrorsChangedEventAssert</span>(person);
          person.FirstName = <span color="#c0504d">"Bob"</span>;
          errorsChangedAssert.ExpectNothing();
}

目前我们有 8 个测试了,这意味着当我们修改 FirstName 的属性值,我们要考虑会发生改变的每件事。但是这不算完。我们还需要确保没有别的会被意外改变。理论上说,这意味着更多的断言和相当数量的测试,但是,接下来我们采用取巧的方法,用 ChangeAssert 方法来替代 HasErrors 测试。

[TestMethod]

复制代码
<span color="#0000ff">public void</span> Person_FirstName_Set_Nothing_Unexpected_Changed()
{
     <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
     <span color="#0000ff">var</span> changeAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ChangeAssert</span>(person);
     person.FirstName = <span color="#c0504d">"Bob"</span>;
     changeAssert.AssertOnlyChangesAre(<span color="#c0504d">"FirstName"</span>, <span color="#c0504d">"FullName"</span>, <span color="#c0504d">"IsChanged"</span>);
}<br></br>

ChangeAssert 简单地通过映射获取对象的状态,因此,稍后你可以断言到除了你指出的几个具体属性其他的没变。

恭喜,你完成了你的第一个测试用例。完成一个,还有很多很多等着。

为什么说是“一个”测试用例?

那 8 个测试只是完成了覆盖 FirstName 属性从“Adam”修改成“Bob”这一个场景,在其他的值没有在错误状态、LastName 不为 null 或空的情况下。让我们看看测试用例的完整清单:

  1. 将 FirstName 值设置为“Adam”
  2. 将 FirstName 值设置为 null
  3. 将 FirstName 设为空串
  4. 在 LastName 值为 null 的情况下,执行 case1-3
  5. 在 LastName 为空串的情况下,执行 case1-3
  6. 在 FirstName 值以 null 开头的情况下,执行 case1-5
  7. 在 FirstName 值以空串开头的情况下,执行 case1-5

目前我们看到了 27 个不同的场景。如果每个场景需要 8 个不同测试,仅仅为这一个属性,我们需要执行至多 216 个测试。根据这种思路,这是相当琐碎的一段代码。因此我们该怎么做呢?

测试也有代码味道

回看第一个测试用例的 8 个测试,它们都有同样的设置和运算。唯一的不同是我们写的断言。在业界这个被称为一个代码味道。事实上,根据维基百科所列的这里应该有两个代码味道:

  • Duplicated code
  • 重复的代码
  • Excessively long identifiers
  • 过长的标识符

我们可以通过将断言合并到一个测试来轻松地消除这两个代码味道:

复制代码
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set()
{
     <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
     <span color="#0000ff">var </span>eventAssert = <span color="#0000ff">new</span> <span color="#4bacc6">PropertyChangedEventAssert</span>(person);
     <span color="#0000ff">var</span> errorsChangedAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ErrorsChangedEventAssert</span>(person);
     <span color="#0000ff">var</span> changeAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ChangeAssert</span>(person);
     person.FirstName = <span color="#c0504d">"Bob"</span>;
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Bob"</span>, person.FirstName, <span color="#c0504d">"FirstName setter failed"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Bob Smith"</span>, person.FullName, <span color="#c0504d">"FullName not updated with FirstName changed"</span>);
     <span color="#4bacc6">Assert</span>.IsTrue(person.IsChanged, <span color="#c0504d">"IsChanged flag was not set when FirstName changed"</span>);
     eventAssert.Expect(<span color="#c0504d">"IsChanged"</span>);
     eventAssert.Expect(<span color="#c0504d">"FirstName"</span>);
     eventAssert.Expect(<span color="#c0504d">"FullName"</span>);
     errorsChangedAssert.ExpectNothing(<span color="#c0504d">"Expected no ErrorsChanged events"</span>);
     changeAssert.AssertOnlyChangesAre(<span color="#c0504d">"FirstName", "FullName", "IsChanged"</span>);

知道什么导致测试失败很重要,因此我们在断言里添加失败的信息提示。

单元测试和代码重用

回看那 27 个测试用例,我们可以断定设置 FirstName 为 null 或者空串应该也需求同样的测试。因此我们可以扩展成:

复制代码
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_Empty()
{
     Person_FirstName_Set_Invalid(<span color="#4bacc6">String</span>.Empty);
}
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_Null()
{
     Person_FirstName_Set_Invalid(<span color="#0000ff">null</span>);
}
<span color="#0000ff">public void</span> Person_FirstName_Set_Invalid(<span color="#0000ff">string</span> firstName)
{
     <span color="#0000ff">var </span>person =<span color="#0000ff"> new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
     <span color="#0000ff">var</span> eventAssert = <span color="#0000ff">new </span><span color="#4bacc6">PropertyChangedEventAssert</span>(person);
     <span color="#0000ff">var</span> errorsChangedAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ErrorsChangedEventAssert</span>(person);
     <span color="#0000ff">var</span> changeAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ChangeAssert</span>(person);
     <span color="#4bacc6">Assert</span>.IsFalse(person.IsChanged, <span color="#c0504d">"Test setup failed, IsChanged is not false"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Adam"</span>, person.FirstName, <span color="#c0504d">"Test setup failed, FirstName is not Adam"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Smith"</span>, person.LastName, <span color="#c0504d">"Test setup failed, LastName is not Smith"</span>);
     person.FirstName = firstName;
     <span color="#4bacc6">Assert</span>.AreEqual(firstName , person.FirstName, <span color="#c0504d">"FirstName setter failed"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Smith"</span>, person.FullName, <span color="#c0504d">"FullName not updated with FirstName changed"</span>);
     <span color="#4bacc6">Assert</span>.IsTrue(person.IsChanged, <span color="#c0504d">"IsChanged flag was not set when FirstName changed"</span>);
     eventAssert.Expect(<span color="#c0504d">"IsChanged"</span>);
     eventAssert.Expect(<span color="#c0504d">"FirstName"</span>);
     eventAssert.Expect(<span color="#c0504d">"FullName"</span>);
     <span color="#4bacc6">Assert</span>.IsTrue(person.HasErrors, <span color="#c0504d">"HasErrors should have remained false"</span>);
     errorsChangedAssert.ExpectCountEquals(1, <span color="#c0504d">"Expected an ErrorsChanged event"</span>);
     changeAssert.AssertOnlyChangesAre(<span color="#c0504d">"FirstName"</span>,<span color="#c0504d"> "FullName"</span>, <span color="#c0504d">"IsChanged"</span>, <span color="#c0504d">"HasErrors"</span>);

可以发现 Person_FirstName_Set 和 Person_FirstName_Set_Invalid 的差异很小,我们可以进一步试着通用化:

[TestMethod]

复制代码
<span color="#0000ff">public void</span> Person_FirstName_Set_Valid()
{
     Person_FirstName_Set(<span color="#c0504d">"Bob"</span>, <span color="#0000ff">false</span>);
}
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_Empty()
{
     Person_FirstName_Set(<span color="#4bacc6">String</span>.Empty, <span color="#0000ff">true</span>);
}
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FirstName_Set_Null()
{
     Person_FirstName_Set(<span color="#0000ff">null</span>, <span color="#0000ff">true</span>);
}
<span color="#0000ff">public void</span> Person_FirstName_Set(<span color="#0000ff">string</span> firstName, <span color="#0000ff">bool</span> shouldHaveErrors)
{
     <span color="#0000ff">var</span> person = <span color="#0000ff">new</span> <span color="#4bacc6">Person</span>(<span color="#c0504d">"Adam"</span>, <span color="#c0504d">"Smith"</span>);
     <span color="#0000ff">var</span> eventAssert = <span color="#0000ff">new</span> <span color="#4bacc6">PropertyChangedEventAssert</span>(person);
     <span color="#0000ff">var</span> errorsChangedAssert = <span color="#0000ff">new</span> <span color="#4bacc6">ErrorsChangedEventAssert</span>(person);
     <span color="#0000ff">var</span> changeAssert =<span color="#0000ff"> new</span> <span color="#4bacc6">ChangeAssert</span>(person);
     <span color="#4bacc6">Assert</span>.IsFalse(person.IsChanged, <span color="#c0504d">"Test setup failed, IsChanged is not false"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Adam"</span>, person.FirstName, <span color="#c0504d">"Test setup failed, FirstName is not Adam"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Smith"</span>, person.LastName, <span color="#c0504d">"Test setup failed, LastName is not Smith"</span>);
     person.FirstName = firstName;
     <span color="#4bacc6">Assert</span>.AreEqual(firstName, person.FirstName, <span color="#c0504d">"FirstName setter failed"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual((firstName + <span color="#c0504d">" Smith"</span>).Trim(), person.FullName, <span color="#c0504d">"FullName not updated with FirstName changed"</span>);
     <span color="#4bacc6">Assert</span>.AreEqual(<span color="#0000ff">true</span>, person.IsChanged, <span color="#c0504d">"IsChanged flag was not set when FirstName changed"</span>);
     eventAssert.Expect(<span color="#c0504d">"IsChanged"</span>);
     eventAssert.Expect(<span color="#c0504d">"FirstName"</span>);
     eventAssert.Expect(<span color="#c0504d">"FullName"</span>);
     <span color="#0000ff">if</span> (shouldHaveErrors)
     {
          <span color="#4bacc6">Assert</span>.IsTrue(person.HasErrors, <span color="#c0504d">"HasErrors should have remained false"</span>);
          errorsChangedAssert.ExpectCountEquals(1, <span color="#c0504d">"Expected an ErrorsChanged event"</span>);
          changeAssert.AssertOnlyChangesAre(<span color="#c0504d">"FirstName"</span>, <span color="#c0504d">"FullName"</span>, <span color="#c0504d">"IsChanged"</span>, <span color="#c0504d">"HasErrors"</span>);
     }
     <span color="#0000ff">else </span>
     {
          errorsChangedAssert.ExpectNothing(<span color="#c0504d">"Expected no ErrorsChanged events"</span>);
          changeAssert.AssertOnlyChangesAre(<span color="#c0504d">"FirstName"</span>, <span color="#c0504d">"FullName"</span>, <span color="#c0504d">"IsChanged"</span>);
     }
}

在测试代码变得令人迷惑之前,我们可以把它通用化什么程度,这里绝对有个限制。但是一个有意义的测试名称,并给每个断言配一个好的描述可以让你的测试更加容易让人理解。

控制变量

目前所有的断言都只考虑到了测试用例的输出。他们假设每个 Person 对象初始状态已知,然后从此出发进行其他操作。但是如果我们想让测试更具科学性,必须确保我们能控制变量。或者换句话说,我们需要保证,一切在掌握之中。

请看下面一组断言:

复制代码
<span color="#4bacc6">Assert</span>.IsFalse(person.HasErrors, <span color="#c0504d">"Test setup failed, HasErrors is not false"</span>);
<span color="#4bacc6">Assert</span>.IsFalse(person.IsChanged, <span color="#c0504d">"Test setup failed, IsChanged is not false"</span>);
<span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Adam"</span>, person.FirstName, <span color="#c0504d">"Test setup failed, FirstName is not Adam"</span>);
<span color="#4bacc6">Assert</span>.AreEqual(<span color="#c0504d">"Smith"</span>, person.LastName, <span color="#c0504d">"Test setup failed, LastName is not Smith"</span>);

由于我们不想在每个测试的开始重复这些断言,我们可以选择把他们移到一个工厂方法中,这样我们可以保证总是拿到一个干净的对象。这个同样适用于重用这些设置去测试其他属性的测试用例。

[TestMethod]

复制代码
<span color="#0000ff">public void</span> Person_FirstName_Set()
{
     <span color="#0000ff">var</span> person = GetAdamSmith();
     ... 

表格式的测试

之所以走到这一步,是因为“测试方法”的数量跟测试的完善程度没有关系。它们只是组织和执行测试用例一种比较方便的方式。

另一个组织大量测试用例的方法是表格驱动测试法。不能执行单个测试,但是仅用一行代码就可以增加新的测试用例。表格式测试里的表格可以来源于 XML 的文件,数据库表,写死在数组里或者只是使用同一个函数用不同的值反复调用。一些框架如 MBTest 甚至可以让你用属性给出测试用例,但是为了让例子轻便,我们还是坚持保持最低的共同部分。

复制代码
[<span color="#4bacc6">TestMethod</span>]
<span color="#0000ff">public void</span> Person_FullName_Tests()
{     
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#c0504d">"Jones"</span>, <span color="#c0504d">"Bob Jones"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob "</span>, <span color="#c0504d">"Jones"</span>, <span color="#c0504d">"Bob Jones"</span>);
     Person_FullName_Test(<span color="#c0504d">" Bob"</span>, <span color="#c0504d">"Jones"</span>, <span color="#c0504d">"Bob Jones"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#c0504d">" Jones"</span>, <span color="#c0504d">"Bob Jones"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#c0504d">"Jones "</span>, <span color="#c0504d">"Bob Jones"</span>);
     Person_FullName_Test(<span color="#0000ff">null</span>, <span color="#c0504d">"Jones"</span>, <span color="#c0504d">"Jones"</span>);
     Person_FullName_Test(<span color="#0000ff">string</span>.Empty, <span color="#c0504d">"Jones"</span>, <span color="#c0504d">"Jones"</span>);
     Person_FullName_Test(<span color="#c0504d">"      "</span>, <span color="#c0504d">"Jones"</span>, <span color="#c0504d">"Jones"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#c0504d">""</span>, <span color="#c0504d">"Bob"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#0000ff">null</span>, <span color="#c0504d">"Bob"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#0000ff">string</span>.Empty, <span color="#c0504d">"Bob"</span>);
     Person_FullName_Test(<span color="#c0504d">"Bob"</span>, <span color="#c0504d">"      "</span>, <span color="#c0504d">"Bob"</span>);
}
<span color="#0000ff">private void</span> Person_FullName_Test(<span color="#0000ff">string</span> firstName, <span color="#0000ff">string</span> lastName, <span color="#0000ff">string</span> expectedFullName)
{
     <span color="#0000ff">var</span> person = GetAdamSmith();
     person.FirstName = firstName;
     person.LastName = lastName;
     <span color="#4bacc6">Assert</span>.AreEqual(expectedFullName, person.FullName,
          <span color="#0000ff">string</span>.Format(<span color="#c0504d">"Incorrect full name when first name is '{0}' and last name is '{1}'"</span>
          firstName ?? <span color="#c0504d">"<null>"</null></span>, lastName ?? <span color="#c0504d">"<null>"</null></span>));
}<br></br>

在运用这个技巧时,要使用带参数的错误信息,这很重要。如果不加,你会发现在定位哪些参数组合不对时,还得一步一步调试代码。

结论

在为任何变量编写单元测试时,最好尝试最大化以下几个因素:

  • 有意义的单位工作量测试覆盖率
  • 面对变动的代码基线时,保证可维护性
  • 测试套件的性能
  • 明确说明测试什么以及为什么

鉴于这些因素往往会冲突,谨慎地运用单个用例多重断言可提升上述四个方面,具体做法是: + 减少需要编写的样板代码量 + 减少因 API 更改而需要更新的样板代码量 + 减少每个断言需要执行的样板代码数量 + 将某一操作的所有断言,用文档记录在同一个地方

关于作者

Jonathan Allen 从 2006 年起一直为 InfoQ 撰写新闻报道,目前为.NET 领域的主任编辑。如果您有兴趣给 InfoQ 撰写新闻或者教育类文章,请联系他 jonathan@infoq.com .

原文地址: Writing a Comprehensive Unit Test


感谢郑柯对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012-06-22 00:003550

评论

发布
暂无评论
发现更多内容

排序笔记

烫烫烫个喵啊

算法 排序

爸爸,我想握住你的手

一直AC一直爽

随笔杂谈 父爱

HashiCorp官宣:禁止国内使用其旗下Consul等开源软件?

xcbeyond

Consul 条款

技术面试官应该怎么问?面试者应该怎么答?

xcbeyond

面试 自我介绍

公开课 | 吉祥人寿从0到1的 Jira 落地实践

Atlassian

敏捷开发 研发管理 Jira

寻找感动的养分

一直AC一直爽

感恩 随笔杂谈 感动

我有一个梦想

一直AC一直爽

随笔杂谈 梦想

总结:PHP值得注意的几个问题

一直AC一直爽

php

百度CTO王海峰对话王辰院士:全球“最强大脑”助力大数据抗疫时代来临

脑极体

Mysql错误:Ignoring query to other database解决方法

一直AC一直爽

MySQL

第七周总结

andy

极客大学

week7 作业

Geek_2e7dd7

week7 学习总结

Geek_2e7dd7

Elasticsearch源码解析:环境搭建

Jackey

elasticsearch

redis系列之——缓存穿透、缓存击穿、缓存雪崩

诸葛小猿

redis 缓存穿透 缓存击穿 缓存雪崩

Redis(二)单机版安装

奈何花开

Java redis

架构师训练营 -- 第七周作业

stardust20

轻松应对并发问题,简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析

newbe36524

容器 微服务 架构设计 .net core ASP.NET Core

最短路径问题(无负边值)——Dijkstra算法

烫烫烫个喵啊

算法 prim 最短路径

读《我的大学,我的苦难》有感

一直AC一直爽

随笔杂谈 读后感

剪刀爱情

一直AC一直爽

电影

ZK 从入门到放弃 入门篇

小隐乐乐

手把手教你写数独计算器(1)

一直AC一直爽

c++ 算法 数独

布隆过滤器是个啥!

诸葛小猿

布隆过滤器 bloomfilter bloom filter

可读代码编写炸鸡九 - 抽取子问题

多选参数

编程 代码 代码优化 代码规范 可读代码

思维模型盲区:所知障和从众效应

石云升

思维模型 倾听 从众效应

性能测试

陈皮

架构师是怎样炼成的 7-1 性能测试与优化

闷骚程序员

广义表的实现!

烫烫烫个喵啊

算法 广义表

Apache下error.log文件太大的处理方法

一直AC一直爽

我向面试官讲解了单例模式,他对我竖起了大拇指

苹果看辽宁体育

设计模式 单例模式

编写综合的单元测试_.NET_Jonathan Allen_InfoQ精选文章