C#中单元测试的示例?

10
什么是单元测试,如何编写?我听说很多人在应用程序编写之前就开始编写它们了,这怎么可能?我的印象是,单元测试是一些代码,它调用应用程序的方法并带有设置值,并期望返回特定的值。如果没有返回特定的值,则测试失败。我错了吗?我读了很多关于单元测试的内容,但对它在代码中的实际样子知之甚少,所以提供一个示例会很好。

这是一个单元测试吗?

开始伪代码...

CheckForDuplicateSubdomains(){
  get all users in DB with matching subdomains
  if greater than zero, fail test
}

PS:我正在使用C#的ASP.NET MVC


1
我还建议阅读“如何开始单元测试?”这个问题的答案。https://dev59.com/cEjSa4cB1Zd3GeqPJ_aX。 - Jeff Sternal
6个回答

9

您对于单元测试的理解是正确的。单元测试的目的是逐一测试所有函数,使用不同的输入数据来确保它们按照您的预期工作(而不是在将它们插入应用程序后才发现问题,从而导致测试变得更加复杂)。

在编写函数之前编写单元测试是“测试驱动开发”方法论的一部分。您首先只编写函数的框架和所有单元测试。这样,起初所有的测试都会失败(因为函数还未被编程)。然后您编写函数,直到所有测试通过为止。


1
TDD(测试驱动开发)包括编写单个单元测试,然后编写代码使其通过,然后重构该代码以清理它,最后使用新的单元测试重复此过程。您不会像您所暗示的那样编写一堆测试,然后一堆代码。 - ColinD
我认为TDD并不排除一次编写多个测试,尽管在这方面可能我有所无知。例如,如果函数必须执行两个操作--例如将一行插入数据库并返回行号,那么可能需要两个测试,一个测试确保已输入该行,第二个测试显示行号是否正确。当然,您可以在单个测试中执行两个验证,但此时我们正在就单个测试的定义进行细微的区分。 - Bryan Oakley
1
你只需要编写足够的测试以获得失败,然后只需编写足够的代码以使其通过,然后再编写足够的代码以产生另一个失败。可以将其视为编写规范,然后编写足够的代码来满足规范。当您查看代码时,您会意识到需要更好的规范,每个规范都会导致更好的代码。最后,测试显示了您考虑的所有情况,并且对于下一个开发人员来说是良好的阅读材料。 - Tim Ottinger

1

单元测试是在单元级别上进行的自动化测试。 在此级别上,它们指原子代码单元,例如无法进一步分解的函数或对象的特定部分。 一个计算平方的函数的单元测试可能看起来像:

 assertEqual(4, square(2));
 assertEqual(4, square(-2));
 assertEqual(0, square(0));

现在,一旦您已经确定了正方形的接口,就可以立即编写此代码。对于更复杂的函数,您可以通过测试通过的数量来衡量函数完成的程度。


1
我认为单元测试是一些代码,它调用您应用程序的方法并期望返回特定值,如果未返回特定值,则测试失败。我错了还是被误导了吗?
不,你说得很对。
单元测试的重要之处在于尽可能地测试小代码块。
在您的示例中,您从数据库获取某些内容,然后计算项目数... 如果您的方法失败,您将永远不知道出了什么问题,因为有太多可能出错的地方...
您的数据库连接可能会丢失,SQL 无效等等。
如果您正在使用 asp.net MVC,则编写单元测试应该比使用普通 asp.net 更容易。

1

单元测试是指测试您代码的一小部分,通常是一个方法(确切地说是一个单元)。

在TDD中,开发人员可以在编写方法之前编写单元测试,因为他们已经知道代码单元应该执行什么操作。它不关心它如何执行工作......测试只是确保结果是正确的。

那段伪代码也可以用作单元测试(不确定它要测试什么,但我必须假设您正在测试一个不应返回重复子域的方法)。

理论上,单元测试(和测试驱动开发)应该通过确保每个代码单元都按预期执行来减轻以后的头痛。


1

是的,你的例子可以作为一个单元测试。创建测试的原因有几个。首先,它作为项目的实时文档。它建立了行为和预期结果,这应该有助于您实际实现代码(知道它需要做什么以及基本如何启动它,编写代码会更容易)。其次,如果您事后编写测试,您更有可能编写与您已经编写的代码兼容的测试,这对您没有帮助。定义代码单元需要执行的操作,编写测试,然后编写/修复代码,使其反映测试中的行为。这种策略将转化为随着应用程序发展而提高的理解和质量。


0

在 xUnit 框架系列中,有非常成熟的单元测试约定,最初源于 jUnit。在该框架中,断言是进行测试的主要方式。在测试套件中,目标是确保你所有的断言都是正确的,达到100%。

考虑你的例子。

CheckForDuplicateSubdomains(){
  get all users in DB with matching subdomains
  if greater than zero, fail test
}

这个测试通常会以“test”开头命名,这样框架的测试运行器就知道这个函数是一个测试而不是设置方法。重要的是要清楚地表明,我们正在测试代码的行为,而不是数据的状态

testNoDuplicateSubdomains(){
}

在每个测试中,都有四个阶段

  • 夹具设置,
  • 执行SUT,
  • 结果验证和
  • 夹具拆卸。

SUT是“被测试系统”,通常是您生产代码中的一个类的方法。

testNoDuplicateSubdomains(){
  // fixture setup
  prepareDatabaseTestData()
  // exercise SUT, 
  SUT = new ProductionObject()
  result = SUT.getAllUsersWithMatchingSubDomains()
  // result verification and 
  assertEmpty(result) // or whatever makes sense
  // fixture teardown.  
  removeDatabaseTestData()
}

和许多软件开发技术一样,编写单元测试需要一定的经验。学习最佳实践的好资源是Gerard Meszaros的xUnit Test Patterns


在 OP 的 CheckForDuplicateSubdomains() 示例中似乎存在一个误解,即它似乎更倾向于验证某些数据的状态而不是某些代码块的行为。尽管我已经将其带入了我的修改示例中,但我认为这个讨论仍然很有启发性。另外,我建议初学者最好选择在离数据库较远的代码段中获得编写单元测试的经验。代码-数据库接口是一个出了名难以进行单元测试的部分。 - Ewan Todd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接