草庐IT

什么是测试金字塔?如何使用测试金字塔来构建自动化测试体系?

surfirst 2024-02-02 原文

测试金字塔 (Test Pyramid)是一套使用单元测试,集成测试和端到端测试来构建自动化测试体系的方法。

如下图所示,在金字塔的最下方是单元测试,中段是集成测试,最上方是端到端测试。单元测试实现的成本最低,运行速度最快,是毫秒级的。集成测试的实现成本比单元测试高,因为要跟外部系统连接,所以运行速度比单元测试要慢1-2各个数量级。端到端测试最符合用户的完整体验,从 UI 开始通过 API 直达系统内部代码,因为涉及的环节更多,所以实现成本更高,运行速度比集成测试更慢。

测试金字塔理论推荐单元测试应该是数量最多,覆盖范围最大的测试种类。道理很简单,单元测试成本低,运行速度快,在发现问题的时候解决问题也最快。集成测试数量次之,最后才是昂贵的端到端测试。由于端到端测试经过的环节更多,所以通过端到端测试发现的问题,解决起来用时更多。

单元测试

单元测试是一种由开发者实现的白盒测试。在测试金字塔里,单元测试是自动化测试体系的基础。一般使用代码覆盖率来评价单元测试是否达到了测试的要求,但是在实践中开发团队往往发现很难达到指定的代码覆盖率。开发工程师写的单元测试和测试工程师编写的测试用例也很难匹配,跟产品经理的业务描述差得更远。

这里的一个重要原因是许多开发者把程序简单地理解为增删改查(CRUD),而没有围绕业务逻辑来编写代码。对外部服务没有构建“以我为中心”的模型,编写代码的时候直接使用要依赖服务定义的模型,这样在编写单元测试时就会因为模拟某些服务难度太高不得不放弃,只能使用集成测试来验证代码的正确性。

我们可以借鉴《领域驱动设计》里的分层架构,以及六边形架构来构建抽象的领域模型,并且使用单元测试来验证模型实现了产品经理的业务描述。这样我们就可以使用面向对象知识围绕业务构建领域层,把外界服务看做各种连接到模型的“端口”。在编写单元测试时,就不会遇到无法模拟其他服务的问题。

总结一下,我们的单元测试应该构建在“领域层”之上,以验证领域模型对业务描述的正确性为主要目标。

单元测试是否可以连接数据库?

单元测试是系统内部的测试,单元测试不能跨越系统。数据库是另外一个系统,跨系统的测试属于集成测试的范畴,所以单元测试中是不可以连接数据库的。单元测试要遵循 FIRST 原则,其中的 “F” 就表示要快。单元测试的范围在系统内部不访问外部系统才可能快。访问数据库会把毫秒级的测试时间,提升到几十甚至数百毫秒以上。这显然是集成测试的运行时间水平。

集成测试

集成测试是验证系统和系统之间的相互调用是否符合预期的测试。因为要跨越系统,所以和单元测试相比集成测试运行起来要慢1到2个数量级。

当发现问题时,问题可能会出在集成测试连接的任意一个系统中,开发者需要排查每一个系统直到找到问题为止,而在单元测试中发现问题时只需要排查本系统,所以集成测试相比单元测试排查问题用时也更多。

开发者在设计集成测试时可以限定要集成的系统范围,这样就可以把测试重点放在开发者关心的系统上。集成测试往往以 API 调用的方式进行,开发者编写测试脚本按顺序调用 API,检查结果是否符合预期。


集成测试是系统之间的测试。数据库也是一种系统

端到端测试

端到端测试以 UI 为起点,模拟用户使用系统的场景,从 UI 输入验证系统的输出是否符合预期。因为涉及到 UI,端到端测试必然会跨系统,又因为开发者只能控制 UI 部分的输入顺序和数量,所以开发者不能控制端到端测试中被调用的 API 的顺序和数量,在发现问题时不光要检查各个可能要涉及的系统,还要考虑 API 可能被调用的顺序和数量,排查问题的难度比集成测试更高。

在运行时间上,UI 测试模拟用户的输入而不是像集成测试那样直接调用系统 API,所以运行时间类似于人类反应的时间,与集成测试相比端到端测试整体运行时间又有数量级级别的下降。

端到端测试也包含了部分探索性测试和人工测试。


端到端测试的输入从UI开始

端到端测试在性能测试上更能反映系统在真实世界的运行情况

如果以集成测试的方式来做性能测试,开发者可以按照指定的顺序调用API,模拟在规定时间内调用 API 的数量下系统的响应能力,但是在真实世界,用户是通过 UI 来驱动系统的,UI 页面往往包含各类信息,这类信息会并发地以不同顺序调用系统的 API,这样在用户流量大的情形下,不同 API 被纠缠在一起以不同的顺序和数量传递到不同的系统里。

在上图中,用户打开“新浪财经”页面会调用页面UI,要闻,突发,股市指数,天气预报等10几个系统。这比单纯的集成测试要调用的系统数量上要多,调用顺序由UI页面结构决定,每次页面调整都会导致 API 调用顺序的改变。显然端到端测试要比集成测试复杂。

和集成测试相比,基于端到端测试实现的性能测试,结果可能会和开发者的预期差距更大,但是可能会发现更多问题。

如何使用测试金字塔来构建自动化测试体系

基于对测试金字塔中 3 种不同种类测试的特点的分析,考虑到运行速度和实现成本,我们推荐开发者以单元测试为自动化测试的基础,以尽可能低的成本发现尽可能多的问题,通过集成测试检查系统间的相互调用问题,辅以端到端测试优化用户体验来构建自动化测试体系。

结论

本文通过比较单元测试、集成测试和端到端测试在实现成本,负责范围和纠错时间的区别和联系,介绍了测试金字塔的概念,以及以按照单元测试、集成测试和端到端测试的顺序构建自动化测试体系的必要性。

参考链接

有关什么是测试金字塔?如何使用测试金字塔来构建自动化测试体系?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  10. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

随机推荐