客户旅程_级联自动旅程测试

客户旅程

重要要点

  • 随着我们拥有更大的团队,拥有更多的流程以及采用微服务架构,测试系统的问题变得越来越难。
  • 向前发展,测试问题根本不同。 我们在单元测试环境中测试特定功能点的能力较弱。
  • 昂贵的网络电话使我们受了诅咒。 除非您使技术堆栈同质化并抽象出网络调用,否则我们将无法逃脱此问题。
  • 我们需要新的工具来处理这些状态机。
  • 状态机本质上是美丽的。 我们应该利用它。

古时候...

生活就是要面对障碍。 我们跳了一跳,然后面对另一跳。 对于IT业尤其如此,因为该行业实际上只有两到三十年才能成熟。 当然,如今,每个人都在“敏捷”发展,敏捷性的基本组成部分就是测试的概念,即测试的概念,它是当前行业成熟度的一项关键创新。

当我开始开发时,我常常发现自己坐在代码库的前面,因为担心引入错误而瘫痪了。 如果我介绍一个,那么我至少会失去声誉,不用担心物质损失。 另一方面,如果我设法正确地引入了一项变更,那我将只能完成自己的工作。 问题在于,为了进行更改而没有任何风险,我必须对系统中的所有内容都具有绝对的了解。 当然,在团队合作中,很难获得这种绝对的知识。 一种可能的方法是引入尽可能少的更改以提供我的功能; 永不重构! 毕竟,开发人员可能会感谢您的重构,但是如果出现问题,在您向产品负责人解释时,尽管您的更改对底线产生了负面影响,但他们会避开瘟疫,尽管这会改善代码。

一些测试怎么样?

现在我们有测试。

当我们开始编写测试时,我们正在寻找一种方法来验证我们的代码是否有效。 这为我们提供了一个框架或方法论,使我们可以在不完全了解的情况下进行更改。 如果来到我们之前的开发人员将他们的知识正确地提取到了测试中,那么通过运行测试,我们可以确定我们没有破坏任何东西。 一切都很好,但是测试的效用并不仅限于此。 开发人员出于其他原因开始欣赏测试; 教学原因。 测试不仅告诉您该代码有效,而且为您提供了使用方法的示例! 现在,我们很自然地可以在代码中记录我们的需求,并且该文档将随代码一起使用。 测试将与代码一起进行并且可以重复; 每次更改后都可以运行测试。 我们从一个稳定的点开始,然后引入变化,并在一个稳定的点结束。 这不是一个新主意。 当我学习计算机科学时,聪明的学生很快就想到了一种称为“迭代开发”的开发方法。 从小处着手,使某些事情起作用,然后迭代扩展; 永远不要一次写完整的解决方案。 但是现在我们有了测试,并且测试可以自动方式告诉我们我们处于稳定点。 我们可以谈论建立一个“绿色”建筑; 表示可以接受并且可以共享或部署和使用的代码库版本。

但是运行测试是可选的

开发代码要好得多。 我们可以进行更改和重构。 代码库的质量大大提高。 因此不可避免地,我们开始遇到下一组问题。 我们可以处理开发人员过去所做的更改,但是开发人员当前所做的更改又如何呢?

我们必须团队合作。 只要我们在每次更改后都运行测试,并且只将良好的代码提交到源代码管理中,一切都会很好。 问题在于,开发人员要么是永恒的乐观主义者,要么就是懒惰。 我们说,这一小小的改变肯定不会破坏任何东西。 让我跳过本地计算机上的测试并提交到源代码管理中。 因此,当然,我不可避免地会引入无法建立的变更; 测试将失败。 然后,其他开发人员当然会看到我的更改并遇到我的所有错误。 甚至更糟; 他们会经历我的错误而不会知道我刚刚所做的事情。 他们将不得不深入研究源代码控制并分析失败的测试。 每个需要更新的开发人员都必须这样做。 因此,我引入的错误可以修复多次。 如果团队沟通问题,那么一名开发人员将解决问题,而其他开发人员则等待。

持续集成通过设置一个在开发人员提交代码时将自动运行测试的系统来改变了这一切。 每次更改代码后,都会对代码进行验证,而我们不再依赖开发人员的纪律。 就像多线程软件开发一样,我们现在在我们的开发方法中引入了“编码信号量”。 此信号量协调了许多开发人员并行更改代码的行为。 当构建为“绿色”时,开发人员可以更新代码库。 当构建为“红色”时,最后的更改会破坏某些内容,因此最好避免。

但是信号灯并没有真正起作用。 问题是存在第三种状态。 该构建可以是“当前正在构建”。 这是一个灰色区域,开发人员不知道有什么问题。 他们只是知道可能有问题。 禁止将内容提交红色版本最初是为了降低修复损坏的版本的复杂性,而不是最大限度地提高开发人员的生产力。 你可以更新吗? 如果您愿意解决任何潜在的问题,那么可以。 您可以提交代码吗? 当然可以,该构建不是红色的,它正在构建中。 这些政策的逐步形成并不是设计出来的,而是由试图弄清楚如何共同努力的团队制定的。

并且构建时间开始花费的时间越来越长。

闲手打破规则

因此,在这里,我们正在构建应用程序,致力于减少构建时间。 但是六个月后,我们的测试逐渐流行起来。 现在的构建时间是半小时到一个小时。 我们有一个由十名开发人员组成的团队,我们很幸运每天能提交一次。 每个人都开始意识到他们的大部分时间都花在尝试获取代码上,而不是开发实际的代码上。 每次比赛都是绿色的! 开发人员将命运摆在首位,相互承诺。 构建现在是红色的。 没有人想挖掘最近的五次提交来找到问题,抱歉,现在只有六次提交,来找到问题。 每个人都意识到这里存在方法上的问题。 Scrum主管很不情愿地同意在红色版本上进行提交是不可避免的。 暂停使用代码信号量,以吸引一位开发人员花一周时间尝试使红色版本变为绿色。 当即将发布一个版本时,主代码就会分支,开发人员和测试人员会在下一周尝试使一些“交付的”代码正常工作。

哪里出错了?

这使我们处于最新状态。 当前,许多团队都在经历此问题。 那么潜在的解决方案是什么? 好吧,让我们将问题分解成几个组成部分:

  • 太多的开发人员试图访问相同的代码库。
  • 代码验证时间太长,因此信号清除速度不够快。

如果减轻这些因素中的任何一个,就可以解决问题。

开发人员将自己分成团队

因此,让我们讨论第一个因素:太多的开发人员在同一个代码库上工作。 好吧,您首先雇用了这么多开发人员是有原因的。 这个想法是要加快发展。 将开发人员送回家不会帮助您完成此任务。 从某种意义上讲,这是放弃问题。 我们想要的是保持生产力,但是在代码库或持续集成管道上的争用减少。 我们希望更少的开发人员互相等待。

还有另一种方法。 您可以将代码库分为几个代码库,并且每个小组都有不同的团队工作。 用并发编程术语,我们已删除单个排除锁,转而使用多个锁。 我们减少了争用,开发人员的等待也减少了。 我们已经解决了一个问题,但是又引入了另一个问题。 现在,我们有不同的交付物,无论它们是微服务还是库,都需要对其进行独立测试。 交付成果共享合同。 我们的测试忽略了全球情况。 我们不再确定这些组件是否相互影响,因为它们是独立的系统,现在具有一组独立的测试。 现在,我们的测试不那么包容,更详尽,并且最终减少了产品所有者和用户对验收测试的使用。

因此,我们引入了第二阶段持续集成管道。 该管道将​​可交付成果组合在一起,并开发了测试以使用该系统。 我们引入了一个新团队来管理这些测试,并为测试赋予了新的名称。 我们称它们为端到端测试,冒烟测试和验收测试。

情况发生了变化,而且变化很大。 我们尝试集成的组件更像黑匣子组件。 我们开始失去对我们可以控制的内部数量的控制。 我们在此级别的测试处于更高的级别。 而且它们比我们以前使用的单元测试要昂贵得多。 他们需要很长时间才能运行。

我们对代码库进行了划分,我们发现在开发组件时不同的团队又可以提高工作效率,但是当我们将组件集成在一起时,又遇到了旧问题。 测试需要很长时间才能运行。 我们可以说只是推迟了这个问题。 在此过程中,我们已经将我们的系统组成了微服务的标志。 那肯定是一件好事,但在这里我们已经成熟了一些,但又遇到了同样的问题。

面对房间里缓慢的大象

为何构建这么慢? 问题的核心是网络调用(或磁盘访问)比方法调用慢得多。 网络呼叫可能只需要20毫秒。 但是方法调用是如此便宜,以至于您可以在同一时间段内进行数百万个方法调用。 如此多的人提出,应该使用单元测试来支持集成测试(我的意思是指进行网络调用的任何测试。)这肯定可以解决问题,但是我们需要的是一头笨拙的东西。承认。 单元测试不是集成测试,因为它们使大部分最终系统都未经测试。 但是,他们确实测试了开发人员实际开发的内容。 但是“配置就是代码”。 因此,当我配置Spring或数据库或nginx时; 为什么在坚持测试开发人员用Java编写的任何内容的同时,我仍然不愿意测试这些配置,为什么感到高兴? 如果不进行配置,为什么要特别注意该语言? 如果有的话,有一个编译器可以确保语言的类型安全。 没有用于配置的编译器,因此,如果需要进行测试,那么肯定一定是无类型配置吗? 因此,用替代集成测试代替单元测试的论点就是减少测试的论点。 单元测试有其自身的优势,特别是作为用于良好软件开发的开发工具,但就最终用户的价值而言,它们不能替代。

这场辩论还有另一方面。 在单元测试环境中,您可以在任意启动状态下进行测试。 您可以访问模拟和存根,并且可以操纵测试流程以专注于要测试的特定功能点。 通过使用IoC或通过覆盖方法(例如抽象工厂方法模式)可以提供此功能。 使用集成测试,您可以减少控制。 您可以使用诸如WireMock之类的工具在网络级别上访问模拟和存根,但无法注入这些组件,因此必须将其作为状态机与系统进行交互。 您必须使系统经历多个状态,才能到达可以测试需求的特定阶段,并且必须一次又一次地进行网络调用。 难怪这些测试如此昂贵!

我们走了一个完整的圈子。 开发团队现在可以高效工作,但是他们共享的合同存在更多的开发风险。 现在,他们的不同交付物的组成属于另一个团队,这没有在可以进行方法调用的单元测试环境中运行的好处。 他们必须打网络电话,现在必须测试状态机。 开发软件方面的障碍现在已经下移了,但仍然存在。

但是现在我们要去旅行了

让我们再次看看这些测试。 自从我们开始以来,我们已经改变了很多情况。 我们已经建立了尽可能多的单元测试,并且它们运行很快,但是现在我们有了微服务和不同的团队。 我们拥有第三方组件和现成的软件,并且我们想证明它们都能正常工作。 而且我们知道,证明所有这些功能都可以很好地协同工作并不是仅凭单个功能点就可以证明的。 我们将不得不通过许多不同的步骤来完成这个联邦系统。

例如,假设我们正在为在线商店开发结帐流程。 我们有处理不同功能的不同微服务。 一种服务处理购物篮,另一种服务处理商店本身的产品展示。 另一个负责广告等。所有这些组件共享一份合同,并且正在独立开发。 现在,我们面临着测试它们是否一起工作的问题。 因此,我们编写了一个执行整个过程的测试。 在单元测试的上下文中,我们可以单独测试一项功能。 例如,我们可以测试是否可以将商品放入购物篮。 或者我们可以测试篮子中的所有物品总和是否正确。 但是现在我们正在测试微服务和Web门户。 我们需要先将物品放到篮子里,然后才能测试篮子是否正确装箱。 这意味着我们需要浏览网站并单击项目以将其添加到购物篮中。 所有这些动作共同形成了一种新的测试方式,我现在将其称为“旅程测试”。 为了将这些组件视为黑匣子,我们必须经过通常很昂贵的旅程。

因此,让我们并排说明一些旅程。 每个步骤都由一个字母表示,该字母大致等同于访问网页并单击某些内容。 我们可能有五次这样的旅程,除了旅程5之外,它们都从网页A开始,并且都过渡到网页C。依此类推...

而且它们每个都需要大约30秒才能运行。

好吧,仔细研究它们会发现一些重要的特征。

  • 它们非常重复。 这主要是由于所有旅程结束时步伐的增加所致。 F,G和H
  • 步骤B和D以不同的方式开始或以不同的方式受到影响,但步骤F以相同的方式结束。

显然这里有些效率低下。 我可以巩固旅程测试。 我会经历五次旅程,再降为三次旅程。 因此,再次以表格形式:

因此,我们对在哪个旅程中进行的测试很聪明,因此涵盖了与以前相同的功能。 这节省了40%的时间。 如果在整个测试集中获得相同的性能提升,则与30分钟的构建相比,这可能是22分钟的构建。 以我的经验,通过智能地组成行程测试可以实现的性能提升比这更重要。

因此,现在将这些测试合并为有效的测试脚本并进行管理成为测试人员的工作。 但是,仍然需要考虑以下几点:

  • 我们称这些测试为什么? 他们不再专注于特定需求,而是一次性满足多个需求。
  • 我们如何确定差距? 我们怎么知道我们错过了什么?
  • 我们如何确定哪些脚本是多余的? 如果我们有一个新的要求,我们如何知道在哪里插入这些新的断言?

管理这种复杂性是我编写的称为Cascade的测试框架的目的。

有新问题吗? 新工具!

那么Cascade如何做到这一点呢?

我们定义组成单独课程的旅程的每个步骤。 然后,框架将管理它们并从中生成测试。

Cascade的源代码包含如何使用它的示例。 第一个示例介绍了如何使用Cascade测试在线银行网站。

我们必须从某个地方开始,因此在这种情况下,我们将在登录页面上打开一个浏览器。 你可以看到在github工作代码示例在这里

@Step
public class OpenLandingPage {
    @Supplies
    private WebDriver webDriver;

    @Given
    public void given() {
        webDriver = new ChromeDriver();
    }

    @When
    public void when() {
        webDriver.get("http://localhost:8080");
    }

    @Then
    public void then() {
        assertEquals("Tabby Banking", webDriver.getTitle());
    }

 }

注意,注释在框架中广泛使用。 步骤保存在单独的文件中,每个步骤文件都用@Step注释。 使用IoC形式以及@Supplies@Demands批注在步骤之间共享数据。 生命周期方法使用@Given@When@Then方法进行注释。

生命周期方法值得考虑。 @Given用于注释将数据提供给测试的方法。 该测试数据在所有步骤文件之间共享。 通过这种方式,步骤文件可以协作来描述它们将在其上进行操作的数据。 @When表示此步骤的操作方法。 此方法应执行状态转换,例如单击按钮或提交表单。 最后, @Then标记执行验证的方法。

在这种情况下,我们将初始化Selenium的WebDriver并打开侦听端口8080的Web服务器的索引页。最后检查该网页是否已打开。

OpenLandingPage步骤之后是Login 步骤

@Step(OpenLandingPage.class)
public interface Login {
    public class SuccessfulLogin implements Login {
        @Supplies
        private String username = "anne";
        @Supplies
        private String password = "other";

        @Demands
        private WebDriver webDriver;

        @When
        public void when() {
            enterText(webDriver, "[test-field-username]", username);
            enterText(webDriver, "[test-field-password]", password);
            click(webDriver, "[test-cta-signin]");
        }

        @Then
        public void then() {
            assertElementPresent(webDriver, "[test-form-challenge]");
        }
    }

    @Terminator
    public class FailedLogin implements Login {
        @Supplies
        private String username = "anne";

        @Supplies
        private String password = "mykey";

        @Demands
        private WebDriver webDriver;

        @When
        public void when() {
            enterText(webDriver, "[test-field-username]", username);
            enterText(webDriver, "[test-field-password]", "invalidpassword");
            click(webDriver, "[test-cta-signin]");
        }

        @Then
        public void then() {
            assertElementPresent(webDriver, "[test-form-login]");
            assertElementDisplayed(webDriver, "[test-dialog-authentication-failure]");
        }
    }
}

您可能注意到的第一件事是,我们现在有两个实现接口的类。 用Cascade的术语来说,我们有两种登录步骤的方案。 我们提供了成功登录和失败登录的方案。

引起您注意的第二件事是@Step批注。 在此,我们声明此步骤之前的步骤。 因此,从这些信息中,您可以推断出Cascade的功能。 它在类路径中扫描@Step注释的类,然后将它们连接到测试中。

接下来,您可以在这里看到我们有一个需要数据的步骤。 在这种情况下,登录步骤需要Selenium的webdriver实例,以便它们可以驱动浏览器。

然后,如果仔细查看每个方案提供的值,您会注意到它们正在为同一用户设置数据。 如果阅读@When方法的定义,您将看到提供给浏览器的密码与正在播放的特定场景有关。 换句话说, FailedLogin方案输入了错误的密码。

可以预见的是, @Then方法随后将检查我们是否已到达场景的相应网页,或者正在显示故障对话框。

最后,您可能已经注意到@Terminator注释。 该注释告诉Cascade,此步骤之后不应执行任何步骤。 因此,验证失败的旅程就此结束。

github上的示例将旅程扩展到了更高的层次。 随意看看。

对状态机建模

因此,当您完成所有步骤文件的编写并将它们链接在一起时,您已经做了一些非常有趣的事情。 您不再仅仅拥有描述旅程的不同脚本的集合。 您拥有的是一个模型。 您已经描述了状态机,还描述了如何从一种状态过渡到另一种状态。

一旦创建了这个非常漂亮的模型,我们就可以用它做有趣的事情,因为它是一个数学系统,而不仅仅是脚本的集合。

我们可以在该模型的测试报告中生成图形

级联将生成状态机的图形表示。

我们可以查看旅程中任何特定阶段的所有数据的摘要。 通过单击图中的适当链接,我们将看到类似这样的内容。

我们只能选择性地运行特定的测试或小组测试

级联允许您为测试生成器指定过滤器。 然后,它将滤除与指定谓词不匹配的测试。

@FilterTests
Predicate filter = and(
    withStep(Portfolio.CurrentAccountOnly.class),
    withStep(SetupStandingOrder.SetupStandingOrderForLater.class)
);

级联可以使用算法来最小化测试集

这是Cascade的真正瑰宝。 这就是我写整本书的原因。 我们可以使用一种算法来计算每个方案的相对稀有性,并且可以使用该信息来生成最少的旅程测试集,以便每个方案至少包含一次。 更好的是,所使用的算法可以为每种情况计算相对值。 我们可以使用该信息来平衡各个测试中的方案。 测试人员不再需要分析测试集以最小化测试执行时间。 Cascade会自动执行此操作。

级联可以命名您的旅程测试

Cascade将使用测试套件整体中每个步骤的相对稀有性为整个场景生成一个名称,以突出显示场景。

级联可以以不同方式最小化测试

Cascade可以使用略有更改的算法,以不同的方式最小化测试。 不用说,让我们对所有方案进行最少的测试,而可以说,让我们针对所有步骤或所有过渡进行测试。 每个不同的集合以不同的方式完成,并生成不同数量的测试。 这最适用于烟雾测试等。

级联可以关注覆盖范围

Cascade可以计算出该测试套件在整个测试套件中的覆盖范围。 但是,这不是传统的代码覆盖率报告。

那我们输了什么呢?

我们正在失去自己明确定义测试脚本的直接性,而将其替换为一个告诉我们脚本将是什么的系统。 这是否太高要付出的代价将在很大程度上取决于Cascade在实践中如何易于理解和适应。 最初,Cascade所做的大部分工作似乎都是魔术或伏都教,但Cascade生成的测试报告所做的工作并不能减轻这个问题。 测试报告以图形方式对状态机进行建模。 因此,它们提供了一种查看和分析您的系统的方式,这在我之前是不可能的。

因此,让我们总结一下

  • 随着我们拥有更大的团队,拥有更多的流程以及采用微服务架构,测试系统的问题变得越来越难。
  • 向前发展,测试问题根本不同。 我们在单元测试环境中测试特定功能点的能力较弱。
  • 昂贵的网络电话使我们受了诅咒。 除非您使技术堆栈同质化并抽象出网络调用,否则我们将无法逃脱此问题。
  • 我们需要新的工具来处理这些状态机。
  • 状态机本质上是美丽的。 我们应该利用它。

翻译自: https://www.infoq.com/articles/Cascade-Automated-Journey-Testing/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

客户旅程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值