HTTP请求测试(开发人员的生存工具)

本文概述

  • 测试套件不可行时该怎么办
  • 自动化测试
  • 在理想世界中进行测试
  • 真实世界中的测试
  • 但是, 你需要某种测试
  • 我用什么代替
测试套件不可行时该怎么办 有时, 我们(程序员和/或客户)拥有的资源有限, 无法用来编写预期的可交付成果和针对该可交付成果的自动化测试。当应用程序足够小时, 你可以偷工减料并跳过测试, 因为(大多数情况下)你会记得添加功能, 修复错误或重构时代码其他地方发生的情况。也就是说, 我们不会一直使用小型应用程序, 而且随着时间的推移, 它们会变得越来越大和越来越复杂。这使得手动测试变得困难而又烦人。
在我的最后几个项目中, 我被迫在没有自动化测试的情况下工作, 老实说, 在代码推送后让客户向我发送电子邮件说该应用程序在我什至没有接触过代码的地方损坏了, 这真令人尴尬。
因此, 在我的客户没有预算或无意添加任何自动化测试框架的情况下, 我开始通过向每个页面发送HTTP请求, 解析响应标头并查找” 200″ 来测试整个网站的基本功能。响应。听起来简单明了, 但是你可以做很多事情来确保保真度, 而无需实际编写任何测试, 单元, 功能或集成。
HTTP请求测试(开发人员的生存工具)

文章图片
由于种种原因, 很多合同项目中都没有编写测试, 所以该怎么办?
鸣叫
自动化测试 在Web开发中, 自动化测试包括三种主要的测试类型:单元测试, 功能测试和集成测试。我们经常将单元测试与功能测试和集成测试结合起来, 以确保所有内容在整个应用程序中都能顺利运行。当这些测试同时运行或依次运行(最好使用单个命令或单击)时, 我们开始将其称为自动测试, 无论是否单位。
这些测试的主要目的(至少是在Web开发中)是为了确保所有应用程序页面的呈现都没有问题, 并且没有致命的(应用程序停止)错误或错误。
单元测试
单元测试是一种软件开发过程, 其中最小的代码部分-单元-被独立测试以确保正确操作。这是Ruby中的示例:
test "should return active users" do active_user =create(:user, active: true) non_active_user = create(:user, active: false) result = User.activeassert_equal result, [active_user] end

功能测试
功能测试是一种用于检查系统或软件的功能的技术, 旨在涵盖所有用户交互方案, 包括故障路径和边界情况。
注意:我们所有的示例都在Ruby中。
test "should get index" do get :index assert_response :success assert_not_nil assigns(:object) end

整合测试
【HTTP请求测试(开发人员的生存工具)】一旦对模块进行了单元测试, 就将它们依次逐一集成, 以检查组合行为并验证要求是否得到正确实施。
test "login and browse site" do # login via https https! get "/login" assert_response :success post_via_redirect "/login", username: users(:david).username, password: users(:david).password assert_equal '/welcome', path assert_equal 'Welcome david!', flash[:notice] https!(false) get "/articles/all" assert_response :success assert assigns(:articles) end

在理想世界中进行测试 测试在业界已被广泛接受, 并且很有意义。好的测试可以使你:
  • 以最少的人力即可确保整个应用程序的质量
  • 因为你确切知道代码从测试失败中中断的位置, 所以更容易识别错误
  • 为你的代码创建自动文档
  • 避免” 编码便秘” , 根据Stack Overflow上的一些观点, 这是一种幽默的说法, “ 当你不知道接下来要写什么, 或者你面前有艰巨的任务时, 请从小写开始。 。”
我可以继续讲关于测试的出色程度, 以及测试如何改变了世界和yada yada yada, 但你明白了。从概念上讲, 测试很棒。
相关:单元测试, 如何编写可测试的代码及其重要性
真实世界中的测试 虽然这三种测试都有其优点, 但大多数项目中并未编写它们。为什么?好吧, 让我分解一下:
时间/最后期限
每个人都有最后期限, 编写新的测试可能会阻碍会议的进行。编写应用程序及其相应的测试可能需要花费一个半(或更多)时间。现在, 有些人以节省时间为由, 不同意这一点, 但我认为情况并非如此, 我将在” 意见分歧” 中解释原因。
客户问题
通常, 客户并不真正了解什么是测试, 或为什么它对应用程序有价值。客户往往更关心产品的快速交付, 因此将程序测试视为适得其反。
或者, 可能很简单, 因为客户没有预算来支付实施这些测试所需的额外时间。
缺少知识
现实世界中有一个庞大的开发人员部落, 他们不知道存在测试。在每次会议, 聚会, 音乐会(甚至是在我的梦中)中, 我遇到的开发人员都不知道如何编写测试, 不知道要测试什么, 不知道如何设置测试框架等等。上。测试不是在学校中完全讲授的, 设置/学习框架以使其运行起来可能很麻烦。是的, 进入市场存在一定的障碍。
“ 很多工作”
对于新手和经验丰富的程序员, 甚至对于那些改变世界的才华横溢的类型, 编写测试都可能是不胜枚举的。最重要的是, 编写测试并不令人兴奋。有人可能会想:” 当我可以实施一项主要功能并取得令人印象深刻的结果时, 为什么要忙碌的工作呢?” 这是一个艰难的争论。
最后但并非最不重要的一点是, 很难编写测试, 而且计算机科学专业的学生也没有接受过相应的培训。
哦, 使用单元测试进行重构并不有趣。
意见分歧
在我看来, 单元测试对算法逻辑有意义, 但对于协调活动代码则没有太大意义。
人们声称, 即使你在编写测试方面花了更多的时间, 也可以在调试或更改代码时节省数小时。我希望有所不同, 并提出一个问题:你的代码是静态的还是不断变化的?
对于我们大多数人来说, 它一直在变化。如果你要编写成功的软件, 则总是要添加功能, 更改现有功能, 删除它们, 吃掉它们, 或者为了适应这些变化, 你必须不断更改测试, 并且更改测试需要时间。
但是, 你需要某种测试 没有人会说缺乏任何测试是最糟糕的情况。在代码中进行更改后, 你需要确认它确实有效。许多程序员尝试手动测试基础知识:浏览器是否呈现页面?是否正在提交表格?是否显示正确的内容?依此类推, 但我认为这是野蛮的, 低效的和劳动密集型的。
HTTP请求测试(开发人员的生存工具)

文章图片
我用什么代替 测试网络应用程序的目的是手动还是自动进行, 目的是确认是否在用户浏览器中呈现了任何给定的页面而没有任何致命错误, 并且该页面正确显示了其内容。实现此目的的一种方法(在大多数情况下, 是一种更简单的方法)是将HTTP请求发送到应用的端点并解析响应。响应代码告诉你页面是否已成功交付。可以通过解析HTTP请求的响应主体并搜索特定的文本字符串匹配来轻松测试内容, 或者, 你可以一步一步地使用诸如nokogiri之类的网络抓取库。
如果某些端点需要用户登录, 则可以使用为自动化交互(在进行集成测试时理想)而设计的库, 例如机械化登录或单击某些链接。实际上, 在自动化测试的大背景下, 这看起来很像集成或功能测试(取决于你如何使用它们), 但是编写起来要快得多, 可以包含在现有项目中, 也可以添加到新项目中。 , 比建立整个测试框架所需的精力更少。发现!
相关:雇用自由职业者质量检查工程师的前3%。
边缘案例在处理具有广泛价值的大型数据库时会带来另一个问题。测试我们的应用程序是否在所有预期的数据集上都能正常运行可能令人生畏。
一种解决方法是预见所有的极端情况(这不仅很困难, 而且常常是不可能的)并为每个情况编写测试。这很容易变成数百行代码(想象一下恐怖), 并且维护起来很麻烦。但是, 使用HTTP请求和仅一行代码, 你就可以直接在生产数据上, 在开发计算机或登台服务器上本地下载的数据上测试这种极端情况。
当然, 现在, 这种测试技术不是灵丹妙药, 并且与任何其他方法一样, 都有很多缺点, 但是我发现这些类型的测试更快, 更容易编写和修改。
HTTP请求测试(开发人员的生存工具)

文章图片
实践中:使用HTTP请求进行测试
由于我们已经确定编写代码而不进行任何形式的测试不是一个好主意, 因此, 对于整个应用程序, 我最基本的测试是将HTTP请求发送至本地所有页面, 并解析响应标头以获取200(或所需)代码。
例如, 如果我们要使用HTTP请求而不是(在Ruby中)编写上述测试(寻找特定内容和致命错误的测试), 则将是这样的:
# testing for fatal error http_code = `curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.articles_url(host: 'localhost', port: 3000) }` ifhttp_code !~ /200/ return "articles_url returned with #{http_code} http code." end# testing for content active_user =create(:user, name: "user1", active: true) non_active_user = create(:user, name: "user2", active: false) content = `curl #{Rails.application.routes.url_helpers.active_user_url(host: 'localhost', port: 3000) }` if content !~ /#{active_user.name}/ return "Content mismatch active user #{active_user.name} not found in text body" #You can customise message to your liking end if content =~ /#{non_active_user.name}/ return "Content mismatch non active user #{active_user.name} found in text body" #You can customise message to your liking end

该行curl -X#{route [:method]} -s -o / dev / null -w” %{http_code}” #{Rails.application.routes.url_helpers.articles_url(host:’ localhost’ , port:3000 )涵盖了很多测试用例;任何在文章页面上引发错误的方法都会在这里被捕获, 因此它可以有效地覆盖一次测试中的数百行代码。
第二部分专门捕获内容错误, 可以多次使用以检查页面上的内容。 (可以使用机械化处理更复杂的请求, 但这超出了本博客的范围。)
现在, 如果要测试某个特定页面是否适用于大量不同的数据库值(例如, 你的文章页面模板适用于生产数据库中的所有文章), 则可以执行以下操作:
ids = Article.all.select { |post| `curl -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.article_url(post, host: 'localhost', port: 3000) }`.to_i != 200).map(& :id) return ids

这将返回数据库中所有未呈现的文章的ID数组, 因此现在你可以手动转到特定的文章页面并检查问题。
现在, 我了解到这种测试方式在某些情况下可能无法正常工作, 例如测试独立脚本或发送电子邮件, 并且无疑比单元测试要慢, 因为我们正在为每个测试直接调用端点, 但是当你不能有单元测试或功能测试, 或两者都没有, 这总比没有好。
你将如何构建这些测试?对于小型, 不复杂的项目, 你可以将所有测试写入一个文件, 并在每次提交更改之前运行该文件, 但是大多数项目将需要一套测试。
我通常会根据测试内容, 为每个端点编写2到3个测试。你也可以尝试测试单个内容(类似于单元测试), 但是我认为这将是多余且缓慢的, 因为你将为每个单元进行HTTP调用。但是, 另一方面, 它们将更加干净和易于理解。
我建议将测试放在常规测试文件夹中, 每个主要端点都有自己的文件(例如在Rails中, 每个模型/控制器每个都有一个文件), 根据我们的需求, 该文件可分为三部分正在测试。我经常至少进行三个测试:
测试一 检查页面是否返回, 没有任何致命错误。
HTTP请求测试(开发人员的生存工具)

文章图片
请注意, 我如何列出Post的所有端点并对其进行遍历, 以检查是否呈现了每个页面而没有任何错误。假设一切顺利, 并且所有页面均已渲染, 你将在终端中看到以下内容:?sample_app git:(master)?ruby test / http_request / post_test.rb失败的URL列表-[]
如果未呈现任何页面, 你将看到类似以下内容(在此示例中, posts / index页面有错误, 因此未呈现):?sample_app git:(master)?ruby test / http_request / post_test.rb失败的网址-[{:: URL => ” posts_url” , :params => [], :method => ” GET” , :http_code => ” 500″ }]
测试二 确认所有期望的内容都在其中:
HTTP请求测试(开发人员的生存工具)

文章图片
如果在页面上找到了我们期望的所有内容, 则结果如下所示(在此示例中, 我们确保posts /:id具有帖子标题, 描述和状态):?sample_app git:(master)?ruby test / http_request / post_test.rb在Post#show页面上找不到帖子ID为1的内容列表-[]
如果在页面上未找到任何预期的内容(此处我们希望页面显示帖子的状态-如果帖子处于活动状态, 则为” 活动” , 如果帖子被禁用, 则为” 禁用” ), 结果如下所示:?sample_app git:(master )?ruby test / http_request / post_test.rb在Post#show页面上找不到帖子ID为1的内容列表-[“ 有效” ]
测试三 检查页面是否在所有数据集上呈现(如果有):
HTTP请求测试(开发人员的生存工具)

文章图片
如果所有页面都正确呈现, 我们将得到一个空列表:?sample_app git:(master)?ruby test / http_request / post_test.rb呈现错误的帖子列表-[]
如果某些记录的内容呈现存在问题(在此示例中, ID为2和5的页面给出了错误), 结果将如下所示:?sample_app git:(master)?ruby test / http_request / post_test。 rb呈现错误的帖子列表-[2, 5]
如果你想弄乱上面的演示代码, 这是我的github项目。
那么哪个更好?这取决于…
在以下情况下, HTTP请求测试可能是最好的选择:
  • 你正在使用网络应用
  • 你时间紧迫, 想快速写点东西
  • 你正在处理一个大型项目, 该项目既有未编写测试的现有项目, 但你仍需要某种方法来检查代码
  • 你的代码涉及简单的请求和响应
  • 你不想花费大部分时间来维护测试(我读过某个单元测试=维护地狱, 而我部分同意他/她的观点)
  • 你想测试一个应用程序是否适用于现有数据库中的所有值
在以下情况下, 传统测试是理想的选择:
  • 你正在处理的不是Web应用程序, 而是脚本
  • 你正在编写复杂的算法代码
  • 你有时间和预算用于编写测试
  • 业务要求没有错误或错误率低(财务, 庞大的用户群)
感谢你阅读本文;现在, 你应该有一种默认的测试方法, 可以在需要时间时依靠它。
有关:
  • 性能和效率:使用HTTP / 3
  • 保持加密, 确保安全:使用ESNI, DoH和DoT

    推荐阅读