HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

编写综合的单元测试

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

评论

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

Stata 15 for Mac(强大的数据分析计算软件)v15.1永久激活版

Rose

重塑精准营销新纪元:深度解析拍立淘API与用户画像构建的融合之道

代码忍者

API Explorer API 接口 API 测试

Python使用asyncio包实现异步编程方式

我再BUG界嘎嘎乱杀

Python 编程 后端 异步编程 开发语言

mac触摸板增强工具BetterTouchTool下载 附BetterTouchTool注册码

Rose

Go语言中使用 sqlx 来操作 MySQL

左诗右码

Go 语言

【永久破解版】MAMP PRO (本地Web服务器开发环境) mac&win

Rose

Python数据分析:数据可视化(Matplotlib、Seaborn)

我再BUG界嘎嘎乱杀

Python 编程 数据分析 后端 数据可视化

AI 机器人对家长培育孩子好性格有帮助吗?——数业智能心大陆给出答案

心大陆多智能体

育儿 智能体 AI大模型 心理健康 数字心理

传智教育引通义灵码进课堂,为技术人才教育学习提效

阿里云云效

阿里云 云原生 通义灵码

SwitchResX for Mac 轻松调整苹果电脑显示器配置参数

Rose

Charles for Mac注册激活版:轻松地捕获分析修改HTTP、HTTPS和TCP流量

Rose

元宇宙Gamefi链游系统开发实现技术原理介绍

V\TG【ch3nguang】

元宇宙Gamefi链游系统开发

智能搜索,Paste for Mac让你快速找到需要的剪切板内容!

Rose

传智教育引通义灵码进课堂,为技术人才教育学习提效

阿里巴巴云原生

阿里云 云原生 通义灵码

深圳堡垒机生产经营公司哪家靠谱?选哪家好?

行云管家

网络安全 堡垒机 深圳

Wi-Fi 7 Technology IPQ5332 vs. IPQ9574: Comparison of Core Chips for Industrial Routers

wifi6-yiyi

WiFi7 ipq9574

Clicker for YouTube mac版 YouTube视频播放器客户端

Rose

GitHub星标68K!Python数据分析入门手册带你从数据获取到可视化

我再BUG界嘎嘎乱杀

Python 编程 数据分析 后端 开发语言

淘宝商品评论接口API:获取淘宝商品评论数据评论总数(支持排序)

tbapi

淘宝API接口 淘宝商品评论接口 淘宝商品评论数据 淘宝商品评论API

再升级!MoneyPrinterPlus集成GPT_SoVITS

程序那些事

工具 AIGC

语聊APP出海中东,Yalla、WePlay、YoYo、SoulChill、YoHo 中东语聊APP的关键成功因素

山东布谷科技胡月

语音厅平台搭建 语音厅源码 语音直播平台开发 语聊APP源码 语音聊天室APP

淘宝商品描述API:深度解析HTML格式内容的策略与技巧

代码忍者

API 接口 API 测试

中大型集团数字化转型常用软件,电子签章领域契约锁领先行业

Geek_2a38d5

Memecoin的火爆与AMM在Solana上的主导地位

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

代理IP在跨境出海业务中的应用

IPIDEA全球HTTP

代理IP 跨境电商 出海

万界星空科技服装行业MES系统解决方案

万界星空科技

工业互联网 制造业 服装行业 mes 万界星空科技

好用的 NTFS 磁盘管理器NTFS Disk by Omi NTFS for mac

Rose

互联网主机监控与审计系统是堡垒机吗?两者有什么区别吗?

行云管家

网络安全 运维‘

面试官:说说volatile应用和实现原理?

王磊

苹果apple音乐制作软件:Logic Pro X for Mac v10.8.1中文版

Rose

高效的AirPods耳机管理工具 AirBuddy for Mac v2.6.3汉化版

Rose

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