Contract based testing

9 sep. 2023 | by Ralph Van Der Horst

Contract based testing

Service Virtualisatie en Pact: Efficiëntie in een Multi-Leverancier Ecosysteem

Inleiding: De Uitdaging van een omgeving waarbij verschillende leveranciers aangesloten zijn

In de moderne software-ontwikkeling werken organisaties steeds vaker met complexe ecosystemen van meerdere leveranciers. Deze aanpak brengt echter significante uitdagingen met zich mee, vooral op het gebied van testing en integratie.

Het Probleem: Kostbare Testomgevingen

Wanneer organisaties te maken hebben met een groot aantal leveranciers, kan het opzetten en onderhouden van afzonderlijke testomgevingen voor elk van deze services aanzienlijk in de kosten lopen. Elke omgeving brengt namelijk verschillende kostenposten met zich mee:

  • Infrastructuurkosten: Servers, cloud resources, databases
  • Licentiekosten: Software licenties voor elke omgeving
  • Operationele kosten: Configuratie, onderhoud en monitoring
  • Synchronisatiekosten: Tijd en effort voor het afstemmen tussen omgevingen

De Oplossing: Service Virtualisatie

Service virtualisatie komt hier als redder in nood. Door het gebruik van service virtualisatie kunnen teams afhankelijkheden simuleren en isoleren, waardoor de noodzaak van het hebben van volledige, geïntegreerde testomgevingen voor elk afzonderlijk component of elke leverancier drastisch verminderd wordt.

Voordelen van Service Virtualisatie

Kostenbesparing: Minder fysieke of cloud-gebaseerde resources nodig Flexibiliteit: Snel verschillende scenario’s kunnen simuleren Onafhankelijkheid: Minder afhankelijk van externe leveranciers Schaalbaarheid: Eenvoudig opschalen voor verschillende testscenario’s

Pact: De Game-Changer voor Contract Testing

Pact biedt hierin een unieke waarde. In combinatie met service virtualisatie kan Pact niet alleen contracten valideren, maar ook een gesimuleerde omgeving bieden waarin deze contracten worden gehonoreerd.

Wat is Contract Testing?

Contract testing is een methodologie waarbij de interacties tussen services worden gedefinieerd en gevalideerd door middel van contracten. Deze contracten specificeren:

  • Verwachte requests: Wat de consumer naar de provider stuurt
  • Verwachte responses: Wat de provider terugstuurt
  • Data formaten: Structuur en types van de uitgewisselde data
  • Error scenarios: Hoe fouten worden afgehandeld

Pact in Actie

In plaats van te moeten wachten op een volledige leveranciersimplementatie, kunnen ontwikkelaars en testers met Pact en service virtualisatie hun interacties valideren in een lichtgewicht, virtuele omgeving.

Substantiële Kostenbesparingen

De combinatie van Pact en service virtualisatie levert verschillende kostenbesparingen op:

💰 Verminderde Infrastructuurkosten

Minder fysieke of cloud-gebaseerde resources zijn nodig voor testdoeleinden.

🔗 Verminderde Afhankelijkheid

Teams zijn minder afhankelijk van externe leveranciers voor het testen van integraties.

Snellere Feedback Loops

Door het verminderen van afhankelijkheden kunnen teams sneller itereren en fouten eerder in het ontwikkelproces identificeren.

🔄 Backward Compatibiliteit vanuit Pact

Door te testen tegen oudere versies van contracten, kunnen providers zeker zijn dat nieuwe wijzigingen geen bestaande consumenten zullen breken.

🚀 Vooruitplannen vanuit Pact

Consumenten kunnen hun integraties testen tegen toekomstige versies van providers om te zorgen voor een soepele overgang wanneer die nieuwe versies live gaan.

Contract Testing met Pact: Praktische Implementatie

Laten we een concreet voorbeeld bekijken van contract testing met Pact in Python. We simuleren een scenario waarbij een “Consumer” service informatie wil ophalen over een gebruiker van een “Provider” service.

Stap 1: Omgeving Voorbereiden

Installatie Dependencies

Installeer de benodigde Pact-pakketten met pip:

pip install pact-python

Project Structuur

project/
├── consumer/
│   ├── consumer_service.py
│   └── test_consumer_pact.py
├── provider/
│   ├── provider_service.py
│   └── test_provider_pact.py
└── pacts/
    └── generated_pacts/

Stap 2: Consumer Pact Implementatie

Consumer Service Code

Eerst implementeren we de consumer service:

# consumer/consumer_service.py
import requests
from typing import Dict, Any

class UserService:
    def __init__(self, base_url: str):
        self.base_url = base_url
    
    def get_user(self, user_id: int) -> Dict[str, Any]:
        """Fetch user data from provider service"""
        response = requests.get(f"{self.base_url}/user/{user_id}")
        response.raise_for_status()
        return response.json()
    
    def get_user_profile(self, user_id: int) -> Dict[str, Any]:
        """Get formatted user profile"""
        user_data = self.get_user(user_id)
        return {
            "user_id": user_data["id"],
            "display_name": user_data["name"],
            "profile_complete": bool(user_data.get("email"))
        }

Consumer Pact Test

Nu schrijven we de Consumer Pact test:

# consumer/test_consumer_pact.py
import pytest
from pact import Consumer, Provider
from consumer_service import UserService

@pytest.fixture
def pact():
    """Setup Pact consumer"""
    pact = Consumer('UserConsumerService').has_pact_with(
        Provider('UserProviderService'), 
        port=1234
    )
    pact.start_service()
    yield pact
    pact.stop_service()

class TestUserServiceContract:
    
    def test_get_existing_user(self, pact):
        """Test retrieving an existing user"""
        # Given
        pact.given('User with ID 123 exists')
        
        # Upon receiving
        pact.upon_receiving('A request for user 123')
        
        # With request
        pact.with_request(
            method='GET', 
            path='/user/123',
            headers={'Accept': 'application/json'}
        )
        
        # Will respond with
        pact.will_respond_with(
            status=200,
            headers={'Content-Type': 'application/json'},
            body={
                "id": 123,
                "name": "John Doe",
                "email": "john.doe@example.com"
            }
        )
        
        # Test
        with pact:
            service = UserService('http://localhost:1234')
            result = service.get_user(123)
            
            assert result["id"] == 123
            assert result["name"] == "John Doe"
            assert result["email"] == "john.doe@example.com"
    
    def test_get_nonexistent_user(self, pact):
        """Test retrieving a non-existent user"""
        # Given
        pact.given('User with ID 999 does not exist')
        
        # Upon receiving
        pact.upon_receiving('A request for non-existent user 999')
        
        # With request
        pact.with_request(method='GET', path='/user/999')
        
        # Will respond with
        pact.will_respond_with(
            status=404,
            headers={'Content-Type': 'application/json'},
            body={
                "error": "User not found",
                "code": "USER_NOT_FOUND"
            }
        )
        
        # Test
        with pact:
            service = UserService('http://localhost:1234')
            with pytest.raises(requests.exceptions.HTTPError):
                service.get_user(999)

Stap 3: Provider Pact Verificatie

Provider Service Implementation

# provider/provider_service.py
from flask import Flask, jsonify, request
from typing import Dict, Any

app = Flask(__name__)

# Mock database
USERS_DB = {
    123: {
        "id": 123,
        "name": "John Doe",
        "email": "john.doe@example.com"
    }
}

@app.route('/user/<int:user_id>')
def get_user(user_id: int):
    """Get user by ID"""
    user = USERS_DB.get(user_id)
    if user:
        return jsonify(user)
    else:
        return jsonify({
            "error": "User not found",
            "code": "USER_NOT_FOUND"
        }), 404

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Provider Verification Test

De Provider controleert vervolgens de gegenereerde pact-bestanden:

# provider/test_provider_pact.py
import pytest
from pact import Verifier
from provider_service import app

class TestProviderContract:
    
    def test_against_consumer_pacts(self):
        """Verify provider against consumer pacts"""
        
        # Setup provider states
        def provider_state_setup(state: str):
            if state == "User with ID 123 exists":
                # Ensure user 123 exists in test database
                return True
            elif state == "User with ID 999 does not exist":
                # Ensure user 999 doesn't exist
                return True
            return False
        
        # Configure verifier
        verifier = Verifier(
            provider='UserProviderService',
            provider_base_url='http://localhost:5000'
        )
        
        # Start provider service for testing
        app.run(port=5000, debug=False)
        
        # Verify against pact files
        output, logs = verifier.verify_pacts(
            './pacts/',
            provider_states_setup_url='http://localhost:5000/_pact/provider_states'
        )
        
        assert output == 0  # Verification successful

Stap 4: Advanced Pact Features

Pact Broker Integration

Voor teams die met meerdere services werken, is een Pact Broker essentieel:

# Advanced verification with Pact Broker
def test_verify_with_broker():
    verifier = Verifier(
        provider='UserProviderService',
        provider_base_url='http://localhost:5000'
    )
    
    # Verify against Pact Broker
    output, logs = verifier.verify_with_broker(
        broker_url='http://pact-broker-url',
        broker_username='broker_user',
        broker_password='broker_pass',
        consumer_version='1.0.0',
        provider_version='2.1.0'
    )
    
    assert output == 0

Consumer Version Tags

# Tagging consumer versions
from pact import publish_pacts

def publish_consumer_pacts():
    """Publish pacts to broker with version tags"""
    publish_pacts(
        broker_url='http://pact-broker-url',
        consumer_version='1.2.3',
        pact_files=['./pacts/'],
        tags=['main', 'production']
    )

Best Practices voor Pact Implementation

1. Realistic Test Data

Gebruik realistische data in je pacts die representatief zijn voor productiegebruik.

2. Provider States Management

Implementeer provider states zorgvuldig om verschillende test scenario’s te ondersteunen.

3. Versioning Strategy

Ontwikkel een duidelijke versioning strategie voor zowel consumers als providers.

4. CI/CD Integration

Integreer Pact tests in je CI/CD pipeline voor automatische validatie.

5. Error Scenarios

Test niet alleen happy paths, maar ook error scenarios en edge cases.

Toekomstige Ontwikkelingen

Demo Omgeving in Ontwikkeling

Binnenkort kunt u dit live proberen omdat ik hard bezig ben met een demo omgeving voor:

  • UI Testing: Frontend test automation
  • API Testing: REST en GraphQL API validatie
  • Unit Testing: Geïsoleerde component testing

Leermaterialen

Alle implementaties zijn beschikbaar in de repositories die ik heb opgezet, inclusief:

  • Complete voorbeeldprojecten
  • Step-by-step tutorials
  • Best practices guides
  • Troubleshooting documentation

Conclusie

In een complex ecosysteem met veel leveranciers kan de combinatie van Pact en service virtualisatie zorgen voor:

Snellere leveringen door verminderde afhankelijkheden
Lagere kosten door efficiëntere resource-inzet
Betere kwaliteit door vroege detectie van integratieproblemen
Meer flexibiliteit in ontwikkeling en testing

De investering in contract testing met Pact betaalt zich snel terug door de dramatische verbetering in ontwikkelsnelheid en -kwaliteit, vooral in microservices architecturen.

Community & Resources

Voor meer informatie, vragen en samenwerking:

🔗 LinkedIn Connect: Ralph van der Horst


Dit artikel is onderdeel van een serie over moderne testing methodologieën. Blijf op de hoogte voor meer praktische implementatiegidsen en best practices.

by Ralph Van Der Horst

arrow right
back to blog

share this article

Relevant articles

Wat kan service virtualizatie betekenen voor grote organizaties

Servicevirtualisatie Je hebt die kreet vast wel is gehoord, Servicevirtualisatie, wat houdt dat eigenlijk in. Servicevirtualizatie is het proces …

Read More