赛普拉斯强大的集成测试

If you are writing a moderately complex web application, eventually, you will run into difficulties that can occur when a change in one place has unintended consequences elsewhere in the application. These changes are inevitable as an application ages, and unit testing will not save you. Tests that exercise the full application’s web of dependencies are the best path to assuring success. In addition, as the application evolves, these tests need to be easy to change, and avoid failing when irrelevant implementation details change under the hood.

如果编写的是一个中等复杂的Web应用程序,最终,当一个地方的更改在应用程序的其他地方产生意外后果时,您将遇到可能遇到的困难。 随着应用程序的老化,这些更改是不可避免的,并且单元测试将无法挽救您。 行使整个应用程序相关性网络的测试是确保成功的最佳途径。 此外,随着应用程序的发展,这些测试必须易于更改,并避免在无关紧要的实现细节发生更改时失败。

In my most recent employment at Citrine Informatics, we adopted Cypress (https://cypress.io) as our testing framework for both integration and end-to-end testing. There’s no question: It transformed our working dynamic. Both our certainty we are building the right thing and certainty that things will work went way up. Along the way, we learned a few subtle tricks to keep Cypress stable in both local and CI environments. We also learned how powerful the right testing approach can be towards steering product development to find an optimal user experience. All of this is possible with minimal disruption of developer work to craft the tests themselves, and that is where Cypress shines compared to other testing approaches.

在我最近在Citrine Informatics工作期间,我们采用赛普拉斯( https://cypress.io )作为集成和端到端测试的测试框架。 毫无疑问:它改变了我们的工作动力。 我们正在建立正确的事物的确定性以及事物将正常运行的确定性都在上升。 在此过程中,我们了解了一些微妙的技巧,可以使赛普拉斯在本地和CI环境中保持稳定。 我们还了解了正确的测试方法对于指导产品开发以找到最佳用户体验的强大功能。 所有这些都是可能的,开发人员自己进行测试的工作就不会受到干扰,与其他测试方法相比,赛普拉斯大放异彩。

为什么是柏树? (Why Cypress?)

From a developer’s perspective, Cypress is an incredible experience. Scripted in javascript or typescript, the tests run inside the browser through a browser extension and an electron app that manages the test runner. As you develop a test, you can watch it run in the browser, and afterwards inspect specific elements at a past step, rewind to see why your selector did not select what you expected, and more. The framework is very fast compared to Selenium-based solutions or their step-children (hello, Puppeteer), and has built-in “retry-ability” without exposing a clunky “wait for this” interface.

从开发人员的角度来看,赛普拉斯是令人难以置信的体验。 测试使用JavaScript或打字稿编写脚本,通过浏览器扩展程序和管理测试运行程序的电子应用程序在浏览器中运行。 在开发测试时,您可以观察它在浏览器中的运行情况,然后在过去的步骤中检查特定的元素,倒退以查看为什么选择器未选择您期望的内容,等等。 与基于Selenium的解决方案或其子级组件(您好, Puppeteer )相比,该框架非常快,并且具有内置的“重试能力”,而没有暴露笨拙的“等待此”界面。

It has a wealth of plugins, and a commercial dashboard that makes running tests in parallel and inspecting results in real-time easy. It takes a screenshot by default on test failure, which is something that has to be manually configured for Puppeteer and friends.

它具有丰富的插件和一个商业仪表板,可以并行运行测试并实时轻松地检查结果。 默认情况下,它会在测试失败时获取屏幕截图,这是必须为Puppeteer和朋友手动配置的截图。

Prior to using Cypress, we did not yet have an end-to-end test suite, as the web interface to our platform was brand new. We did have some Jest unit tests, and toyed briefly with a react-testing-library/nock-based framework for mocking out a server as a custom integration test framework. Long story short: don’t do this. It’s theoretically possible, but a nightmare to debug when something fails to work. Instead, write integration tests in an environment where you can see the app as the test runs!

在使用赛普拉斯之前,我们还没有端到端的测试套件,因为我们平台的Web界面是全新的。 我们确实进行了一些Jest单元测试,并简要介绍了一个基于react-testing-library / nock的框架,用于将服务器模拟为自定义集成测试框架。 长话短说:不要这样做。 从理论上讲这是可能的,但是当某些东西无法正常工作时,进行调试是一场噩梦。 相反,请在可以在测试运行时看到应用程序的环境中编写集成测试!

In the 9 months since adopting Cypress, we have learned a ton, and our testing suite has evolved to a mature stage where our tests are now remarkably stable in both an end-to-end test environment against a live server, and an integration test environment using a mocked-out server. Writing new tests for features, or modifying existing tests for changes to existing features is fast, and supports an agile iteration that includes input from product, design and developers.

自采用赛普拉斯以来的9个月里,我们学到了很多东西,我们的测试套件已经发展到成熟阶段,在针对实时服务器的端到端测试环境和集成测试中,我们的测试现在都非常稳定使用模拟服务器的环境。 编写功能的新测试或修改现有的测试以更改现有功能非常快速,并且支持敏捷迭代,包括产品,设计和开发人员的输入。

早期开始:Citrine测试的演变 (Early beginnings: the evolution of testing at Citrine)

When we first adopted Cypress, we tended to use its built-in selection and assertion functionality like this

首次采用赛普拉斯时,我们倾向于使用其内置的选择和断言功能,例如:

describe("Project - project overly", () => {
  it("should toggle the project overlay open and closed", () => {


    // Click project dropdown
    cy.get("#select-project-dropdown").click();


    // Check for empty table
    cy.get("#projects-table-wrapper").should("be.visible");


    // Click project dropdown
    cy.get("#select-project-dropdown").click();


    // Check for empty table
    cy.get("#projects-table-wrapper").should("not.be.visible");
  });
});

Soon after, QA guru Jeff Nyman (check out his extensive blog on testing at https://testerstories.com/author/Administrator/) recommended we take a look at using “page objects” to abstract out the elements on a page. Our first attempts looked like:

不久之后,质量检查专家Jeff Nyman(请查看他关于测试的大量博客,网址https://testerstories.com/author/Administrator/ )建议我们来看一下使用“页面对象”来提取页面上的元素。 我们的首次尝试如下所示:

export class ProjectDashboard {
  nameField() {
    return cy.get("#header-name");
  }


  descriptionField() {
    return cy.get("#header-description");
  }


  assetsTab() {
    return cy.get("#project-dashboard-tabs").contains("Assets");
  }


  projectMembersTab() {
    return cy.get("#project-dashboard-tabs").contains("Project Roster");
  }


  filterButton() {
    return cy.get("#data-objects-filter");
  }


  viewDatasetsLink() {
    return cy.get("#view-datasets-link");
  }
// and more
}

This worked pretty well for us. However, Jeff was gentle, but persistent: things could work better. At this point, our requirements were loosely spelled out in Jira tickets, and our tests were basically hidden from the product team, as something that we coded on our own. Once a ticket was closed, the requirements would disappear into the vacuum of things-you-can’t-find-in-Jira-by-searching-for-them. If something seemed weird in the app, there was no single place to point to that said “this is how it should work.” Directly pinging someone to see if they knew the answer was the best way to get this info, and occasionally, two different people would give opposing answers.

这对我们来说效果很好。 但是,杰夫很温柔,但很执着:事情可以做得更好。 在这一点上,我们的要求在Jira票证中被松散地阐明,并且我们的测试基本上是对产品团队隐藏的,因为这是我们自己编写的。 一旦关闭车票,需求就会消失在您无法通过搜索找到吉拉的事物的真空中。 如果应用程序中有些东西看起来很奇怪,那么就没有一个地方可以指出“这就是它应该如何工作”。 直接对某人执行ping操作,以查看他们是否知道答案是获取此信息的最佳方法,有时,两个不同的人会给出相反的答案。

As a developer, this is frustrating. As a company, this is downright dangerous: your customers will definitely notice if you listen to the wrong person and “fix” expected behavior!

作为开发人员,这令人沮丧。 作为一家公司,这是非常危险的:如果您听错了人的话并“修复”预期的行为,您的客户一定会注意到的!

混淆了需求和测试之间的界限 (Blurring the line between requirements and tests)

At this point, Jeff’s constant refrain of “eventually, we’ll have executable feature specs” began to make sense. Instead of writing vague requirements in a Jira ticket, and often sending developers back to the beginning to fix a necessary requirement that was not at all clear when the feature was all done, there was a better way. We could write our specs in a clear format, one clear enough that it could serve both as requirements, and as the inputs used to run automated tests. The language would allow both running manually (a person reading the spec and manually doing what it says) or running automatically by a testing framework.

在这一点上,杰夫一直坚持的“最终,我们将拥有可执行的功能规范”这一说法开始变得有意义。 有一种更好的方法,而不是在Jira票证中写出模糊的要求,并经常让开发人员重新开始以解决所有功能完成时根本不清楚的必要要求。 我们可以用一种清晰的格式来编写我们的规范,一种足够清晰的规范,使其既可以用作需求,又可以用作运行自动化测试的输入。 该语言将允许手动运行(阅读规范并手动执行说明内容的人员)或通过测试框架自动运行。

We chose to implement this by porting Jeff’s Testable framework into Typescript, and to adapt Cypress to use the cypress-cucumber-preprocessor plugin to directly run feature specifications written in the Gherkin dialect as tests. Since then, we have gradually migrated our existing tests over to this new format, and written several new tests as new features have been built.

我们选择通过将Jeff的Testable框架移植到Typescript中来实现此目的,并使Cypress适应使用cypress-cucumber-preprocessor插件来直接运行以Gherkin方言编写的功能规范作为测试。 从那时起,我们逐渐将现有测试迁移到这种新格式,并在构建新功能时编写了一些新测试。

import { extractDeclaration, extractElement } from "../testable/Element";


/**
 * Define the cell context for a dx-grid table
 */
const cellContext = extractDeclaration({
  cell: {
    self: (num: number) => cy.get(`td:nth-child(${num})`),
    needsParams: true,
    children: {},
  },
  cellByContent: {
    self: (contents: string) => cy.get(`td:contains("${contents}")`),
    needsParams: true,
    children: {},
  },
});


/**
 * Header cell context for a dx-grid table
 */
const headerCellContext = extractDeclaration({
  cell: {
    self: (num: number) => cy.get(`th:nth-child(${num})`),
    needsParams: true,
    children: {},
  },
  cellByContent: {
    self: (contents: string) => cy.get(`th:contains("${contents}")`),
    needsParams: true,
    children: {},
  },
});


/**
 * Define the row context for a row, selected by its row number
 */
const rowByNumberContext = extractElement({
  self: (num: number) => cy.get(`tr:nth-child(${num})`),
  needsParams: true,
  children: cellContext,
});


const headerCellByNumberContext = extractElement({
  self: (num: number) => cy.get(`tr:nth-child(${num})`),
  needsParams: true,
  children: headerCellContext,
});


/**
 * Define the row context for a row, selected by cell contents
 */
const rowByContentContext = extractElement({
  self: (name: string) => cy.get(`tr td:contains("${name}")`),
  needsParams: true,
  children: cellContext,
});


/**
 * Define the re-usable context for a dx-grid-based virtual table
 */
export const tableContext = extractDeclaration({
  header: {
    self: () => cy.get("thead"),
    children: {
      row: headerCellByNumberContext,
    },
  },
  body: {
    self: () => cy.get("table:nth-child(2) tbody"),
    children: {
      spinner: () => cy.get(".MuiCircularProgress-svg"),
      row: rowByNumberContext,
      rowWithContent: rowByContentContext,
      rows: () => cy.get("tr"),
    },
  },
  scrollableElement: () => cy.get("table:nth-child(2)").parent().parent(),
  paginationContainer: () =>
    cy.get("table:nth-child(2)").parent().parent().next(),
  pagination: {
    self: () =>
      cy
        .get("table:nth-child(2)")
        .parent()
        .parent()
        .next()
        .find("div:first-child"),
    children: {
      pageNumbers: () => cy.get("span").first(),
      previous: () => cy.get("[aria-label='Previous']"),
      next: () => cy.get("[aria-label='Next']"),
      pageNumberButton: {
        self: (page: number) => cy.get(`button :contains(${page})`).parent(),
        needsParams: true,
        children: {},
      },
    },
  },
});


export const simpleTableContext = extractDeclaration({
  header: {
    self: () => cy.get("thead"),
    children: {
      row: headerCellByNumberContext,
    },
  },
  body: {
    self: () => cy.get("table tbody"),
    children: {
      spinner: () => cy.get(".MuiCircularProgress-svg"),
      row: rowByNumberContext,
      rowWithContent: rowByContentContext,
      rows: () => cy.get("tr"),
    },
  },
  scrollableElement: () => cy.get("table").parent().parent(),
  paginationContainer: () => cy.get("table").parent().parent().next(),
  pagination: {
    self: () =>
      cy.get("table").parent().parent().next().find("div:first-child"),
    children: {
      pageNumbers: () => cy.get("span").first(),
      previous: () => cy.get("[aria-label='Previous']"),
      next: () => cy.get("[aria-label='Next']"),
      pageNumberButton: {
        self: (page: number) => cy.get(`button :contains(${page})`).parent(),
        needsParams: true,
        children: {},
      },
    },
  },
});

结语(Wrap-up)

This is a lot to digest! The next article in this 2-part series is about how to set up webpack and Cypress within docker to kill off flake. Enjoy, and happy testing!

这要消化很多! 这个分为2部分的系列中的下一篇文章是关于如何在docker中设置webpack和Cypress来杀死薄片的。 享受,祝您测试愉快!

资源资源 (Resources)

Cypress end-to-end testing framework:

赛普拉斯的端到端测试框架:

Tester Stories (Jeff Nyman’s testing blog):

测试人员故事(Jeff Nyman的测试博客):

https://testerstories.com/author/Administrator/

https://testerstories.com/author/Administrator/

cypress-cucumber-preprocessor Cypress plugin:

cypress-cucumber-preprocessor赛普拉斯插件:

翻译自: https://medium.com/@gregorybeaver/robust-integration-tests-with-cypress-7337a40e815a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值