下午好 我想介绍一种用于微服务的端到端测试的新工具-Catcher

为什么要测试?
为什么需要端到端测试? 马丁·福勒(Martin Fowler) 建议避免使用简单的测试。
但是,测试越高,重写越少。 单元测试几乎完全被重写。 如果进行严重的重构,功能测试还必须花费您的时间。 端到端测试应测试业务逻辑,并且更改频率较低。
另外,即使对所有微服务的完整测试覆盖也不能保证它们的正确交互。 开发人员可能会错误地实现该协议(名称/数据类型错误)。
或依靠文档中的数据方案实施新功能,并以方案不匹配错误的形式对产品环境感到惊讶:数据混乱或有人忘记更新数据方案。
并且所涉及的每个服务的测试都是绿色的。
为什么要进行自动测试?
真的 在我以前的工作地点,我们决定花时间部署自动化测试太长,困难且昂贵。 该系统并不大(带有常用卡夫卡的10-15个微服务)。 CTO认为“测试并不重要,主要是系统正常运行。” 在多个环境中手动测试。
外观(一般过程):
- 与其他开发人员达成协议(推出所有参与新功能的微服务)
- 推出所有服务
- 连接到远程卡夫卡(dmz中的双ssh)
- 连接到K8S日志
- 手动形成并向kafka发送消息(感谢上帝json)
- 观察日志,尝试了解它是否有效。
现在,这桶巧克力中有些焦油了:大多数测试需要由用户创建,因为重用现有的测试很困难。
首先,由于系统是分布式的,因此一些服务具有自己的数据库,其中包含有关用户的信息。
其次,kafka用于永久数据存储。 即 即使信息已在数据库中删除/更改,该服务在重新启动后仍会读回它。
新的测试用户的注册(大概)是什么样的:
- 输入任何数据(名称,邮件等)
- 输入个人数据(地址,电话,任何税务信息)
- 输入银行数据(实际上是银行数据)
- 回答20-40个问题(您已经感到疼痛了吗?)
- 进行IDNow识别(感谢上帝,他们在开发环境中将其禁用 ,在舞台上大约5分钟或更长时间,因为他们的沙箱有时会过载)
- 在此步骤中,需要在第三方系统中开设帐户,并且前端无法执行任何操作。 您需要通过ssh转到kafka并作为模拟服务器工作(发送一条有关该帐户已打开的消息)
- 接下来,您需要转到主持人的个人帐户中的另一个前端并确认用户。
超级,用户已注册! 美中不足的是:有些测试需要超过1个测试用户。 有时测试第一次失败。
以及业务团队如何验证新功能和确认?
在以下环境中需要重复所有相同的操作。
不用说,过了一会儿,您就开始感觉像猴子,只按它的按动来注册用户。
其他一些开发人员(通常是前端)在连接kafka时遇到问题。 并且终端中有一个错误,其中包含80多个字符的字符串(并非所有人都知道tmux)。
优点 :
- 无需配置/编写任何内容。 直接在运行的环境中进行测试。
- 不需要很高的资格(更便宜的专家可以做到)
缺点 :
- 需要很多时间(越远,越多)
- 通常仅测试新功能(尚不清楚现有功能是否已损坏)
- 通常,技术熟练的开发人员会进行手动测试(昂贵的专家会进行廉价的工作)。
如何自动化?
如果您点头说:“是的,这是一个很棒的过程,伙计们知道他们在做什么”,那么您将不会对此感兴趣。
自制e2e测试分为两种类型,具体取决于哪个程序员更自由:
- 测试环境中的后端。 它保护了通过端点抽动的测试逻辑。 由于与CI的交互,它甚至可以部分自动化。
- 具有相同有线逻辑的脚本。 唯一的区别是您需要去某个地方并从那里运行它。 如果您信任CI,那么您甚至可以自动运行它。
听起来不错。 有问题吗?
是的,这样的测试是根据编写者所知道的内容编写的。 通常,这些是脚本语言,如rub或python,可让您快速轻松地编写此类内容。 但是,有时您可能会偶然发现一堆bash脚本,C或其他更奇特的东西(我花了一个星期的时间将bash脚本上的自行车复制到python,因为这些脚本不再可扩展,而且没人真正知道它们的工作方式和测试内容) 。
这里的项目示例
优点 :
缺点 :
- 对开发人员资格的其他要求是可能的(如果他们使用Java开发,并且测试是用Python编写的)
- 编写代码以测试编写的代码(谁来测试测试?)
准备好了吗?
当然,只是朝BDD的方向看。 有一个黄瓜 ,有一个量规 。
简而言之,开发人员用一种特殊的语言描述业务场景,然后在代码中实现脚本的步骤。 该语言通常是人类可读的,并且假定不仅开发人员而且项目经理都可以读写。
这些脚本以及这些步骤的实现也位于单独的项目中,并由第三方产品(Cucumber / Gauge / ...)运行。
该脚本如下所示:
Customer sign-up ================ * Go to sign up page Customer sign-up ---------------- tags: sign-up, customer * Sign up a new customer with name "John" email "jdoe@test.de" and "password" * Check if the sign up was successful
并实现:
@Step("Sign up as <customer> with email <test@example.com> and <password>") public void signUp(String customer, String email, String password) { WebDriver webDriver = Driver.webDriver; WebElement form = webDriver.findElement(By.id("new_user")); form.findElement(By.name("user[username]")).sendKeys(customer); form.findElement(By.name("user[email]")).sendKeys(email); form.findElement(By.name("user[password]")).sendKeys(password); form.findElement(By.name("user[password_confirmation]")).sendKeys(password); form.findElement(By.name("commit")).click(); } @Step("Check if the sign up was successful") public void checkSignUpSuccessful() { WebDriver webDriver = Driver.webDriver; WebElement message = webDriver.findElements(By.className("message")); assertThat(message.getText(), is("You have been signed up successfully!")); }
完整项目在这里
优点 :
- 用人类可读的语言描述业务逻辑并将其存储在一个地方(可用作文档)
- 使用现成的解决方案,开发人员只需要知道如何使用它们
缺点 :
- 管理者不会读写脚本
- 您必须同时遵循规范及其实现(这是编写代码并编辑规范)
好吧,那为什么要抓捕?
当然可以简化流程。
开发人员仅在json / yaml中编写脚本,然后Catcher执行它们。 该脚本由顺序执行的步骤组成,例如:
steps: - http: post: url: '127.0.0.1/save_data' body: {key: '1', data: 'foo'} - postgres: request: conf: 'dbname=test user=test host=localhost password=test' query: 'select * from test where id=1'
Catcher支持jinja2模板,因此在上面的示例中,您可以使用变量而不是有线值。 全局变量可以存储在清单文件中(如整体中),从环境中提取并注册新的:
variables: bonus: 5000 initial_value: 1000 steps: - http: post: url: '{{ user_service }}/sign_up' body: {username: 'test_user_{{ RANDOM_INT }}', data: 'stub'} register: {user_id: '{{ OUTPUT.uuid }}' - kafka: consume: server: '{{ kafka }}' topic: '{{ new_users_topic }}' where: equals: {the: '{{ MESSAGE.uuid }}', is: '{{ user_id }}'} register: {balance: '{{ OUTPUT.initial_balance }}'}
此外,您可以运行测试步骤:
- check: # check user's initial balance equals: {the: '{{ balance }}', is: '{{ initial_value + bonus }}'}
您还可以运行其他脚本中的某些脚本,这对代码的整洁度和重用性(包括通过标签系统启动仅部分步骤,延迟启动等)具有极好的效果。
include: file: register_user.yaml as: sign_up steps: # .... some steps - run: include: sign_up # .... some steps
插入和使用脚本可以解决等待资源(在服务启动时等待服务)的问题。
除了现成的内置步骤和其他存储库之外,还可以用python(仅继承自ExternalStep )或任何其他语言编写模块:
并使用:
--- variables: one: 1 two: 2 steps: - math: add: {the: '{{ one }}', to: '{{ two }}'} register: {sum: '{{ OUTPUT }}'}
脚本放置在docker文件中并通过CI运行。
此图像也可以在Marathon / K8s中用于测试现有环境。 目前,我正在开发一个后端(类似于AnsibleTower),以使测试过程更加轻松,便捷。
优点 :
- 无需编写代码(仅在自定义模块的情况下)
- 通过清单文件切换环境(如集成)
- 您可以使用自己的模块(使用任何语言,甚至sh)
缺点 :
而不是结论
当我编写此工具时,我只是想减少通常花在测试上的时间。 碰巧的是,每个新公司都必须编写(或重写)这样的系统。
但是,该工具比我预期的更灵活。 如果有人对本文(或工具本身)感兴趣,我可以告诉您如何使用Catcher来组织集中式迁移和更新微服务系统。
更新
正如我在评论中指出的那样,该主题未公开。
我将在这里指出最具争议的论文。
- 端到端测试不是单元测试。 我已经在本文中提到了M. Fowler 。 单元测试位于测试后端项目(标准
tests
目录)中,并在每次代码更改为CI时运行。 e2e测试是一个单独的项目,通常需要更长的时间,测试所有参与服务的交互,并且对项目的代码一无所知(黑盒)。 - 您不应该使用Catcher进行集成(及以下)测试。 很贵 在您的YP上为当前后端编写测试要快得多。 仅当您的业务逻辑分布在2个或更多服务上时,才需要进行端到端测试。
- 麦田守望者》也是BDD。 从我的角度来看,与Gauge / Cucumber相比,其主要优点是现成的模块以及添加模块的便捷性。 理想情况下,仅编写测试。 在上一家公司中,我在标准组件上编写了全部4个测试,而没有编写任何程序。 因此,资格要求(以及此类专家的价格)将更低。 仅需要了解json / yaml以及具有读取规范的能力。
- 要编写Catcher测试,您必须学习Catcher-DSL。 las,这是真的。 首先,我想让测试直接在麦克风上自行编写。 但是后来我认为他们会因为不必要而解雇我;)如上所述-Catcher DSL是标准的json / yaml和步骤规范。 根本没有什么新鲜的。
- 您可以使用标准技术并编写自己的东西。 但是,我们正在谈论微服务。 这是大量不同的技术和核武器以及大量的团队。 如果对于Java命令junit + testcontainers是显而易见的选择,那么erlang团队会选择其他东西。 在一个拥有30多个团队在顶部的大型公司中,他们将决定将所有测试提交给新的基础架构/质量保证团队。 您能想象他们在这个动物园里有多高兴吗?
- 如果您有4-5个e2e测试,那么您可以使用任何脚本语言编写所有内容,而不必理会。 但是,如果逻辑随时间而变化,则在2-4年后,您将不得不重构,直接分发测试的业务逻辑以及对被测试组件的访问方法的实现。 因此,最终,您编写的Catcher不够灵活。 我花了4个实现来理解这一点;)