写点什么

编写综合的单元测试

  • 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:003519

评论

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

海外主机是什么意思?与国内主机有什么区别?

行云管家

服务器 主机 服务器运维 海外 主机运维

第九周作业

lv

企业IM首选移动数字化平台WorkPlus

WorkPlus

昆仑分布式数据库技术优势

KunlunBase昆仑数据库

分布式数据库 国产数据库

Linux之ss命令

入门小站

Linux

Linux下C++后台服务器开发

Linux服务器开发

C/C++ 后端开发 Linux服务器开发 C++后台开发 Linux后台开发

云图说丨初识数据工坊DWR

华为云开发者联盟

大数据 数据处理 算子 数据工坊 工作流编排

为什么要选择昆仑分布式数据库?

KunlunBase昆仑数据库

国产数据库

应用环境能力 | 阿里巴巴DevOps实践指南

阿里云云效

阿里巴巴 阿里云 研发效能 开发

声网崩溃数据的自动化闭环处理

声网

自动化 测试 Dev for Dev

墨天轮国产数据库沙龙 | 胡津铭:时序数据库DolphinDB,从量化金融到万物互联

墨天轮

数据库 时序数据库 DolphinDB 国产数据库

JavaScript 基础(一):语法和程序结构

devpoint

JavaScript 函数 数据类型 3月月更

昆仑分布式数据库技术特点

KunlunBase昆仑数据库

分布式数据库 国产数据库

恒源云(GpuShare)_加速pytorch训练的方法来喽~

恒源云

深度学习 PyTorch

天翼云TeleDB数据库为实现自主可控全面亮剑

天翼云开发者社区

昆仑分布式数据库架构介绍

KunlunBase昆仑数据库

数据库 分布式数据库

优雅的编码习惯总是让人心情愉悦(Shell篇)

XinXing

Shell Code 优雅 脚本 规范

黄东旭当选 CCF 数据库专业委员会、开源发展委员会、大数据专家委员会执行委员

PingCAP

面试官:对于宏任务和微任务,你知道多少?

是乃德也是Ned

JavaScript 面试 前端 ES6 Promise

如何高效完成ECS多环境部署?

阿里云云效

阿里云 云原生 开发 部署与维护 ECS

【51单片机】独立按键控制LED灯(四种形式)

謓泽

3月月更

主流移动端账号登录方式的原理及设计思路

WorkPlus

天翼云与龙芯完成产品兼容适配加速国产化云平台发展

天翼云开发者社区

31 家企业入选阿里云首期云原生加速器,共建云原生行业新生态

阿里巴巴云原生

阿里云 云原生 云原生加速器 招募 行业生态

“养老”变“享老”,老龄人口高峰与养老产业爆发催生金融需求

易观分析

养老服务 养老金融

CRM系统改善业务的方法

低代码小观

CRM 客户关系管理 企业管理系统 CRM系统 企业管理工具

Promise静态四兄弟,你学会了吗?

战场小包

JavaScript 前端 Promise 3月月更

穿透、击穿、雪崩…Redis这么多问题,如何解决?

华为云开发者联盟

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

云管理平台有哪些?建议选择哪家?

行云管家

云计算 多云 云管理

C++ 内存管理中内存泄漏问题产生原因以及解决方法

Linux服务器开发

C/C++ 内存管理 内存泄漏 Linux服务器开发 Linux后台开发

史上最通俗,彻底搞懂字符乱码问题的本质

WorkPlus

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