我为什么要进行单元测试和Web测试(而不仅仅是网络测试)?

时间:2008-10-06 21:28:14

标签: c# asp.net visual-studio unit-testing

我目前的立场是:如果我使用网络测试(在我的情况下通过VS.NET'08测试工具和WatiN,可能)使用代码覆盖率和广泛的数据来彻底测试我的ASP.NET应用程序,应该不需要编写单独的单元测试,因为我的代码将通过所有层与UI一起进行测试。代码覆盖将确保我能够访问每个功能代码(或显示未使用的代码),并且我可以提供涵盖所有合理预期条件的数据。

但是,如果您有不同的意见,我想知道:

  1. 其他的好处是单元测试能否证明将其包含在项目中的努力是合理的(请记住,我正在进行网络测试,所以在很多情况下,单元测试将涵盖网络测试已涵盖的代码。

  2. 你能用详细的例子详细解释你的理由吗?我经常看到诸如“这不是它的意义”或“它促进更高质量”的回应 - 这实际上并没有解决我必须面对的实际问题,也就是说,我怎么能证明 - 有实际结果 - 花更多钱时间测试?

12 个答案:

答案 0 :(得分:22)

  

代码覆盖率将确保我点击每个功能代码

“点击”表示“测试”

仅进行网络测试的问题在于,它只能确保您点击代码,并且出现在高级别时才是正确的。< / p>

仅仅因为你加载了页面,并没有崩溃,并不意味着它实际上正常工作。以下是我遇到的一些事情,其中​​'网络测试'涵盖了100%的代码,但完全错过了一些非常严重的错误,单元测试不会有。

  1. 从缓存中正确加载页面,但实际数据库已损坏
  2. 页面加载了数据库中的每一个项目,但只显示了第一个项目 - 即使它因为花费的时间太长而完全失败,它看起来还不错
  3. 页面显示一个看起来有效的号码,实际上是错误的,但由于1000000很容易被误认为是100000
  4. 页面巧合显示有效数字 - 10x50与25x20相同,但其中一个是错误的
  5. 该页面应该向数据库添加一个日志条目,但这对用户不可见,因此没有看到。
  6. 绕过身份验证以使网络测试真正起作用,因此我们错过了身份验证代码中的一个明显错误。
  7. 很容易想出更多这样的例子。

    你需要两个单元测试来确保你的代码实际上做了它应该在低级别做的事情,然后功能/集成(你称之为web)测试在那些之上,以证明它实际上,当所有那些小的经过单元测试的部件被链接在一起时就会起作用。

答案 1 :(得分:9)

单位测试在转向时可能比网络测试快得多。这不仅在开发时间方面是正确的(如果你可以直接测试一个方法,那么你可以直接测试一个方法,而不是你必须构建一个特定的查询,最终会在几个层之后点击它),而且在执行时间(按顺序执行数千个请求将比执行短方法花费数千次)。

单元测试将测试小功能单元,因此当它们失败时,您应该能够很容易地找出问题所在。

此外,使用单元测试模拟依赖关系要比完全前端更容易 - 除非我错过了一些非常酷的东西。 (几年前,我研究了模拟和网络测试如何整合,当时没有什么合适的。)

答案 2 :(得分:5)

单元测试通常不能证明任何给定的功能集合起作用 - 至少它不应该。它证明您的合同合同按预期运作。

验收测试更倾向于客户要求。每个要求都应该有验收测试,但验收测试和单元测试之间确实没有要求 - 它们甚至可能不在同一个框架中。

单元测试可用于推动代码开发,重新测试的速度是一个重要因素。在测试时,您经常会删除被测试的类所依赖的部件进行单独测试。

验收测试系统就像你提供它一样 - 从GUI到数据库。有时他们需要花费数小时或数天(或数周)来运行。

如果你开始认为它是两个完全不同的野兽,那么你将是一个更有效的测试者。

答案 3 :(得分:2)

良好,专注的单元测试可以在发现问题时更快地找到并解决问题。当一个写得很好的单元测试中断时,你几乎知道失败的意义以及导致它的原因。

此外,它们通常运行速度更快,这意味着您在开发期间更有可能在编辑 - 编译 - 测试周期中运行它们(而不是仅在您即将办理登机手续时) )。

答案 4 :(得分:2)

编写单元测试时,您将被迫以更好的方式编写代码。更松散耦合,更面向对象。这将带来更好的架构和更灵活的系统。

如果你用TDD风格编写单元测试,你可能不会做那么多不必要的代码,因为你将专注于微小的步骤而只做必要的。

在进行重构以改进代码以提高可维护性并减少代码味道时,您会更有信心。

单元测试本身将作为系统执行和未执行操作的安静文档。

这些只是我在将TDD应用到我的工作时注意到的一些好处。

答案 5 :(得分:2)

还有一个方面 - 请原谅这个理想的环境:

假设您有3个组件最终必须协同工作。每个都可以通过5个单元测试单独进行完全经过单元测试(无论这意味着什么)。这使得 5 + 5 + 5 = 15 单元测试完全覆盖。

现在,如果你有集成/网络/组合测试来测试所有组件,你需要(记住这个抽象场景的理想世界) 5 * 5 * 5 = 125 测试测试所有排列,以获得与上述15个测试用例相同的结果(假设您甚至可以触发所有排列,否则会出现未经测试/未指定的行为,以后在扩展组件时可能会咬你)。

另外,如果功能发生变化,125个测试用例的设置会更复杂,周转时间更长,可维护性也会大大降低。我宁愿选择15个单元测试加上一些基本的网络测试,以确保组件连接得有点正确。

答案 6 :(得分:1)

单元测试可以提供速度,最重要的是,引入故障或错误的位置精确度。它使您能够测试每个组件,与每个其他组件隔离,并确保它按预期工作。

例如,如果您进行了Web测试,则会将Ajax请求发送回服务器上的服务,该服务随后会访问数据库,然后数据库将失败。是导致问题的Javascript,服务,业务逻辑还是数据库?

如果您单独测试每个服务,存根/模拟数据库或每个业务逻辑单元,那么您更有可能确切地知道错误的位置。单元测试不仅涉及覆盖范围(尽管很重要),更多的是关于隔离。

答案 7 :(得分:1)

几乎就像Dijkstra所说的那样:单​​元测试只能用来表明软件有缺陷,而不是证明它没有缺陷。因此,一般来说,每次访问每个代码路径(并获得100%覆盖率)与测试无关 - 它只是有助于消除bitrot。

如果您正在阅读本书,那么只有在编写了触发该错误的单元测试后,才能消除每个严重错误。巧合的是,修复错误意味着这个特定的单元测试不再失败。将来,此单元测试会检查此特定错误是否仍然存在。

编写一个触发特定错误的单元测试要比写一个端到端(web)测试要容易得多,这个测试只能执行并且不会在整个过程中运行大量完全不相关的代码(也可能失败并陷入根本原因分析。)

答案 8 :(得分:1)

单元测试测试每个组件是否有效。这些非常有助于发现接近创建时间的缺陷,从而大大降低修复缺陷的成本,并显着减少最终发布的缺陷数量。此外,良好的单元测试使重构更容易,更强大。

集成测试(或在这种情况下为“web”测试)也非常重要,但是比单元测试更具防御性。单个集成测试涵盖了如此大量的代码,当一个代码失败时,需要进行大量调查以确定导致失败的缺陷(或可能是缺陷组)。这样做成本很高,尤其是当您尝试测试发布版本以将其发布时。这是更加昂贵的,因为引入修复程序的错误的机会往往相当高,并且失败的可能性阻碍了对发布的进一步测试(这对于开发周期来说非常昂贵)。

相反,当单元测试失败时,您确切知道缺陷代码的确切位置,并且您通常确切地知道代码出错了什么。此外,单元测试失败应该一次只影响一个开发人员,并在签入代码之前修复。

比以前修复错误总是更昂贵。总是

考虑建造一辆汽车。您是否会等到整个车辆从装配线上下来测试每个组件是否正常工作?此时,如果您发现CD播放器或引擎或空调或巡航控制不起作用,您必须让整个车辆离线,解决问题,然后重新测试一切(希望不会揭示任何新的缺陷)。这显然是错误的做法,就像尝试发布软件显然是错误的,而只是测试它是否在流程结束时工作而不是从头开始沿着每一个重要步骤。

答案 9 :(得分:0)

这取决于ASP.NET应用程序架构如何。如果网页只是连接底层的业务逻辑层或数据访问层,那么独立于ASP.NET状态模型工作的单元测试比开发和运行的类似WaitiN测试更快。

我最近在Visual Studio 2003中开发了一个遗留ASP.NET应用程序的区域,其中NUnit作为测试框架。以前的测试涉及通过UI测试来确保功能正确实现,而90%的测试都是在不需要UI交互的情况下进行的。

我唯一的问题是时间估计 - 其中一项任务是在Trac中计划为数据访问/业务逻辑需要1天,UI创建和测试需要2天。随着NUnit在数据访问/业务逻辑上运行,该部分开发的时间从1天增加到2天。用户界面开发时间缩短为1/2天。

这继续将新模块中的其他任务添加到应用程序中。该单元测试发现的错误更快,并且以一种不那么痛苦的方式(对我而言),我对应用程序的运行更有信心。更好的单元测试是非常可重复的,因为它们不依赖于任何UI重新设计,因此设计中的更改在编译时失败,而不是在运行时,往往不那么脆弱。

答案 10 :(得分:0)

单元测试允许更严格的性能测试,并且更容易确定瓶颈发生的位置。对于大型应用程序,当10,000个用户同时执行一个方法时,性能成为一个主要问题,如果该方法需要1/100秒执行,可能是因为编写的数据库查询不好,因为现在一些用户必须等待最多10秒钟才能加载页面。我知道我个人不会等待很长时间才能加载页面,而只会转移到另一个地方。

答案 11 :(得分:0)

单元测试允许您在添加前端的复杂性之前,专注于获取数据,应用规则和验证业务流程的逻辑。在编写和执行单元测试时,我知道应用程序的基础结构将支持我在进程之间来回传递的数据类型,我的事务正常工作等等。

换句话说,当您开始验收测试时,潜在的故障点会更好地隔离,因为您已经使用单元测试解决了代码中的潜在错误。根据单元测试的历史,您将知道只有某些模块会抛出某些错误。这使得追踪罪魁祸首的代码变得更加容易。