测试金字塔

测试金字塔”是一个隐喻,它告诉我们将软件测试分成不同粒度的桶。它也给出了我们应该在这些组中进行多少次测试的想法。尽管测试金字塔的概念已经存在了一段时间,但团队仍然很难正确实施。本文重新探讨了Test Pyramid的原始概念,并展示了如何将其付诸实践。它显示了你应该在金字塔的不同层次上寻找哪种类型的测试,并给出了如何实现这些测试的实例。

目录

测试自动化的重要性

软件已成为我们生活的世界的重要组成部分。它早已超出了提高企业效率的唯一目的。今天,公司试图想方设法成为一流的数字公司。随着我们每个人的用户每天都会与越来越多的软件进行交互。创新的车轮转得更快。

如果你想跟上步伐,你必须研究如何在不牺牲质量的情况下更快地交付你的软件。持续交付是一种自动确保软件可以随时发布到生产环境的实践,可以帮助您实现这一目标。通过持续交付,您可以使用构建管道自动测试您的软件并将其部署到您的测试和生产环境中。

手动构建,测试和部署不断增加的软件数量很快就变得不可能了 - 除非您希望将所有时间花费在手动,重复性工作而不是提供工作软件上。自动化所有内容 - 从构建到测试,部署和基础架构 - 是您唯一的前进方向。

image

传统上,软件测试过于手工,通过将应用程序部署到测试环境,然后执行一些黑盒测试,例如通过点击用户界面来查看是否有任何问题。这些测试通常由测试脚本指定,以确保测试人员能够进行一致性检查。

很明显,手动测试所有更改非常耗时,重复且乏味。重复是无聊的,无聊会导致错误,并使你在本周末之前寻找不同的工作。

幸运的是,对于重复性任务有一种补救措施:自动化。

自动化重复性测试可以成为软件开发人员生活中的重大改变。使这些测试自动化,您不再需要盲目地遵循点击协议来检查您的软件是否仍能正常工作。自动化你的测试,你可以改变你的代码库而不用打眼球。如果你曾经尝试过在没有适当的测试套件的情况下进行大规模的重构,我敢打赌你知道这会是一次可怕的经历。你怎么知道你是否意外地在路上摔坏了东西?那么,你点击你所有的手动测试用例,就是这样。但说实话:你真的喜欢这个吗?如何做大规模的变化,并知道你是否在几秒钟内摔坏了东西,同时喝了一口咖啡?如果你问我,听起来会更愉快。

测试金字塔

如果您想认真对待软件的自动化测试,您应该了解一个关键概念:测试金字塔。迈克·科恩在他的着作“ 与敏捷成功”一书中提出了这个概念。这是一个伟大的视觉隐喻,告诉你思考不同层次的测试。它还会告诉你在每个图层上要做多少测试。

image


image

原始测试金字塔由您的测试套件应包含的三个层组成(从下到上):

单元测试
服务测试
用户界面测试
不幸的是,如果仔细观察,测试金字塔的概念会有点短。有人认为,麦克科恩的测试金字塔的命名或某些概念方面并不理想,我必须同意。从现代的角度来看,测试金字塔似乎过于简单化,因此可能会产生误导。

尽管如此,由于它的简单性,当建立自己的测试套件时,测试金字塔的本质是一个很好的经验法则。你最好的选择是记住Cohn最初的测试金字塔中的两件事:

用不同的粒度编写测试
更高层次的你可以获得更少的测试
坚持金字塔形状,以提出一个健康,快速和可维护的测试套件:写许多小而快的单元测试。写一些更粗粒度的测试和很少 的高级测试,从头到尾测试你的应用程序。注意,你最终不会得到一个 测试冰淇淋锥,这将是一个噩梦来维持,并且运行时间太长。

不要太拘泥于科恩测试金字塔中单个图层的名称。事实上,它们可能会引起误解:服务测试是一个难以理解的术语(科恩本人谈论的观察结果 是许多开发人员完全忽略了这一层)。在诸如react,angular,ember.js和其他单页应用程序框架的日子里,UI测试显然不必位于金字塔的最高层 - 您完全可以单元测试您的UI这些框架。

考虑到原始名称的缺点,只要在代码库和团队的讨论中保持一致,就可以为测试图层提供其他名称。

Tools, Framework和 Library

  • JUnit:我们的测试赛跑者
  • Mockito:嘲笑依赖关系
  • Wiremock:用于剔除外部服务
  • Pact:用于编写CDC测试(消费者驱动契约)
  • Selenium:用于编写UI驱动的端到端测试
  • REST-assured:用于编写REST API驱动的端到端测试

示例应用程序

我已经写了一个简单的微服务,包括一个测试套件,其中包含测试金字塔不同层次的测试。

示例应用程序显示了典型的微服务的特征。它提供了一个REST接口,与数据库交谈并从第三方REST服务获取信息。它在Spring Boot中实现,即使您以前从未使用过
SpringBoot ,也应该可以理解。

请务必查看Github上的代码。自述文件包含您在计算机上运行应用程序及其自动化测试所需的说明。

功能

该应用程序的功能很简单。它提供了一个具有三个端点的REST接口:

  • GET / hello 返回“Hello World”。总是。
  • GET / hello / {lastname} 用提供的姓氏查找该人。如果此人是已知的,则返回“Hello {姓氏} {姓氏}”。
  • GET /天气 返回德国汉堡当前的天气情况。

高层次结构

在高层次上,系统具有以下结构:

image

我们的微服务提供了一个可以通过HTTP调用的REST接口。对于某些端点,服务将从数据库获取信息。在其他情况下,该服务将通过HTTP 调用外部天气API来获取并显示当前天气状况。

内部架构

在内部,Spring服务有一个spring典型的体系结构:

image

  • Controller类提供REST端点并处理HTTP 请求和响应
  • Repository类与数据库接口,并负责将数据写入永久存储器和从永久存储器读取数据
  • Client类与其他API交谈,在我们的例子中,它 通过darksky.net weather API的HTTPS获取JSON
  • Domain类捕捉我们的领域模型,包括领域逻辑(在我们的例子中,这是公平的,相当微不足道)。

有经验的Spring开发人员可能注意到这里经常使用的图层缺失:受Domain-Driven Design的启发,很多开发人员构建了一个由服务类组成的 服务层。我决定不在此应用程序中包含服务层。其中一个原因是我们的应用程序很简单,服务层本来就是不必要的间接层。另一个是我认为人们过度使用服务层。我经常遇到在服务类中捕获整个业务逻辑的代码库。领域模型仅仅成为一个数据层,而不是行为(一个 贫血域模型)。对于每一个非平凡的应用程序来说,这会浪费很多潜能来保持代码的结构良好和可测试性,并且不能充分利用面向对象的功能。

我们的存储库非常简单,并提供简单的 CRUD 功能。为了简化代码,我使用了Spring Data。Spring Data为我们提供了一个简单而通用的CRUD存储库实现,我们可以使用它来代替滚动我们自己的实现。它还负责为我们的测试启动内存数据库,而不是像生产中那样使用真正的PostgreSQL数据库。

看看代码库,让自己熟悉内部结构。这对我们的下一步将是有用的:测试应用程序!

单元测试

测试套件的基础将由单元测试组成。您的单元测试确保您的代码库的某个单元(您的受测主题)按预期工作。单元测试具有测试套件中所有测试的最窄范围。测试套件中的单元测试数量将远远超过任何其他类型的测试。

image

什么是单位?

如果你问三个不同的人在单元测试中的“单位”是什么意思,你可能会收到四个不同的,稍微微妙的答案。在一定程度上,这是一个你自己定义的问题,可以没有规范的答案。

如果你使用的是功能语言,一个单元很可能是一个单一的功能。您的单元测试将调用具有不同参数的函数,并确保它返回期望值。在面向对象的语言中,单元可以从单一方法到整个类。

Mock 和 Stub

Mocks 和 Stubs是两种不同类型的 测试(不止这两种)。许多人可以互换地使用术语Mock和Stub。我认为精确并保持其特定属性是件好事。您可以使用测试双打将您在生产中使用的对象替换为帮助您进行测试的实施。

用简单的话来说,这意味着你用一个假的版本替换了一个真实的东西(例如一个类,模块或函数)。假的版本看起来和行为像真实的东西(回答相同的方法调用),但回答您在单元测试开始时自己定义的预设回应。

使用测试双打并不特定于单元测试。更精细的测试双打可用于以受控方式模拟系统的整个部分。然而,在单元测试中,你很可能会遇到很多模拟和存根(取决于你是否是社交或孤独的开发人员),只是因为很多现代语言和库让设置变得简单和舒适嘲笑和存根(stub)。

无论您选择哪种技术,很可能您的语言标准库或一些流行的第三方库会为您提供优化的安装模拟方法。甚至从头开始编写你自己的模拟只是写一个假的类/模块/功能与真实的相同的签名,并在你的测试中设置假。

你的单元测试运行速度非常快。在一台体面的机器上,你可以在几分钟内完成数千个单元测试。单独测试小部分代码库,避免触击数据库,文件系统或触发HTTP查询(通过使用这些部分的模拟和存根)来保持测试的快速。

一旦你掌握了编写单元测试的一窍不通,你将会越来越流利地编写它们。剔除外部协作者,设置一些输入数据,调用测试主题并检查返回的值是否与您的预期相符。看看测试驱动开发,让你的单元测试指导你的开发; 如果正确应用,它可以帮助您进入一个良好的流程,并提出良好的可维护设计,同时自动生成全面的全自动测试套件。尽管如此,这不是银弹。继续,给它一个真正的机会,看看它是否适合你。

要测试什么?

单元测试的好处在于,您可以为所有生产代码类编写它们,而不管它们的功能或内部结构中属于哪个层。您可以像测试存储库,域类或文件读取器一样单元测试控制器。只需坚持每个生产级经验法则的一个测试课程,您就有了一个良好的开端。

单元测试类应该至少测试该类的公共接口。私有方法无法进行测试,因为你无法从不同的测试类中调用它们。受保护的或包私有的可以从测试类访问(考虑到测试类的包结构与生产类相同),但测试这些方法可能已经太过分了。

编写单元测试时有一条细线:它们应该确保测试所有不重要的代码路径(包括开心路径和边缘情况)。同时它们不应该与你的实现过于紧密相关。

为什么?

太接近生产代码的测试很快变得令人讨厌。只要您重构生产代码(快速回顾:重构意味着更改代码的内部结构而不更改外部可见行为),您的单元测试将会中断。

这样你就失去了单元测试的一大好处:充当代码变更的安全网。你宁愿厌倦那些每次重构都会失败的愚蠢测试,这会导致更多的工作而不是帮助; 而且他的想法是这个愚蠢的测试东西吗?

测试结构

所有测试的良好结构(这不仅限于单元测试)是这样的:

  • 设置测试数据
  • 在测试中调用你的方法
  • 断言预期的结果被返回

记住这个结构有一个很好的记忆:“安排,行动,坚持”。另一个你可以使用的是来自BDD的灵感。它是“given”、“when”、“then”三个一组,在这里给出了反映设置的情况,当方法调用,然后是断言部分。

这种模式也可以应用于其他更高级别的测试。在任何情况下,他们都可以确保您的测试易于阅读并保持一致。除此之外,考虑到这种结构的测试往往更短,更具表现力。

实现单元测试

现在我们知道要测试什么以及如何构建单元测试,我们终于可以看到一个真实的例子。

我们来看一下这个ExampleController类的简化版本:

@RestController
public class ExampleController {

    private final PersonRepository personRepo;

    @Autowired
    public ExampleController(final PersonRepository personRepo) {
        this.personRepo = personRepo;
    }

    @GetMapping("/hello/{lastName}")
    public String hello(@PathVariable final String lastName) {
        Optional<Person> foundPerson = personRepo.findByLastName(lastName);

        return foundPerson
                .map(person -> String.format("Hello %s %s!",
                        person.getFirstName(),
                        person.getLastName()))
                .orElse(String.format("Who is this '%s' you're talking about?",
                        lastName));
    }
}

该hello(lastname)方法的单元测试可能如下所示:

public class ExampleControllerTest {

    private ExampleController subject;

    @Mock
    private PersonRepository personRepo;

    @Before
    public void setUp() throws Exception {
        initMocks(this);
        subject = new ExampleController(personRepo);
    }

    @Test
    public void shouldReturnFullNameOfAPerson() throws Exception {
        Person peter = new Person("Peter", "Pan");
        given(personRepo.findByLastName("Pan"))
            .willReturn(Optional.of(peter));

        String greeting = subject.hello("Pan");

        assertThat(greeting, is("Hello Peter Pan!"));
    }

    @Test
    public void shouldTellIfPersonIsUnknown() throws Exception {
        given(personRepo.findByLastName(anyString()))
            .willReturn(Optional.empty());

        String greeting = subject.hello("Pan");

        assertThat(greeting, is("Who is this 'Pan' you're talking about?"));
    }
}

我们正在使用JUnit编写单元测试,这是Java事实上的标准测试框架。我们使用Mockito来替换真实的PersonRepository类,并用我们的测试存根。这个存根允许我们定义在这个测试中存根方法应该返回的罐头响应。存根使我们的测试更加简单,可预测,并且使我们能够轻松设置测试数据。

在安排,行动,断言结构之后,我们编写了两个单元测试 - 一个正面的案例和一个被搜查的人无法找到的案例。第一个正面的测试用例创建一个新的人物对象,并告诉模拟库在用“Pan”作为lastName参数值调用时返回该对象。测试然后继续调用应该测试的方法。最后它声称答复等于预期的答复。

第二个测试的工作原理类似,但测试测试方法未找到给定参数的人的场景。

集成测试

所有复杂的应用程序都将与其他部分(数据库,文件系统,对其他应用程序的网络调用)集成在一起。在编写单元测试时,这些通常是您为了提供更好的隔离和更快的测试而遗漏的部分。尽管如此,您的应用程序仍会与其他部分进行交互,并需要进行测试。 集成测试可以帮助您。他们测试应用程序与应用程序之外的所有部分的集成。

对于您的自动化测试,这意味着您不仅需要运行自己的应用程序,还需要运行您正在与之集成的组件。如果您正在测试与数据库的集成,则需要在运行测试时运行数据库。为了测试您可以从磁盘读取文件,您需要将文件保存到磁盘并将其加载到集成测试中。

我之前提到“单元测试”是一个模糊的术语,对于“集成测试”来说更是如此。对于某些人来说,集成测试意味着要测试整个应用程序堆栈与系统中的其他应用程序相连接。我喜欢更狭窄地对待集成测试,并且一次测试一个集成点,通过将测试双打替换为单独的服务和数据库。结合合同测试和对测试双打运行合同测试以及真实实施,您可以提出更快,更独立并且通常更容易推理的集成测试。

狭窄的集成测试活在您服务的边界。从概念上讲,它们始终是触发一种导致与外部部分(文件系统,数据库,单独服务)集成的操作。数据库集成测试看起来像这样:

image

注:数据库集成测试将您的代码与真实数据库集成在一起

  1. 启动一个数据库
  2. 将您的应用程序连接到数据库
  3. 在代码中触发一个将数据写入数据库的函数
  4. 通过读取数据库中的数据来检查预期数据是否写入了数据库

另一个例子,测试你的服务通过REST API与单独的服务集成可能是这样的:
image

注:这种集成测试检查您的应用程序是否可以正确地与单独的服务进行通信

  1. 开始你的申请
  2. 启动单独服务的一个实例(或者具有相同接口的测试double)
  3. 在您的代码中触发一个从独立服务的API中读取的函数
  4. 检查您的应用程序是否可以正确解析响应

你的集成测试 - 比如单元测试 - 可以是相当的白盒。有些框架允许您启动应用程序,同时仍然可以嘲笑应用程序的其他部分,以便检查是否发生了正确的交互。

编写集成测试,用于序列化 或反序列化数据的所有代码段。这种情况发生的频率比你想象的要多。想一想:

  • 调用您的服务的REST API
  • 读取和写入数据库
  • 调用其他应用程序的API
  • 读取和写入队列
  • 写入文件系统

围绕这些边界编写集成测试可确保将数据写入这些外部协作者并从中读取数据可以正常工作。

在编写窄集成测试时,应该着眼于在本地运行外部依赖关系:启动本地MySQL数据库,对本地ext4文件系统进行测试。如果您要与单独的服务集成,请在本地运行该服务的实例,或者构建并运行模仿真实服务行为的假版本。

如果无法在本地运行第三方服务,则应选择运行专用测试实例,并在运行集成测试时指向此测试实例。避免在自动化测试中与实际生产系统集成。将数以千计的测试请求发布到生产系统是一种绝对的方式,让人们生气,因为你的日志混乱(最好的情况下),甚至 DoS 的服务(最坏的情况)。通过网络集成服务是广泛集成测试的典型特征,并且使测试变得更慢,通常更难以编写。

关于测试金字塔,集成测试的级别高于单元测试。集成文件系统和数据库等慢速部件往往比运行单元测试要慢得多,而这些部件都会被删除。毕竟,作为测试的一部分,你必须考虑外部零件的旋转,它们也可能比小而孤立的单元测试更难编写。不过,它们的优势在于让您确信您的应用程序可以正确处理所需的所有外部部件。单元测试无法帮助你。

数据库集成

这PersonRepository是代码库中唯一的存储库类。它依赖于Spring Data,并没有实际的实现。它只是扩展了CrudRepository接口并提供了一个单一的方法头。其余的是春天魔术。

public interface PersonRepository extends CrudRepository <PersonString> { 
    Optional <Person> findByLastName(String lastName); 
}

随着CrudRepository接口春季启动提供了一个功能齐全的CRUD存储库findOne,findAll,save,update和delete 方法。我们的自定义方法定义(findByLastName())扩展了这个基本功能,并为我们提供了一种Person通过姓氏获取s 的方法。Spring Data分析了方法的返回类型及其方法名称,并根据命名约定检查方法名称以找出它应该做什么。

虽然Spring Data负责实现数据库存储库,但我仍然编写了一个数据库集成测试。你可能会争辩说,这是测试框架和我应该避免的,因为它不是我们正在测试的代码。不过,我相信至少有一个集成测试是至关重要的。首先它测试我们的自定义 findByLastName方法实际上按预期行事。其次,它证明我们的存储库正确使用了Spring的接线并可以连接到数据库。

为了让您在机器上运行测试变得容易(无需安装PostgreSQL数据库),我们的测试连接到内存中的 H2数据库。

我已经将H2定义为build.gradle文件中的测试依赖项。在 application.propertiestest目录没有定义任何 spring.datasource属性。这告诉Spring Data使用内存数据库。因为它在类路径上发现H2,所以它在运行我们的测试时仅使用H2。

当使用int配置文件运行真正的应用程序时(例如,通过设置 SPRING_PROFILES_ACTIVE=int为环境变量),它将连接到一个PostgreSQL数据库,如下定义application-int.properties。

我知道,要了解和理解这些Spring细节是非常多的。为了达到目的,你必须筛选大量的文档。由此产生的代码很容易理解,但如果你不了解Spring的细节,就很难理解。

除此之外,使用内存数据库是危险的业务。毕竟,我们的集成测试针对的是不同于生产环境的不同类型的数据库。继续并自行决定是否更喜欢使用Spring魔术和简单的代码,而不是更明确而更详细的实现。

已经有足够的解释了,下面是一个简单的集成测试,它将人员保存到数据库中,并通过姓氏找到它:

@RunWith(SpringRunner.class)
@DataJpaTest
public class PersonRepositoryIntegrationTest {
    @Autowired
    private PersonRepository subject;

    @After
    public void tearDown() throws Exception {
        subject.deleteAll();
    }

    @Test
    public void shouldSaveAndFetchPerson() throws Exception {
        Person peter = new Person("Peter", "Pan");
        subject.save(peter);

        Optional<Person> maybePeter = subject.findByLastName("Pan");

        assertThat(maybePeter, is(Optional.of(peter)));
    }
}

您可以看到,我们的集成测试遵循与单元测试相同的排列,行为和断言结构。告诉你,这是一个普遍的概念!

与独立服务集成

我们的微服务与darksky.net,一个天气REST API 交谈。当然,我们希望确保我们的服务能够正确地发送请求并解析响应。

我们希望在运行自动化测试时避免碰到真正的darksky服务器。我们免费计划的配额限制只是原因的一部分。真正的原因是脱钩。我们的测试应该独立于darksky.net上可爱的人进行。即使您的机器无法访问darksky服务器或darksky服务器因维护而停机。

在运行我们的集成测试时,我们可以通过运行我们自己的虚假darksky服务器来避免碰到真正的darksky服务器。这听起来像是一项艰巨的任务。由于像Wiremock这样的工具, 这很容易。看这个:

@RunWith(SpringRunner.class)
@SpringBootTest
public class WeatherClientIntegrationTest {

    @Autowired
    private WeatherClient subject;

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(8089);

    @Test
    public void shouldCallWeatherService() throws Exception {
        wireMockRule.stubFor(get(urlPathEqualTo("/some-test-api-key/53.5511,9.9937"))
                .willReturn(aResponse()
                        .withBody(FileLoader.read("classpath:weatherApiResponse.json"))
                        .withHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withStatus(200)));

        Optional<WeatherResponse> weatherResponse = subject.fetchWeather();

        Optional<WeatherResponse> expectedResponse = Optional.of(new WeatherResponse("Rain"));
        assertThat(weatherResponse, is(expectedResponse));
    }
}

要使用Wiremock,我们WireMockRule在固定端口(8089)上实例化一个。使用DSL,我们可以设置Wiremock服务器,定义它应该监听的端点,并设置应该响应的预设响应。

接下来我们调用我们想要测试的方法,即调用第三方服务的方法,并检查结果是否正确解析。

了解测试如何知道应该调用假Wiremock服务器而不是真正的darksky API非常重要。秘密在我们的application.properties文件包含在 src/test/resources。这是运行测试时Spring加载的属性文件。在这个文件中,我们覆盖了像API键和URL这样的配置,其值适合我们的测试目的,例如调用假Wiremock服务器而不是真实服务器:

weather.url = http:// localhost:8089

请注意,这里定义的端口必须与我们WireMockRule在测试中实例化时定义的端口相同。通过在我们的WeatherClient类的构造函数中注入URL,可以在我们的测试中用真假API替换真实天气API的URL :

@Autowired
public WeatherClient(final RestTemplate restTemplate,
                     @Value("${weather.url}") final String weatherServiceUrl,
                     @Value("${weather.api_key}") final String weatherServiceApiKey) {
    this.restTemplate = restTemplate;
    this.weatherServiceUrl = weatherServiceUrl;
    this.weatherServiceApiKey = weatherServiceApiKey;
}

这样我们告诉我们从 我们在应用程序属性中定义的属性中WeatherClient读取 weatherUrl参数的值weather.url。

使用Wiremock等工具为单独服务编写窄集成测试非常简单。不幸的是,这种方法有一个缺点:我们如何确保我们设置的假服务器的行为像真正的服务器?在目前的实施中,单独的服务可能会改变其API,我们的测试仍然会通过。现在我们只是测试我们WeatherClient可以解析假服务器发送的响应。这是一个开始,但非常脆弱。使用 端到端测试并且针对真实服务的测试实例而不是使用假服务运行测试将解决此问题,但会使我们依赖于测试服务的可用性。幸运的是,有一个更好的解决方案来解决这个困境:对虚假服务器和真实服务器运行合同测试可确保我们在集成测试中使用的虚假测试是忠实的测试。让我们看看接下来的工作。

契约测试

更多的现代软件开发组织已经找到了通过跨不同团队开发系统来扩展其开发工作的方法。个别团队建立个人化,松散耦合的服务,而不是彼此踩脚趾,并将这些服务整合到一个庞大而有凝聚力的系统中。最近围绕微服务的嗡嗡声正是关注这一点。

将系统分割成许多小型服务常常意味着这些服务需要通过某些(希望定义明确的,有时意外增长的)接口相互通信。

不同应用程序之间的接口可以有不同的形状和技术。常见的是

  • REST和JSON通过HTTPS
  • 使用类似 gRPC的RPC
  • 使用队列构建事件驱动的体系结构

对于每个接口,涉及两方:提供者和消费者。该提供商向消费者提供数据。消费者从提供者得到的数据处理方法。在REST世界中,提供者使用所有必需的端点构建REST API; 消费者调用此REST API来获取数据或触发其他服务中的更改。在异步的,事件驱动的世界中,提供者(通常称为发布者)将数据发布到队列中; 消费者(通常称为 订户)订阅这些队列并读取和处理数据。

image

每个界面都有提供(或发布)和消费(或订阅)方。接口的规范可以被认为是契约

由于您经常在不同团队之间传播消费和提供服务,您会发现自己处于必须明确指定这些服务之间的接口(所谓的合同)的情况。传统上,公司通过以下方式来解决这个问题:

  • 编写一份详细的长期界面规范(合同)
  • 按照定义的合同实施提供服务
  • 将界面规范扔到围栏上给消费团队
  • 等到他们实现他们的消费接口的一部分
  • 运行一些大规模的手动系统测试,看看是否一切正常
  • 希望两个团队永远坚持界面定义,不要搞砸了

更现代化的软件开发团队用更自动化的东西取代了第5步和第6步:自动契约测试 确保消费者和提供者方面的实现仍然坚持已定义的合同。他们作为一个很好的回归测试套件,并确保早期发现与合同的偏差。

在一个更敏捷的组织中,你应该采取更有效和更少浪费的路线。您在同一个组织内构建您的应用程序。直接与其他服务的开发人员直接交谈,而不是过分摒弃过于详细的文档,这不应该太难。毕竟他们是你的同事,而不是第三方供应商,你只能通过客户支持或合法的防弹合同进行交谈。

消费者驱动契约测试(CDC测试)让消费者推动合同的实施。使用CDC,接口的使用者编写测试,从接口检查接口所需的所有数据。然后消费团队发布这些测试,以便发布团队可以轻松获取并执行这些测试。提供团队现在可以通过运行CDC测试来开发他们的API。一旦所有测试通过,他们知道他们已经实施了消费团队所需的一切。

image

契约测试确保接口的提供者和所有消费者都坚持定义的接口契约。通过CDC测试,接口的消费者以自动化测试的形式发布他们的需求; 提供者不断地获取并执行这些测试

这种方法允许提供团队只实施真正必要的事情(保持简单, YAGNI 等等)。提供界面的团队应持续(在他们的构建流水线中)获取并运行这些CDC测试,以立即发现任何重大更改。如果他们打破界面,他们的CDC测试将会失败,从而防止重大更改上线。只要测试保持绿色,团队可以进行他们喜欢的任何更改,而不必担心其他团队。消费者驱动的合同方法会给你带来一个看起来像这样的过程:

  • 消费团队编写符合所有消费者期望的自动化测试
  • 他们为提供团队发布测试
  • 提供团队持续运行CDC测试并保持绿色
  • 一旦CDC测试中断,两个团队都会互相交流

如果您的组织采用微服务方法,进行CDC测试是建立自治团队的重要一步。CDC测试是促进团队沟通的自动化方式。他们确保团队之间的界面随时都在工作。CDC测试失败是一个很好的指标,你应该走到受影响的团队,聊聊任何即将到来的API变化,并找出你想如何前进。

一个天真的CDC测试实现可以像对API发起请求一样简单,并声明响应包含你需要的所有东西。然后将这些测试打包为可执行文件(.gem,.jar,.sh)并将其上传到其他团队可以获取的地方(例如Artifactory等工件存储库 )。

在过去的几年中,CDC方法变得越来越流行,并且已经构建了几种工具来简化书写和交换工作。

契约可能是最近最突出的一个。它具有为消费者和提供商编写测试的复杂方法,可为您提供开箱即用的独立服务存根,并允许您与其他团队交换CDC测试。Pact已经被移植到很多平台上,并且可以与JVM语言,Ruby,.NET,JavaScript等一起使用。

如果您想开始使用CDC并且不知道如何,Pact可以是一个理智的选择。该文件可以在第一是压倒性的。保持耐心,并努力通过它。它有助于深入了解疾病预防控制中心,从而使您在与其他团队合作时更容易倡导使用疾病预防控制中心。

消费者驱动的合同测试可以成为一个真正的游戏规则改变者,以建立自信的团队,可以快速而自信地行动。帮你一个忙,阅读这个概念并试一试。一套可靠的CDC测试对于能够快速移动而不会破坏其他服务并对其他团队造成很大的挫折是无价的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值