Logo
  • A propos
  • Blog
  • Services
  • Media
  • Contact
Contactez-nous !

Facade Pattern : Cacher la complexité de vos scénarios automatisés

Date publication
Sep 16, 2025

Facade Pattern : Cacher la complexité de vos scénarios automatisés

Cet article est le troisième de notre série dédiée aux design patterns pour l'automatisation des tests. Après avoir maîtrisé le Page Object Model et le Factory Pattern, découvrons comment le Facade Pattern peut transformer vos scénarios complexes en interfaces élégantes et simples.

image

Introduction : L'art de simplifier la complexité

Dans l'univers de l'automatisation des tests, nous sommes souvent confrontés à des scénarios qui enchaînent de multiples étapes complexes : authentification, navigation, interactions avec plusieurs composants, validations multiples... Ces orchestrations sophistiquées peuvent rapidement transformer nos tests en véritables labyrinthes de code.

C'est là que le Facade Pattern révèle toute sa puissance. Tel un chef d'orchestre qui dirige une symphonie complexe d'un simple geste, ce pattern nous permet de masquer la complexité technique derrière une interface simple et intuitive. Un seul appel pour orchestrer des dizaines d'opérations !

Qu'est-ce que le Facade Pattern ?

Le Facade Pattern est un pattern structurel qui fournit une interface simplifiée à un ensemble complexe de classes, bibliothèques ou frameworks. Dans le contexte de l'automatisation des tests, il nous permet de regrouper des séquences d'actions complexes derrière des méthodes simples et expressives.

Objectif principal : Regrouper plusieurs actions complexes dans un seul appel

Le Facade Pattern répond à des besoins essentiels en automatisation :

  • Simplification : Masquer la complexité technique des interactions
  • Abstraction : Créer un niveau d'abstraction métier
  • Réutilisabilité : Encapsuler des scénarios récurrents
  • Maintenabilité : Centraliser les changements d'implémentation

Architecture du Facade Pattern

image

Exemple concret : Scénario e-commerce complet

Prenons l'exemple d'un site e-commerce où nous devons tester un parcours d'achat complet. Sans Facade Pattern, nos tests deviendraient rapidement illisibles et difficiles à maintenir.

Sans Facade Pattern (approche complexe et répétitive) :

Avec Facade Pattern (approche élégante et lisible) :

Comparaison des approches :

Sans Facade : 50+ lignes de code complexe avec gestion manuelle de chaque étape Avec Facade : 1 ligne pour exécuter tout le scénario et maintenance facilité en cas de changement dans le workflow.

// Sans Facade (complexe)
LoginPage loginPage = new LoginPage(driver);
loginPage.enterEmail("user@test.com");
loginPage.enterPassword("password123");
// ... 45+ autres lignes ...

// Avec Facade (simple)
boolean success = ecommerce.completePurchase("user@test.com", "password123", "Dell Laptop");

Architecture en couches avec Facade

image

Facade Pattern avancé : Gestion d'état et contexte

Bonnes pratiques essentielles

1. Interface métier expressive

Créez des méthodes qui parlent le langage métier :

2. Gestion d'erreur unifiée

Implémentez une stratégie de gestion d'erreur cohérente :

3. Configuration et paramétrage

Permettez la configuration flexible :

4. Logging et monitoring

Intégrez le logging pour le debugging :

5. Tests des facades

Testez vos facades avec des mocks appropriés :

FAQ

Q: Quand utiliser le Facade Pattern dans mes tests ?

R: Utilisez-le dès que vous avez des scénarios complexes qui enchaînent plusieurs Page Objects ou actions. C'est particulièrement utile pour les workflows métier complets (achat, inscription, processus d'approbation) et les cas de test end-to-end.

Q: Facade Pattern vs Page Object Model, quelle différence ?

R: Le POM encapsule une page/composant spécifique, tandis que le Facade orchestre plusieurs Page Objects pour réaliser un scénario métier complet. POM = "une page", Facade = "un processus métier".

Q: Comment éviter que mes facades deviennent trop complexes ?

R: Créez des facades spécialisées par domaine métier (LoginFacade, ShoppingFacade, PaymentFacade) et composez-les dans un facade principal. Respectez le principe de responsabilité unique.

Q: Les facades impactent-elles les performances de mes tests ?

R: Non, l'impact est négligeable. Les facades n'ajoutent qu'une couche d'abstraction légère. En réalité, elles peuvent améliorer les performances en optimisant les interactions et en évitant les opérations redondantes.

Q: Comment gérer les variations de scénarios avec les facades ?

R: Utilisez des paramètres optionnels, des classes de configuration, ou des méthodes surchargées. Vous pouvez aussi créer des facades spécialisées pour les variantes importantes (ExpressFacade vs StandardFacade).

Q: Peut-on tester les facades de manière isolée ?

R: Oui et c'est recommandé ! Utilisez des mocks pour les dépendances (Page Objects, autres facades) et testez la logique d'orchestration. Cela permet de valider les scénarios sans exécuter tous les sous-composants.

Q: Comment documenter efficacement mes facades ?

R: Documentez les scénarios métier couverts, les prérequis, les données nécessaires et les résultats possibles. Ajoutez des exemples d'utilisation et précisez les cas d'erreur gérés.

Q: Faut-il une façade par scénario ou par domaine fonctionnel ?

Plutôt par domaine fonctionnel (e-commerce, authentification, inscription…). Cela évite la prolifération inutile de façades.

A retenir

Le Facade Pattern est une excellente façon de simplifier vos tests automatisés.

Il cache la complexité technique derrière une interface claire, rend vos tests plus lisibles et réduit la duplication.

Dans le prochain article, nous découvrirons le Builder Pattern et comment il peut nous aider à créer des objets de test complexes avec clarté et flexibilité, en complément parfait de nos facades.

Plus d’articles comme celui-ci

Combiner les Patterns : Architecturer un framework de test solide et évolutif
Combiner les Patterns : Architecturer un framework de test solide et évolutif
Oct 7, 2025
Screenplay Pattern : Structurer vos tests pour plus de lisibilité et de robustesse
Screenplay Pattern : Structurer vos tests pour plus de lisibilité et de robustesse
Sep 30, 2025
Builder Pattern : Créer des objets de test complexes avec clarté
Builder Pattern : Créer des objets de test complexes avec clarté
Sep 23, 2025
Facade Pattern : Cacher la complexité de vos scénarios automatisés
Facade Pattern : Cacher la complexité de vos scénarios automatisés
Sep 16, 2025
Factory Pattern : Réduire la duplication et générer vos objets de test efficacement
Factory Pattern : Réduire la duplication et générer vos objets de test efficacement
Sep 9, 2025
Page Object Model : La base solide pour toute automatisation UI
Page Object Model : La base solide pour toute automatisation UI
Sep 2, 2025
Logo

Accueil

Blog

Newsletters

Podcasts

Vidéos

Qui suis-je ?

Shift Op Solutions

Mentorat

Formations

Etat des Lieux

Contact

Copyright © Jean-François Fresi 2024 - Site créé en nocode.

LinkedInYouTubeSpotifyRSS
@Test
public void testCompletePurchaseFlow() {
    // Login complexe
    LoginPage loginPage = new LoginPage(driver);
    loginPage.enterEmail("user@test.com");
    loginPage.enterPassword("password123");
    loginPage.clickLogin();
    HomePage homePage = loginPage.waitForRedirection();
    Assert.assertTrue(homePage.isUserLoggedIn());
    
    // Recherche et sélection produit
    SearchPage searchPage = homePage.openSearch();
    searchPage.enterSearchTerm("laptop");
    searchPage.applyFilters("brand:Dell", "price:1000-2000");
    ProductListPage productList = searchPage.search();
    ProductPage productPage = productList.selectFirstProduct();
    
    // Configuration produit
    productPage.selectVariant("color", "Silver");
    productPage.selectVariant("storage", "512GB");
    productPage.setQuantity(1);
    
    // Ajout au panier
    CartPage cartPage = productPage.addToCart();
    Assert.assertTrue(cartPage.isProductInCart("Dell Laptop"));
    
    // Processus de commande
    CheckoutPage checkoutPage = cartPage.proceedToCheckout();
    checkoutPage.fillShippingAddress("123 Main St", "City", "12345");
    checkoutPage.selectShippingMethod("standard");
    
    // Paiement
    PaymentPage paymentPage = checkoutPage.continueToPayment();
    paymentPage.selectPaymentMethod("credit_card");
    paymentPage.fillCreditCardDetails("4111111111111111", "12/25", "123");
    
    // Confirmation
    OrderConfirmationPage confirmation = paymentPage.completeOrder();
    Assert.assertTrue(confirmation.isOrderConfirmed());
    String orderNumber = confirmation.getOrderNumber();
    Assert.assertNotNull(orderNumber);
    
    // ... Plus de 50 lignes pour un seul test ! 😰
}
// Facade principal pour l'e-commerce
public class EcommerceFacade {
    private final WebDriver driver;
    private final LoginPage loginPage;
    private final SearchPage searchPage;
    private final ProductPage productPage;
    private final CartPage cartPage;
    private final CheckoutPage checkoutPage;
    private final PaymentPage paymentPage;
    
    public EcommerceFacade(WebDriver driver) {
        this.driver = driver;
        this.loginPage = new LoginPage(driver);
        this.searchPage = new SearchPage(driver);
        this.productPage = new ProductPage(driver);
        this.cartPage = new CartPage(driver);
        this.checkoutPage = new CheckoutPage(driver);
        this.paymentPage = new PaymentPage(driver);
    }
    
    // Une méthode simple qui cache toute la complexité !
    public boolean completePurchase(String email, String password, String productName) {
        try {
            // 1. Login
            loginPage.navigateTo();
            loginPage.login(email, password);
            
            // 2. Recherche et ajout produit
            searchPage.navigateTo();
            searchPage.searchProduct(productName);
            productPage.addToCart();
            
            // 3. Commande
            cartPage.proceedToCheckout();
            checkoutPage.fillShippingDetails();
            
            // 4. Paiement
            paymentPage.payWithDefaultCard();
            
            // 5. Vérification
            return paymentPage.isOrderConfirmed();
            
        } catch (Exception e) {
            System.out.println("Purchase failed: " + e.getMessage());
            return false;
        }
    }
    
    // Méthode pour créer un compte complet
    public boolean createAccountAndPurchase(String email, String password, String productName) {
        try {
            // Création du compte
            loginPage.navigateTo();
            loginPage.createAccount(email, password);
            
            // Puis achat
            return completePurchase(email, password, productName);
            
        } catch (Exception e) {
            return false;
        }
    }
}

// Test utilisant le Facade - Simple et lisible !
@Test
public void testCompletePurchaseFlow() {
    EcommerceFacade ecommerce = new EcommerceFacade(driver);
    
    // Tout le scénario complexe en une seule ligne !
    boolean success = ecommerce.completePurchase(
        "user@test.com", 
        "password123", 
        "Dell Laptop"
    );
    
    Assert.assertTrue(success, "Purchase should be successful");
}

@Test
public void testNewUserPurchaseFlow() {
    EcommerceFacade ecommerce = new EcommerceFacade(driver);
    
    // Création compte + achat en une ligne !
    boolean success = ecommerce.createAccountAndPurchase(
        "newuser@test.com",
        "newpassword123", 
        "iPhone 15"
    );
    
    Assert.assertTrue(success, "New user purchase should be successful");
}
public class StatefulEcommerceFacade {
    private final WebDriver driver;
    private final SessionContext context;
    
    public StatefulEcommerceFacade(WebDriver driver) {
        this.driver = driver;
        this.context = new SessionContext();
    }
    
    public EcommerceFacade loginAs(User user) {
        LoginResult result = new LoginFacade(driver).authenticateUser(user);
        if (result.isSuccessful()) {
            context.setCurrentUser(user);
            context.setLoggedIn(true);
        }
        return this;
    }
    
    public EcommerceFacade addToCart(Product... products) {
        ensureLoggedIn();
        ShoppingFacade shopping = new ShoppingFacade(driver);
        
        for (Product product : products) {
            ShoppingResult result = shopping.addProductToCart(product);
            if (result.isSuccessful()) {
                context.addToCart(product);
            }
        }
        return this;
    }
    
    public PurchaseResult checkout(PaymentInfo paymentInfo) {
        ensureLoggedIn();
        ensureCartNotEmpty();
        
        CheckoutFacade checkout = new CheckoutFacade(driver);
        PaymentFacade payment = new PaymentFacade(driver);
        
        CheckoutResult checkoutResult = checkout.processCheckout(
            context.getCurrentUser().getShippingInfo()
        );
        
        if (checkoutResult.isSuccessful()) {
            PaymentResult paymentResult = payment.processPayment(paymentInfo);
            if (paymentResult.isSuccessful()) {
                context.completeOrder(paymentResult.getOrderNumber());
                return PurchaseResult.successful(paymentResult.getOrderNumber());
            }
        }
        
        return PurchaseResult.failed("Checkout process failed");
    }
    
    private void ensureLoggedIn() {
        if (!context.isLoggedIn()) {
            throw new IllegalStateException("User must be logged in");
        }
    }
    
    private void ensureCartNotEmpty() {
        if (context.getCartItems().isEmpty()) {
            throw new IllegalStateException("Cart cannot be empty");
        }
    }
}

// Utilisation fluide
@Test
public void testFluentPurchaseFlow() {
    User user = TestUserFactory.createValidUser();
    Product laptop = TestProductFactory.createLaptop();
    Product mouse = TestProductFactory.createMouse();
    PaymentInfo payment = TestPaymentFactory.createValidCreditCard();
    
    StatefulEcommerceFacade ecommerce = new StatefulEcommerceFacade(driver);
    
    PurchaseResult result = ecommerce
        .loginAs(user)
        .addToCart(laptop, mouse)
        .checkout(payment);
    
    Assert.assertTrue(result.isSuccessful());
}
public class EcommerceFacade {
    // ✅ Noms métier expressifs
    public PurchaseResult completePurchaseJourney(User buyer, Product item, PaymentMethod payment) { }
    public RefundResult processCustomerRefund(String orderNumber, RefundReason reason) { }
    public AccountResult setupNewCustomerAccount(CustomerProfile profile) { }
    
    // ❌ Noms techniques peu clairs
    public boolean doLoginAndBuyStuff(String email, String pwd, String productId) { }
}
public class EcommerceFacade {
    private final ErrorHandler errorHandler;
    
    public PurchaseResult completePurchase(User user, Product product, PaymentInfo payment) {
        try {
            return executePurchaseFlow(user, product, payment);
        } catch (LoginException e) {
            return errorHandler.handleLoginError(e);
        } catch (PaymentException e) {
            return errorHandler.handlePaymentError(e);
        } catch (Exception e) {
            return errorHandler.handleUnexpectedError(e);
        }
    }
}
public class ConfigurableEcommerceFacade {
    private final FacadeConfiguration config;
    
    public ConfigurableEcommerceFacade(FacadeConfiguration config) {
        this.config = config;
    }
    
    public PurchaseResult completePurchase(User user, Product product, PaymentInfo payment) {
        if (config.skipLogin() && isUserAlreadyLoggedIn()) {
            // Skip login step
        }
        
        if (config.useExpressCheckout()) {
            return processExpressCheckout(user, product, payment);
        }
        
        return processStandardCheckout(user, product, payment);
    }
}
public class EcommerceFacade {
    private static final Logger logger = LoggerFactory.getLogger(EcommerceFacade.class);
    
    public PurchaseResult completePurchase(User user, Product product, PaymentInfo payment) {
        logger.info("Starting purchase flow for user: {}, product: {}", 
                   user.getEmail(), product.getName());
        
        Stopwatch stopwatch = Stopwatch.createStarted();
        
        try {
            PurchaseResult result = executePurchaseFlow(user, product, payment);
            
            logger.info("Purchase flow completed in {} ms. Success: {}", 
                       stopwatch.elapsed(TimeUnit.MILLISECONDS), result.isSuccessful());
            
            return result;
        } catch (Exception e) {
            logger.error("Purchase flow failed after {} ms", 
                        stopwatch.elapsed(TimeUnit.MILLISECONDS), e);
            throw e;
        }
    }
}
public class EcommerceFacadeTest {
    @Mock private LoginFacade loginFacade;
    @Mock private ShoppingFacade shoppingFacade;
    @Mock private PaymentFacade paymentFacade;
    
    private EcommerceFacade ecommerceFacade;
    
    @BeforeEach
    public void setUp() {
        ecommerceFacade = new EcommerceFacade(loginFacade, shoppingFacade, paymentFacade);
    }
    
    @Test
    public void testSuccessfulPurchase() {
        // Given
        User user = TestUserFactory.createValidUser();
        Product product = TestProductFactory.createLaptop();
        PaymentInfo payment = TestPaymentFactory.createValidCreditCard();
        
        when(loginFacade.authenticateUser(user)).thenReturn(LoginResult.successful());
        when(shoppingFacade.addProductToCart(product)).thenReturn(ShoppingResult.successful());
        when(paymentFacade.processPayment(payment)).thenReturn(PaymentResult.successful("ORDER123"));
        
        // When
        PurchaseResult result = ecommerceFacade.completePurchase(user, product, payment);
        
        // Then
        assertTrue(result.isSuccessful());
        assertEquals("ORDER123", result.getOrderNumber());
        
        verify(loginFacade).authenticateUser(user);
        verify(shoppingFacade).addProductToCart(product);
        verify(paymentFacade).processPayment(payment);
    }
}