Die Ziele dieser Veröffentlichung sind :
- Eine kurze Einführung in verbraucherorientierte Verträge (CDC)
- Konfigurieren Sie die CDI-basierte CI-Pipeline
Verbrauchergesteuerte Verträge
In diesem Teil werden wir die Hauptpunkte der CDC behandeln. Dieser Artikel erhebt keinen Anspruch auf Vollständigkeit zum Thema Vertragstests. Es gibt eine ausreichende Menge an Materialien zu diesem Thema auf demselben Habré .
Um fortzufahren, müssen wir uns mit den wichtigsten Bestimmungen der CDC vertraut machen:
- Die Kontakttests befinden sich auf der Ebene der Service- / Integrationstests über den Unit-Tests gemäß der Mike Cohn- Pyramide.
- Vertragstests können angewendet werden, wenn zwei (oder mehr) Dienste miteinander interagieren.
- Verbraucherorientierter Ansatz bedeutet, dass der erste Schritt bei der Implementierung darin besteht, einen Test auf der Verbraucherseite zu schreiben. Das Testergebnis ist ein Pakt (Vertrag) im JSON-Format, der die Interaktion zwischen dem Verbraucher (z. B. Webschnittstelle / mobile Schnittstelle: ein Dienst, der einige Daten empfangen möchte) und dem Anbieter (z. B. Server-API: ein Dienst, der Daten bereitstellt) beschreibt.
- Der nächste Schritt besteht darin, den Vertrag mit dem Anbieter zu überprüfen. Dies wird vom Pakt- Rahmenwerk vollständig umgesetzt.
Beginnen wir also mit dem Verbraucherseitentest . Ich habe Pactman benutzt . So sieht der Test aus:
import pytest from pactman import Like from model.client import Client @pytest.fixture() def consumer(pact): return Client(pact.uri) def test_app(pact, consumer): expected = '123456789' (pact .given('provider in some state') .upon_receiving("request to get user's phone number") .with_request( method='GET', path=f'/phone/john', ) .will_respond_with(200, body=Like(expected)) .given('provider in some state') .upon_receiving("request to get non-existent user's phone number") .with_request( method='GET', path=f'/phone/micky' ) .will_respond_with(404) ) with pact: consumer.get_users_phone(user='john', host=pact.uri) consumer.get_users_phone(user='micky', host=pact.uri)
Mit Pact DSL beschreiben wir Request / Response-Interaktionen. Nach dem Start des Tests erhalten wir eine neue Datei ({consumer} - {provider} -pact.json):
{ "consumer": { "name": 'basic_client' }, "provider": { "name": 'basic_flask_app' }, "interactions": [ { "providerStates": [ { "name": "provider in some state", "params": {} } ], "description": "request to get user's phone number", "request": { "method": "GET", "path": "/phone/john" }, "response": { "status": 200, "body": "123456789", "matchingRules": { "body": { "$": { "matchers": [ { "match": "type" } ] } } } } }, { "providerStates": [ { "name": "provider in some state", "params": {} } ], "description": "request to get non-existent user's phone number", "request": { "method": "GET", "path": "/phone/micky" }, "response": { "status": 404 } } ], "metadata": { "pactSpecification": { "version": "3.0.0" } } }
Als nächstes müssen wir den Pakt zur Überprüfung an den Anbieter weitergeben. Dies geschieht mit Pact Broker.
Pact Broker ist ein Vertrags-Repository mit einigen zusätzlichen Funktionen, mit denen wir die Kompatibilität von Serviceversionen verfolgen und Netzwerkdiagramme erstellen können (Service-Interaktion).
Paktmakler

Der Pakt

Versionsmatrix

Anbieterüberprüfung
Dieser Teil des Tests wird vollständig vom Framework durchgeführt. Nach der Überprüfung werden die Ergebnisse an Pact Broker zurückgesendet.
provider-verifier_1 | Verifying a pact between basic_client and basic_flask_app provider-verifier_1 | Given provider in some state provider-verifier_1 | request to get user's phone number provider-verifier_1 | with GET /phone/john provider-verifier_1 | returns a response which provider-verifier_1 | WARN: Skipping set up for provider state 'provider in some state' for consumer 'basic_client' as there is no --provider-states-setup-url specified. provider-verifier_1 | has status code 200 provider-verifier_1 | has a matching body provider-verifier_1 | Given provider in some state provider-verifier_1 | request to get non-existent user's phone number provider-verifier_1 | with GET /phone/micky provider-verifier_1 | returns a response which provider-verifier_1 | WARN: Skipping set up for provider state 'provider in some state' for consumer 'basic_client' as there is no --provider-states-setup-url specified. provider-verifier_1 | has status code 404 provider-verifier_1 | provider-verifier_1 | 2 interactions, 0 failures
Ausführen beider Teile des Tests in der Pipeline
Nachdem beide Teile der Vertragstests zerlegt wurden, wäre es schön, sie bei jedem Commit auszuführen. Hier kommt Gitlab CI zur Rettung. Pipeline-Jobs werden in .gitlab-ci.yml
. Bevor wir zur Pipeline übergehen, müssen wir einige Worte zum GitLab Runner sagen, einem Open-Source-Projekt, mit dem Jobs ausgeführt und die Ergebnisse an GitLab zurückgesendet werden. Jobs können lokal oder mithilfe von Docker-Containern ausgeführt werden. In unserem Projekt verwenden wir Docker. Die Testinfrastruktur ist in Containern implementiert und wird in docker-compose.yml
, das sich im Stammverzeichnis des Projekts befindet.
version: '2' services: basic-flask-app: image: registry.gitlab.com/tknino69/basic_flask_app:latest ports: - 5005:5005 postgres: image: postgres ports: - 5432:5432 env_file: - test-setup.env volumes: - db-data:/var/lib/postgresql/data/pgdata pactbroker: image: dius/pact-broker links: - postgres ports: - 80:80 env_file: - test-setup.env provider-states: image: registry.gitlab.com/tknino69/cdc/provider-states:latest build: provider-states ports: - 5000:5000 consumer-test: image: registry.gitlab.com/tknino69/cdc/consumer-test:latest command: ["sh", "-c", "find -name '*.pyc' -delete && pytest $${TEST}"] links: - pactbroker environment: - CONSUMER_VERSION=$CI_COMMIT_SHA provider-verifier: image: registry.gitlab.com/tknino69/cdc/provider-verifier:latest build: provider-verifier ports: - 5001:5000 links: - pactbroker depends_on: - consumer-test - provider-states command: ['sh', '-c', 'find -name "*.pyc" -delete && CONSUMER_VERSION=`curl --header "PRIVATE-TOKEN:$${API_TOKEN}" https://gitlab.com/api/v4/projects/$${BASIC_CLIENT}/repository/commits | jq ".[0] .id" | sed -e "s/\x22//g"` && echo $${CONSUMER_VERSION} && pact-provider-verifier $${PACT_BROKER}/pacts/provider/$${PROVIDER}/consumer/$${CONSUMER}/version/$${CONSUMER_VERSION} --provider-base-url=$${BASE_URL} --pact-broker-base-url=$${PACT_BROKER} --provider=$${PROVIDER} --consumer-version-tag=$${CONSUMER_VERSION} --provider-app-version=$${PROVIDER_VERSION} -v --publish-verification-results=PUBLISH_VERIFICATION_RESULTS'] environment: - PROVIDER_VERSION=$CI_COMMIT_SHA - API_TOKEN=$API_TOKEN env_file: - test-setup.env volumes: db-data:
Wir haben also Services, die nach Bedarf in Containern ausgeführt werden.
Dienstleister:
basic-flask-app: image: registry.gitlab.com/tknino69/basic_flask_app:latest ports: - 5005:5005
Pact Broker und seine Datenbank. Volumes ermöglichen es uns, ein permanentes Repository für Pakte und Ergebnisse der Anbieterüberprüfung zu haben:
postgres: image: postgres ports: - 5432:5432 env_file: - test-setup.env volumes: - db-data:/var/lib/postgresql/data/pgdata pactbroker: image: dius/pact-broker links: - postgres ports: - 80:80 env_file: - test-setup.env
Dienstleisterstaaten. In der Praxis sollte der Anbieter in einen bestimmten Zustand versetzt werden (z. B. den Benutzer in die Datenbank aufnehmen). In unserem Beispiel wird jedoch einfach eine Dummy-Funktion ausgeführt.
provider-states: image: registry.gitlab.com/tknino69/cdc/provider-states:latest build: provider-states ports: - 5000:5000
Ein Dienst, der den Verbrauchertest ausführt. find -name '* .pyc' -delete && pytest $$ {TEST}
den Befehl, der im Container ausgeführt wird. find -name '* .pyc' -delete && pytest $$ {TEST}
consumer-test: image: registry.gitlab.com/tknino69/cdc/consumer-test:latest command: ["sh", "-c", "find -name '*.pyc' -delete && pytest $${TEST}"] links: - pactbroker environment: - CONSUMER_VERSION=$CI_COMMIT_SHA
Dienstanbieter-Verifizierer:
provider-verifier: image: registry.gitlab.com/tknino69/cdc/provider-verifier:latest build: provider-verifier ports: - 5001:5000 links: - pactbroker depends_on: - consumer-test - provider-states command: ['sh', '-c', 'find -name "*.pyc" -delete && CONSUMER_VERSION=`curl --header "PRIVATE-TOKEN:$${API_TOKEN}" https://gitlab.com/api/v4/projects/$${BASIC_CLIENT}/repository/commits | jq ".[0] .id" | sed -e "s/\x22//g"` && echo $${CONSUMER_VERSION} && pact-provider-verifier $${PACT_BROKER}/pacts/provider/$${PROVIDER}/consumer/$${CONSUMER}/version/$${CONSUMER_VERSION} --provider-base-url=$${BASE_URL} --pact-broker-base-url=$${PACT_BROKER} --provider=$${PROVIDER} --consumer-version-tag=$${CONSUMER_VERSION} --provider-app-version=$${PROVIDER_VERSION} -v --publish-verification-results=PUBLISH_VERIFICATION_RESULTS'] environment: - PROVIDER_VERSION=$CI_COMMIT_SHA - API_TOKEN=$API_TOKEN env_file: - test-setup.env
Verbraucher-Pipeline
.gitlab-ci.yml
im Stammverzeichnis des Consumer-Projekts beschreibt die Prozesse, die auf der Consumer-Seite ausgeführt werden:
image: gitlab/dind:latest variables: TEST: 'tests/docker-compose.app.yml' CONSUMER_VERSION: $CI_COMMIT_SHA BASIC_APP: '11993024' services: - gitlab/gitlab-runner:latest before_script: - docker login -u $GIT_USER -p $GIT_PASS registry.gitlab.com stages: - clone_test - get_broker_up - test - verify_provider - clean_up clone test: tags: - cdc stage: clone_test script: - git clone https://$GIT_USER:$GIT_PASS@gitlab.com/tknino69/cdc.git && ls -ali artifacts: paths: - cdc/ broker: tags: - cdc stage: get_broker_up script: - cd cdc && docker-compose -f docker-compose.yml up -d pactbroker dependencies: - clone test test: tags: - cdc stage: test script: - cd cdc && CONSUMER_VERSION=$CONSUMER_VERSION docker-compose -f docker-compose.yml -f $TEST up consumer-test dependencies: - clone test provider verification: tags: - cdc stage: verify_provider script: - curl -X POST -F token=$CI_JOB_TOKEN -F ref=master https://gitlab.com/api/v4/projects/$BASIC_APP/trigger/pipeline when: on_success clean up: tags: - cdc stage: clean_up script: - cd cdc && docker-compose stop consumer-test dependencies: - clone test
Hier passiert folgendes:
In before_script
wir uns mit den Variablen $ GIT_USER und $ GIT_PASS, die wir unter Einstellungen> CI / CD festgelegt haben, bei unserer gitlab-Registrierung an

- Als nächstes klonen wir ein Testprojekt
- Im nächsten Schritt erhöhen wir Pact Broker
- Dann beginnt der Verbrauchertest.
- Verwenden Sie danach die Gitlab-API, um die Anbieterüberprüfung zu starten.
- Und schließlich reinigen wir uns
Provider-Pipeline
Die Provider-Pipeline-Konfiguration wird in .gitlab-ci.yml
im Stammverzeichnis des Provider-Projekts gespeichert.
image: gitlab/dind:latest variables: TEST: 'tests/docker-compose.app.yml' PROVIDER_VERSION: $CI_COMMIT_SHA services: - gitlab/gitlab-runner:latest stages: - clone_test - provider_verification - clean_up clone test: tags: - cdc stage: clone_test script: - git clone https://$GIT_USER:$GIT_PASS@gitlab.com/tknino69/cdc.git artifacts: paths: - cdc/ verify provider: tags: - cdc stage: provider_verification before_script: - cd cdc - docker login -u $GIT_USER -p $GIT_PASS registry.gitlab.com && docker-compose -f docker-compose.yml up -d basic-flask-app script: - PROVIDER_VERSION=$PROVIDER_VERSION docker-compose -f docker-compose.yml -f $TEST up provider-verifier dependencies: - clone test .clean up: tags: - cdc stage: clean_up script: - cd cdc && docker-compose down --rmi local
Wie in der Consumer Pipeline haben wir mehrere Jobs:
- Klonen Sie ein Testprojekt
- Überprüfen Sie den Anbieter
- Wir reinigen uns
Fassen Sie zusammen :
- Schrieb einen Vertragstest in Python
- Richten Sie eine Testumgebung in Docker-Containern ein
- Konfiguriertes CI basierend auf Vertragstests, d.h. Beim Festschreiben für das Verbraucherprojekt wird die CI-Pipeline gestartet ( auf der Verbraucherseite : Klonen der Testumgebung -> Starten von Pact Broker -> Testen des Verbrauchers -> Starten der Anbieterüberprüfung -> Bereinigen; auf der Anbieterseite : Klonen der Testumgebung -> Anbieterüberprüfung -> Bereinigen )
Durch die Verpflichtung zum Anbieterprojekt wird die Überprüfung des Anbieters eingeleitet, um sicherzustellen, dass der Anbieter den Pakt einhält
Vielen Dank für Ihre Aufmerksamkeit.