写点什么

编写综合的单元测试

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

评论

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

中国平煤神马集团 | 基于融合共享的经营管控多业务(1+2+N)共享中心建设实践

用友BIP

能源 数智化转型 共享中心

华为云API Explorer重磅推出API编排,开发者0代码高效构建工作流

华为云开发者联盟

软件开发 API 华为云 华为云开发者联盟 企业号9月PK榜

世界500强都摒弃使用FTP的真实原因

镭速

ftp传输 FTP替代 FTP替代方案

苹果mac桌面管理软件都有哪些?

南屿

Mac桌面管理工具 苹果电脑必备软件 Mac破解软件

企业如何寻找可替代serv-u的国产文件传输系统?

镭速

在Mac上,按Command-X键无法剪切怎么办?

南屿

Mac右键助手 Command-X键无法剪切 Mac常见问题

Java基础面试题【三】线程(1)

派大星

Java 面试题

苹果电脑串口调试软件:serial 直装激活最新版

mac大玩家j

Mac软件 串口调试工具

软件物料清单管理 | 打开“应用软件盲盒”,预警“开源组件风险”

网安云

开源 网络安全 系统安全 开源软件 软件物料清单

超越内存限制:深入探索内存池的工作原理与实现

华为云开发者联盟

软件开发 存储 华为云 华为云开发者联盟 企业号9月PK榜

Photoshop创成式AI不能用?Alpaca(羊驼)AI智能创成式填充插件

南屿

PS插件下载 Alpaca(羊驼)插件 AI创成式填充插件

如何使用API接口获取商品数据,从申请API接口、使用API接口到实际应用,一一讲解

Noah

API 文档 API 开发

华为云智能化组装式交付方案 ——金融级PaaS业务洞察及Web3实践的卓越贡献

华为云PaaS服务小智

云计算 软件开发 华为云

小程序编译器性能优化之路

百度Geek说

小程序 性能优化 前端 企业号9月PK榜

Codigger的项目代码检测工具的特性和优势

知者如C

IPQ8072/QCN9074/QCN9274/IPQ9574/IPQ6010/IPQ6018 WIFI6E WIFI7 WPA3 Hardware Comparison

wallyslilly

QCN9074 IPQ8072 QCN9024

2023年信创云管平台选哪家?咨询电话多少?

行云管家

云计算 云服务 信创 国产化

cad设计绘图 AutoCAD 2024中文最新「支持m芯片」

胖墩儿不胖y

Mac软件 CAD绘图 cad工具

高效、透明-企事业数字化的采购管理系统(源程序源代码)

金陵老街

用友出席第六届中国企业论坛,分享央国企数智化转型方案

用友BIP

数智化转型

MySQL事务死锁问题排查 | 京东云技术团队

京东科技开发者

MySQL 数据库 事务 死锁 企业号9月PK榜

软件调研、研发、设计、管理、验收文档(全文档整理)

金陵老街

如何通过”系统设置”自定 苹果Mac?

南屿

自定义苹果Mac Mac电脑使用教程 苹果电脑使用

算力百川汇蓝海,商海荡漾绘宏图

鲸品堂

算力 算力网络

Databend 开源周报第112期

Databend

轻量级业务福音!TDengine Cloud 在国轩高科储能项目中的应用

TDengine

tdengine 时序数据库 国产时序数据库

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