单元测试处理降级的网络堆栈,文件损坏和其他缺陷

Kim*_*-wu 45 c++ testing unit-testing mocking

我主要是一个C++编码器,到目前为止,我没有真正为我的所有代码编写测试.我已经确定这是一个坏主意(tm),在添加了巧妙地破坏旧功能的新功能之后,或者,根据您希望如何看待它,引入了他们自己的一些新"功能".

但是,单元测试似乎是一种非常脆弱的机制.您可以在"完美"条件下测试某些内容,但是在内容中断时您无法查看代码的执行情况.例如,对于数据X,例如它是一个爬虫,假设它抓取了一些特定的站点.你只是保存样本页面,对它们进行测试,并希望这些站点永远不会改变吗?这将做工精细的回归测试,但是,你会写什么样的测试,以不断地检查这些网站生活,让你知道当应用程序不这样做的工作,因为该网站改变了一些东西,现在会导致应用程序崩溃?您不希望您的测试套件监控代码的意图吗?

上面的例子有点做作,我还没有遇到过(如果你还没有猜到).但是,让我挑选一些东西.如何测试应用程序将在降级的网络堆栈面前完成其工作?也就是说,说你有丢包适量,对于一个原因或其他,和你有一个功能,DoSomethingOverTheNetwork()应该以优雅降级的时候,它应该堆栈不执行; 但是呢?开发人员通过故意设置一个网关来亲自测试它,该网关在第一次写入时丢弃数据包以模拟坏网络.几个月后,有人检查了一些巧妙地修改某些内容的代码,因此没有及时检测到降级,或者,应用程序甚至没有识别出降级,这种情况从未被发现,因为你无法运行现实世界使用单元测试这样的测试,你呢?

那么,文件损坏怎么样?假设您将一个服务器列表存储在一个文件中,并且校验和看起来没问题,但数据并不是真的.您希望代码处理它,您编写一些您认为可以执行此操作的代码.您如何测试它是否与应用程序的生命周期完全相同?你能?

因此,脆弱.单元测试似乎只在完美的条件下测试代码(这是通过模拟对象等来提升的),而不是它们在野外会面对的.不要误解我的意思,我认为单元测试很棒,但是只有它们组成的测试套件似乎是一种聪明的方法,可以在代码中引入细微的错误,同时对它的可靠性过于自信.

我如何解决上述情况?如果单元测试不是答案,那是什么?

编辑:我看到很多答案都说"只是嘲笑它".好吧,你不能"只是模仿它",这就是原因:以我的降级网络堆栈为例,让我们假设你的函数有一个定义良好的NetworkInterface,我们将嘲笑它.应用程序通过TCP和UDP发送数据包.现在,让我们说,嘿,让我们使用模拟对象模拟界面上10%的损失,看看会发生什么.你的TCP连接增加了他们的重试次数,并增加了他们的后退,所有这些都是很好的做法.你决定改变你的UDP数据包的X%来实际建立TCP连接,有损接口,我们希望能够保证一些数据包的传输,而其他数据包不应该丢失太多.效果很好.同时,在现实世界中..当你增加TCP连接数(或TCP上的数据)时,在一个有足够损耗的连接上,你最终会增加你的UDP数据包丢失,因为你的TCP连接将最终重新开始 - 越来越多地发送他们的数据和/或减少他们的窗口,导致10%的数据包丢失现在实际上更像是90%的UDP数据包丢失.Whoopsie.

没什么大不了的,让我们把它分解为UDPInterface和TCPInterface.等一下......那些是相互依赖的,测试10%的UDP丢失和10%的TCP丢失与上面没有什么不同.

所以,问题是现在你不仅仅是对代码进行单元测试,而是将你的假设引入操作系统的TCP堆栈的工作方式.而且,这是一个坏主意(tm).比避免整个惨败更糟糕的想法.

在某些时候,您将不得不创建一个模拟操作系统,其行为与您的真实操作系统完全相同,除非是可测试的.这似乎不是一个很好的前进方式.

这是我们经历过的事情,我相信其他人也可以添加他们的经验.

我希望有人会告诉我,我错了,并指出原因!

谢谢!

Rae*_*ald 14

首先谈谈单元测试,然后讨论整个应用程序; 看起来你对单元测试有点困惑.根据定义,单元测试是关于在测试软件的每个"单元"时在最细粒度级别上进行测试.在通常使用中,"单元"是单独的功能,而不是整个应用.当代编程风格具有简短的功能,每个功能都有一个定义明确的东西,因此易于单元测试.

  • @Kim Sun-wu - 那你只是用错了字.这更像是*integration*testing. (9认同)

Amy*_*y B 12

你会写什么样的测试来不断检查那些网站?

UnitTests定位您编写的小部分代码.UnitTests不确认世界上的事情是否正常.您应该为那些不完美的场景定义应用程序行为.然后,您可以在那些不完美的场景中单元测试您的应用程序.

例如一个爬虫

爬虫是您可能编写的大量代码.它有一些不同的部分,一部分可能会获取一个网页.另一部分可能会分析html.即使这些部件可能太大而无法编写单元测试.

如何测试应用程序将在降级的网络堆栈面前完成其工作?开发人员通过故意设置一个网关来亲自测试它,该网关在第一次写入时丢弃数据包以模拟坏网络.

如果测试使用网络,则不是UnitTest.

UnitTest(必须以您的代码为目标)无法调用网络.你没有写网络.UnitTest应该包含一个模拟网络,模拟(但每次都是一致的)数据包丢失.

单元测试似乎只在完美的条件下测试代码

UnitTests在定义的条件下测试您的代码.如果您只能定义完美的条件,那么您的陈述是正确的.如果您能够定义不完美的条件,那么您的陈述是错误的.


Dan*_*ker 10

通过任何体面的单元测试书 - 你会发现通常的做法是编写确实涵盖输入不理想或完全错误的边缘情况的测试.

具有异常处理的语言中最常见的方法是"应该抛出"规范,其中某个测试预期会导致抛出特定的异常类型.如果它没有抛出异常,则测试失败.

更新

在您的更新中,您描述了复杂的时序敏感交互.单元测试根本没有帮助.无需引入网络:只需考虑尝试编写一个简单的线程安全队列类,也许在具有一些新的并发原语的平台上.在8核系统上进行测试......它有效吗?你无法通过测试来确定这一点.时序可能导致操作在核心之间重叠的方式太多.根据运气,可能需要数周的连续执行才能发生一些非常不可能的重合.实现这些目标的唯一方法是通过仔细分析(静态检查工具可以提供帮助).大多数并发软件可能都有一些很少出现的错误,包括所有操作系统.

回到可以实际测试的案例,我发现集成测试通常和单元测试一样有用.这可以像自动化产品的安装,添加配置(例如用户可能创建),然后从外部"戳"它,例如自动化UI.这发现了与单元测试分开的另一类问题.


Fin*_*las 5

听起来好像你回答了自己的问题.

模拟/存根是测试难以测试区域的关键.对于您的所有示例,可以手动完成创建具有狡猾数据的网站或导致网络故障的手动方法.然而,这样做是非常困难和乏味的,不是任何人都会推荐的.事实上,做一些意味着你实际上并不是单元测试.

相反,你会使用模拟/存根假装发生这样的情况,允许你测试它们.使用模拟的好处是,与手动方法不同,您可以保证每次运行测试时都会执行相同的过程.因此,测试反过来会更加快速和稳定.

编辑 - 关于更新的问题.

就像免责声明一样,我的网络体验非常有限,因此我不能对您的问题的技术方面发表评论.但是,我可以评论你听起来好像你测试的太多了.换句话说,您的测试覆盖范围太广.我不知道你的代码库是什么样的,但是给定其中的函数/对象,你仍然应该能够提供假的输入,这将允许你测试你的对象/函数是否孤立地做正确的事情.

因此,假设您的孤立区域符合要求,则可以正常工作.仅仅因为您的单元测试通过并不意味着您已经测试了您的应用程序.您仍需要手动测试您描述的此类方案.在这种情况下,听起来好像压力测试 - 限制网络资源等是必需的.如果您的应用程序按预期工作 - 很棒.如果没有,你就错过了测试.单元测试(更多与TDD/BDD相关)是关于确保应用程序工作的小而孤立的区域.您仍然需要集成/手动/回归等.之后进行测试.因此,您应该使用模拟/存根来测试小的,隔离区域的功能.在我看来,单元测试更类似于设计过程.

  • 存根不会让你走得太远.非平凡的类只能在适当的上下文中进行合理的测试. (2认同)

die*_*dha 5

集成测试与单元测试

我应该在这个答案前面说我偏向于集成测试与单元测试作为tdd中使用的主要测试类型.在工作中,我们也混合了一些单元测试,但仅在必要时.我们从集成测试开始的主要原因是因为我们更关心应用程序正在做什么而不是特定功能的作用.根据我的经验,我们还获得了集成覆盖,这在自动化测试方面存在巨大差距.

是否嘲笑,为什么不做两件事

我们的集成测试可以完全连线(到非托管资源)或使用模拟运行.我们发现有助于弥补现实世界与模拟之间的差距.这也为我们提供了决定不使用模拟版本的选项,因为实现模拟的ROI不值得.你可能会问为什么要使用模拟器.

  • 测试套件运行得更快
  • 每次保证相同的响应(没有超时,不可预见的降级网络等)
  • 对行为进行细粒度控制

有时你不应该写一个测试

测试,任何类型的测试都有折衷.您可以查看实现测试,模拟,变体测试等的成本,并根据优势进行权衡,有时候编写测试,模拟或变体是没有意义的.这个决定也是在您的建筑软件的背景下做出的,这确实是决定测试套件需要的深度和广度的主要因素之一.换句话说,我会为社交培根聚会功能编写一些测试,但我不打算为培根朋友算法编写形式验证测试.

你只是保存样本页面,测试那些,并希望网站永远不会改变?

测试不是灵丹妙药

是的,您保存样本(作为固定装置).您不希望页面不会更改,但您无法知道它将如何以及何时更改.如果您有关于如何更改的想法或参数,那么您可以创建变体以确保您的代码将处理这些变体.当它确实发生变化,并且它会中断时,您可以添加新样本,解决问题并继续前进.

你会写什么样的测试来不断地检查这些网站并让你知道什么时候应用程序没有做它的工作,因为网站改变了一些东西,现在导致你的应用程序崩溃?

测试!=监控

测试是测试和开发(和QA)的一部分,而不是生产.MONITORING是您在生产中使用的,以确保您的应用程序正常工作.您可以编写监视器,以便在出现故障时提醒您.那是另一个话题.

如何测试应用程序将在降级的网络堆栈面前完成其工作?

培根

如果是我,我将有一个有线和模拟模式进行测试(假设模拟足够好,有用).如果模拟很难做到,或者它不值得,那么我就会进行有线测试.但是,我发现几乎总有一种方法可以将变量分解为不同的测试.然后,每个测试都针对测试变化向量,同时最小化游戏中的所有其他变化.诀窍是写出重要的变体,而不是每个可能的变体.

那么,文件损坏怎么样?

多少测试

你提到校验和是正确的,但文件实际上已损坏.这里的问题是我正在写的软件类是什么.我是否需要对可能出现统计上很小的假阳性而非常偏执.如果我这样做,那么我们会努力找到测试的深度和广度.