Contract testing for teams that want to move fast

A presentation at WebExpo Prague 2026 in May 2026 in Prague, Czechia by Robin Pokorny

Slide 1

Slide 1

Welcome! I’ve provided these notes in place of my actual speech. While reading isn’t quite the same experience as being in the room together, these summaries will give you the necessary context to help you understand the core ideas behind each slide.

Slide 2

Slide 2

Design like no-one is watching Mike Kus Before we dive into contract testing, you might notice the visuals look different. I deliberately skipped AI image generators and hand-drew everything. It’s a reflection of the personal care and effort I wanted to bring to this topic and to share directly with you. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 2 / 49

Slide 3

Slide 3

I was asked to Some time ago, my company asked me to rethink our testing strategy. We needed fewer bugs and more confidence in our deployments. Reading up on the topic led me to contract testing, a concept that completely changed my approach, and today we’ll walk through it from the problem upward. I was never a QA person — I believe you build it, you own it — so I wanted fresh eyes on the problem. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 3 / 49

Slide 4

Slide 4

fewer, slower, more confidence (sometimes) moderate, mid-cost, mid-confidence many, fast, cheap Most of us are familiar with the standard testing pyramid: lots of fast, cheap unit tests at the base, and a few slow, expensive end-to-end tests at the top. The problem is that the “confidence” supposedly gained at the top is often a lie; flaky end-to-end tests only prove that the test ran, not that the system actually works. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 4 / 49

Slide 5

Slide 5

Fewer end-to-end tests. More integration tests. Earlier. The industry consensus is to “shift left”—testing earlier in the pipeline. We know we need more integration tests and fewer brittle end-toend ones. However, this consensus falls apart in a microservice architecture when the “next service over” is owned by an entirely different team. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 5 / 49

Slide 6

Slide 6

In tutorials, integration testing looks simple: just spin up the other service in a local container and run your tests against it. But in the real world, you don’t control the other service. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 6 / 49

Slide 7

Slide 7

I don’t know how to run it. I’d have to configure it. I’d have to run its dependencies. And theirs. I’d need to know details that should be a black box. Testing against a real external service is a nightmare. You don’t know how to run or configure it, and you’d have to mock all of its downstream dependencies recursively. Trying to test this way forces you to understand implementation details that should remain a black box, violating the very encapsulation microservices are supposed to provide. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 7 / 49

Slide 8

Slide 8

Architecture Styles Worksheet System/Project: Architect/Team: Date: Selected Architecture(s): 0 7 / 4 4 V2.0 Created by Mark Richards, DeveloperToArchitect.com When we chose a distributed, service-based architecture, we bought team autonomy and independent deployability. The steep price we paid for those benefits was testability. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 8 / 49

Slide 9

Slide 9

First Law of Software Architecture “Everything in software architecture is a trade-off.” The integration testing pain we feel isn’t a bug; it’s the cost of our architectural choices. Our goal today isn’t to magically eliminate this cost, but to learn how to pay it intelligently. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 9 / 49

Slide 10

Slide 10

What teams actually do. To get around the pain of spinning up real services, teams use mocks. They write a fake version of the provider service that returns what they need. But the fatal flaw in the industry is treating these mocks as disposable scaffolding instead of recognizing them as actual statements of expectations. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 10 / 49

Slide 11

Slide 11

The mock is the contract. Here is the core pivot: If we stop treating the mock as a private fiction and instead share it with the provider team, it becomes a shared verification fixture. The mock transforms into a formal, written agreement of exactly what the consumer needs. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 11 / 49

Slide 12

Slide 12

Contract testing involves three operational steps: 1. The consumer writes a test that outputs a contract file. 2. This file is shared across the team boundary. 3. The provider replays that file against their real service to prove they can fulfill the expectations. The mock is now a load-bearing part of both pipelines. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 12 / 49

Slide 13

Slide 13

Contract testing is the discipline. Pact is one implementation. Open-source Spec & Tools The de-facto standard Supports 12+ languages pact.io Contract testing is the overall strategic discipline. “Pact” is simply the most prevalent open-source tool used to implement it. Even if you walk away and use a different tool like Spring Cloud Contract, the fundamental concepts remain the same. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 13 / 49

Slide 14

Slide 14

Pact the tooling and library ecosystem a pact a single file produced by the library a pact or contract, an agreement between humans To clarify our terms: Capital-P “Pact” refers to the tooling ecosystem (the libraries, broker, and CLI). A lowercase “pact” is the actual JSON contract file. And the concept itself is a social agreement between teams. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 14 / 49

Slide 15

Slide 15

A pact is an agreement between humans that a machine can enforce. “A pact is an agreement between humans that a machine can enforce.” The pact file is just the written agreement; the CI/CD tooling is the machine that ensures the agreement actually holds true in production. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 15 / 49

Slide 16

Slide 16

Let’s ground this with an example. “Checkout” is our consumer service that needs product data. “Catalog” is our provider service that owns the data. They are managed by two different teams, in different repositories, with entirely different release cadences. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 16 / 49

Slide 17

Slide 17

GET /products/:id C O N S U M E R P R O V I D E R We name the roles by the direction of the call: checkout calls, so it is the consumer; catalog answers, so it is the provider. (I sometimes slip and say “customer” — the words are too similar, please bear with me.) Everything that follows hangs off one simple request: GET /products/:id . Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 17 / 49

Slide 18

Slide 18

checkout writes down what it needs. import { PactV4, MatchersV3 } from “@pact-foundation/pact” const pact = new PactV4({ consumer: “checkout”, provider: “catalog” }) pact .addInteraction() .given(“a product with id sku-42 is low on stock”) .uponReceiving(“a request for sku-42”) .withRequest(“GET”, “/products/sku-42”) .willRespondWith(200, { … }, { id: MatchersV3.string(“sku-42”), name: MatchersV3.string(“Espresso beans”), price: MatchersV3.decimal(12.50), availability: “LOW_STOCK”, // literal — no matcher }) The consumer (Checkout) writes a test defining exactly what it expects. For fields like “price”, just matching the data type is enough. But for “availability”, it specifically needs the literal string “LOW_STOCK” to trigger a UI banner. The choice of matcher is the contract. You write this mock anyway — and today AI happily writes it with you. If you need buy-in, “it’s good for AI” works wonders. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 18 / 49

Slide 19

Slide 19

The pact file. Just JSON. { “consumer”: { “name”: “checkout” }, “provider”: { “name”: “catalog” }, “interactions”: [{ “providerStates”: [{ “name”: “a product with id sku-42 is low on stock” }], “request”: { “method”: “GET”, “path”: “/products/sku-42” }, “response”: { “status”: 200, “body”: { “id”: “sku-42”, “name”: “Espresso beans”, “price”: 12.50, “availability”: “LOW_STOCK” }, “matchingRules”: { “body”: { “$.id”: { “match”: “type” }, “$.name”: { “match”: “type” }, “$.price”: { “match”: “decimal” } }} } }] } The consumer test produces a JSON file. The response body inside is just an example payload, but the matchingRules block is the actual binding contract. Because there is no matcher rule for “availability”, the provider is forced to return the exact string “LOW_STOCK”. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 19 / 49

Slide 20

Slide 20

catalog proves it can deliver. import { Verifier } from “@pact-foundation/pact” await new Verifier({ provider: “catalog”, providerBaseUrl: “http://localhost:3001”, pactBrokerUrl: process.env.PACT_BROKER_URL, consumerVersionSelectors: [{ deployedOrReleased: true }], publishVerificationResult: true, // state handlers answer the consumer’s given(...) stateHandlers: { “a product with id sku-42 is low on stock”: async () => { await seedProduct({ id: “sku-42”, name: “Espresso beans”, price: 12.50, stock: 3 }) }, }, }).verify() On the provider side (Catalog), a Verifier reads the pact, sets up the required precondition (like seeding a low-stock product in the database), and tests the real service to ensure it responds in a way that perfectly satisfies the consumer’s matching rules. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 20 / 49

Slide 21

Slide 21

$ pnpm verify Verifying a pact between checkout and catalog a request for sku-42 Given a product with id sku-42 is low on stock returns a response which has status code 200 ✓ has a matching body ✓ PASS 1 of 1 interactions verified. ─── catalog’s getAvailability is “cleaned up” ─── $ pnpm verify has a matching body ✗ $.availability expected “LOW_STOCK”, got “IN_STOCK” FAIL 1 of 1 interactions failed. Imagine a developer refactors the Catalog service and accidentally breaks the logic, making it silently return “IN_STOCK” instead of “LOW_STOCK”. Unit tests and API schema validations might still pass, but the contract test will fail immediately, catching the bug before it hits production. And it is just a command run in your own repository — no other service needs to be running. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 21 / 49

Slide 22

Slide 22

[“IN_STOCK”, “LOW_STOCK”, “OUT_OF_STOCK”, “BACKORDER”, “DISCONTINUED”] expected “LOW_STOCK” got “IN_STOCK” A schema describes your entire API surface—what is possible. A contract describes only the specific sliver that a consumer actually depends on. A schema can’t catch the bug we just saw because “IN_STOCK” is technically a valid shape; only the consumer’s contract knows it’s semantically wrong. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 22 / 49

Slide 23

Slide 23

A schema describes the whole surface. A contract describes the part you depend on. catalog’s schema what checkout uses A schema is the provider’s view: here is everything I offer, come and use it. A contract is the consumer’s view: this is the part I actually depend on. Almost by definition that is a smaller surface — even several consumers together will rarely cover the whole schema. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 23 / 49

Slide 24

Slide 24

Consumer-driven contract testing This is why it is called consumer-driven contract testing, and the driving is the main switch. It is not the provider declaring what it offers; it is each consumer stating exactly what it needs. Whatever no consumer claims, you can refactor freely — you don’t have to ask, you already know. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 24 / 49

Slide 25

Slide 25

For public APIs with unknown clients, schemas and strict versioning are the right tools. But for internal, private APIs, treating the schema as the contract leads to over-specification and paralyzes deployments. Contract tests tell you exactly what your known internal consumers need so you can refactor everything else freely. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 25 / 49

Slide 26

Slide 26

Telemetry tells you what happened. A contract states what was agreed. the-guild.dev/graphql/hive/ Telemetry and observability tools tell you what consumers happened to do recently—it’s just a measurement. A contract is prescriptive—it tells you what consumers actively agreed to require. Telemetry watches, but contracts enforce promises. (GraphQL Hive even offers “conditional breaking changes”: below 2.3% of traffic counts as non-breaking. Let that sink in.) Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 26 / 49

Slide 27

Slide 27

Partnership succeed or fail together Customer–Supplier downstream priorities factor into upstream planning Shared Kernel two or more teams share a component Conformist upstream has no motivation to provide for the downstream’s needs In Domain-Driven Design, a Context Map defines the relationships between teams (like Customer/Supplier). With contract testing, this relationship doesn’t just live statically on a wiki page—it becomes an executable test that runs in your CI pipeline every time you ship. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 27 / 49

Slide 28

Slide 28

Pact is a Context Map made executable. How well this works follows the relationship type: in a partnership both sides are motivated; a supplier usually welcomes explicit expectations; a shared kernel makes it a no-brainer; even with a conformist you can keep an explicit contract and compare it against their schema. The Context Map stops being a static wiki page and becomes something enforced in CI. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 28 / 49

Slide 29

Slide 29

Contract files sitting in a repo are useless on their own. The Broker is a central service that holds the pact files, verification results, and deployment records. It is the centralized “machine” that enforces the human agreements. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 29 / 49

Slide 30

Slide 30

So how does the pact file actually cross the team boundary? Send it by email? No. It travels through a dedicated service — a shared place both pipelines can reach. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 30 / 49

Slide 31

Slide 31

The full loop: checkout’s test produces the pact file and uploads it to the broker; catalog fetches it, verifies against the real service, and reports the result back. Both sides only ever talk to the broker, never to each other’s pipelines. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 31 / 49

Slide 32

Slide 32

A broker holds the pact files, the verifications, and the deployments. The agreement only enforces if the machine can find it. It is one instance, hosted by you, holding the pact files, the verification results, and the deployment records. An agreement only enforces if the machine can find it — the broker is where the machine finds it. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 32 / 49

Slide 33

Slide 33

What the broker actually stores. Pacticipants every service the broker knows about. Pacticipant versions every git SHA you’ve published. Branches and tags (main, release-v3, feature-foo) labels on versions. Environments (dev, staging, production) distinct from branches. Verification results every [consumer ver, provider ver] pair has pass / fail / pending. The broker tracks five crucial things: the services (pacticipants), their specific git SHA versions, branch/tag labels, environments (like production or staging), and the pass/fail verification results for every single consumer-provider pair. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 33 / 49

Slide 34

Slide 34

The compatibility matrix. catalog v12 catalog v13 catalog v14 checkout v5 (prod) ✓ ✓ … checkout v4 ✓ ✓ ✗ pricing v22 ✓ ✓ ✓ profile v3 ✓ … ✓ The data in the broker forms a real-time compatibility matrix. It shows exactly which versions of the consumer are verified to work with which versions of the provider. Every time a test runs, a cell in this matrix updates. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 34 / 49

Slide 35

Slide 35

Bonus: a service map, generated. Context Map checkout inventory pricing catalog shipping profile Because every contract explicitly states who is consuming what, the broker can automatically generate a highly accurate service dependency graph without relying on potentially incomplete network traffic analysis. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 35 / 49

Slide 36

Slide 36

Can I deploy this version of catalog against the currently-deployed versions of checkout and friends? The core operational question the broker answers is: “Can I deploy this version of my service against what is currently running in production?” It checks the matrix, and if every active consumer is green, it gives the go-ahead. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 36 / 49

Slide 37

Slide 37

Ship or hold? 1. catalog adds an optional new field nobody asked for. 2. catalog renames price to unitPrice. 3. catalog deprecates price (6-month migration window).

  1. Provider adds an optional new field? Ship. No consumer contract depends on it yet. 2. Provider renames an existing field? Hold. The contract will fail, catching the break. 3. Provider deprecates a field? Depends. If an old version of the consumer is still in production using it, the broker blocks the deployment until that consumer is safely retired. Note: contracts have no notion of a six-month migration window — the agreement between humans is always bigger than what the machine can enforce. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 37 / 49

Slide 38

Slide 38

How it doesn’t block you on day one. WIP pacts New consumer expectations don’t fail provider builds yet. Pending pacts Failures don’t block until the consumer has been verified at least once. Adopting this doesn’t mean halting production. You can use “Work In Progress (WIP)” pacts for new expectations, or “Pending” pacts that allow verification failures without blocking the provider’s pipeline until both teams are ready. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 38 / 49

Slide 39

Slide 39

checkout’s CI build → test → publish-pact → can-i-deploy → deploy → test → verify → can-i-deploy → deploy catalog’s CI build The consumer and provider have entirely separate CI pipelines. They build, test, and deploy on their own independent schedules. The only shared point of truth is the broker sitting in the middle. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 39 / 49

Slide 40

Slide 40

Consumer pipeline (checkout). # 1. Run the contract test - run: pnpm test:contract # 2. Publish the pact, tagged with branch + SHA - run: | pact-broker publish pacts \ —consumer-app-version=$GITHUB_SHA \ —branch=$GITHUB_REF_NAME # 3. Ask the broker before deploying - run: | pact-broker can-i-deploy \ —pacticipant=checkout \ —version=$GITHUB_SHA \ —to-environment=production The consumer’s CI runs three straightforward steps: 1) Run the contract test alongside normal unit tests, 2) Publish the generated pact file to the broker, and 3) Ask the broker can-i-deploy before pushing to production. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 40 / 49

Slide 41

Slide 41

Provider pipeline (catalog). # 1. Verify against every pact that matters - run: pnpm test:verify env: PACT_BROKER_URL: ${{ secrets.PACT_BROKER_URL }} # uses consumerVersionSelectors: deployedOrReleased # and publishVerificationResult: true # 2. Ask the broker before deploying - run: | pact-broker can-i-deploy \ —pacticipant=catalog \ —version=$GITHUB_SHA \ —to-environment=production The provider does the inverse: 1) Pull the relevant pacts from the broker and run the verification test against the real service, publishing the result back. 2) Ask can-i-deploy before shipping. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 41 / 49

Slide 42

Slide 42

After a successful deploy, tell the broker. - run: | pact-broker record-deployment \ —pacticipant=catalog \ —version=$GITHUB_SHA \ —environment=production Without this, can-i-deploy doesn’t know which versions are live. This is the most easily missed step: after a successful deployment, you must tell the broker via the record-deployment command. If the broker doesn’t know which version is actually live in production, it can’t accurately answer the can-i-deploy question for the next release. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 42 / 49

Slide 43

Slide 43

can-i-deploy: green. $ pact-broker can-i-deploy \ —pacticipant catalog \ —version 7c3f2a1 \ —to-environment production Computer says yes \o/ CONSUMER | C.VERSION | P.VERSION | SUCCESS? ——————-|—————-|—————-|————-checkout | a8d1f9 | 7c3f2a1 | true pricing | b2e4c7 | 7c3f2a1 | true profile | d6a2b8 | 7c3f2a1 | true When everything aligns, the broker returns “Computer says yes.” All consumers verify successfully against your version, meaning you are free to deploy immediately without needing to coordinate a release meeting. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 43 / 49

Slide 44

Slide 44

can-i-deploy: red. Computer says no ¯_(ツ)_/¯ CONSUMER | C.VERSION | P.VERSION | SUCCESS? ——————-|—————-|—————-|————-checkout | a8d1f9 | 7c3f2a1 | FAILED pricing | b2e4c7 | 7c3f2a1 | true profile | d6a2b8 | 7c3f2a1 | true The verification for checkout v.a8d1f9 against catalog v.7c3f2a1 failed because: $.availability — expected “LOW_STOCK”, got “IN_STOCK” The agreement only enforces if the machine is allowed to say no. When there’s a mismatch, the broker blocks the deployment. For this to work, teams must be willing to let the tool say “no” and block them. If you override it because you’re in a hurry, you no longer have contract testing—you just have an opinion. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 44 / 49

Slide 45

Slide 45

checkout’s CI build → test → publish-pact → can-i-deploy → deploy → test → verify → can-i-deploy → deploy catalog’s CI build A quick recap of the loop: (1) the consumer uploads the contract; (2) the provider downloads, verifies, and uploads the result. After that the two sides are independent — steps 4a and 4b don’t wait on each other. Each pipeline asks can-i-deploy against the target environment and either ships or stops. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 45 / 49

Slide 46

Slide 46

What this buys you. Independent deployability. Honest rollback. A working compatibility matrix. This process buys you three massive benefits: Independent deployability, honest rollbacks (you know instantly if reverting is safe), and a real-time compatibility matrix. You stop relying on hope and start relying on verifiable checks. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 46 / 49

Slide 47

Slide 47

Setup CI plumbing, broker, state handlers, deploy-recording. Diligence A pact only helps if both sides care. Buy-in Lack of it across teams kills this faster than anything technical. Over-specification Match on what you depend on. Nothing more. Contract testing isn’t free. You pay for it in setup time (CI plumbing, the broker), diligence (maintaining state handlers), and organizational buy-in. You must also avoid over-specification, or you risk making your architecture even more brittle. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 47 / 49

Slide 48

Slide 48

Both pipelines publish. Both pipelines verify. Both pipelines record. “Pact Nirvana” is the ultimate state: both pipelines are publishing, verifying, and recording deployments on every single commit. It is the only way teams can truly stop coordinating releases and ship independently. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 48 / 49

Slide 49

Slide 49

Socio-technical software architect · Ataccama me@robinpokorny.com Q & A in the speaker corner Contract testing is less of a testing framework and more of a deployment discipline. Ultimately, the contract is the conversation between your teams—the test simply makes it stick. Contract testing for teams that want to move fast · webexpo · prague · 2026-05-28 · robin pokorny · robinpokorny.com 49 / 49