Factory Pattern : Réduire la duplication et générer vos objets de test efficacement
Cet article est le deuxième de notre série dédiée aux design patterns pour l'automatisation des tests. Après avoir posé les bases avec le Page Object Model, explorons comment le Factory Pattern peut transformer la création de vos objets de test et être un allié précieux pour simplifier et centraliser la création de vos objets de test.
Quand la création devient un art
Imaginez-vous en train de créer manuellement chaque objet de test, répétant sans cesse les mêmes configurations, les mêmes initialisations... Cette approche artisanale devient rapidement un cauchemar de maintenance. C'est précisément là que le Factory Pattern entre en scène, transformant la création chaotique d'objets en un processus élégant et centralisé.
Le Factory Pattern, l'un des patterns de création les plus utilisés en génie logiciel, trouve une application particulièrement puissante dans l'automatisation des tests. Il nous permet de déléguer la création d'objets complexes à des classes spécialisées, garantissant cohérence et flexibilité.
Qu'est-ce que le Factory Pattern ?
Le Factory Pattern est un pattern de création qui fournit une interface pour créer des objets sans spécifier explicitement leur classe concrète. Dans le contexte de l'automatisation des tests, il centralise et standardise la création d'objets comme les drivers, les données de test, ou les Page Objects.
Objectif principal : Centraliser la création d'objets de test
Le Factory Pattern répond à plusieurs besoins cruciaux :
- Centralisation : Un point unique pour la création d'objets similaires
- Abstraction : Masquer la complexité de l'instanciation
- Flexibilité : Faciliter l'ajout de nouvelles variantes
- Réutilisabilité : Éviter la duplication de code de création
Architecture du Factory Pattern
Exemple concret : Factory pour tests multi-navigateurs
L'un des cas d'usage les plus répandus du Factory Pattern en automatisation est la gestion des WebDrivers pour les tests multi-navigateurs. Voici comment l'implémenter efficacement :
Sans Factory Pattern (approche répétitive) :
WebDriver driver;
if (browser.equals("chrome")) {
driver = new ChromeDriver();
} else if (browser.equals("firefox")) {
driver = new FirefoxDriver();
} else {
driver = new EdgeDriver();
}
// Duplication pour chaque navigateur... 😰
// Ce code est répété dans plusieurs tests = maintenance compliquée.
Avec Factory Pattern (approche élégante) :
// Factory
public class WebDriverFactory {
public static WebDriver create(String browser) {
switch (browser.toLowerCase()) {
case "chrome":
return new ChromeDriver();
case "firefox":
return new FirefoxDriver();
case "edge":
return new EdgeDriver();
default:
throw new IllegalArgumentException("Navigateur non supporté : " + browser);
}
}
}
// Utilisation dans les tests
WebDriver driver = WebDriverFactory.create("chrome");
LoginPage login = new LoginPage(driver);
login.loginAs("jean@test.com", "123456");
Résultat : un seul endroit à modifier si vous ajoutez Safari, Opera, ou un mode headless.
Factory avancé : Création de données de test
Le Factory Pattern excelle également dans la création de données de test complexes :
// Factory pour générer des utilisateurs de test
public class TestUserFactory {
private static final Faker faker = new Faker();
public static User createValidUser() {
return User.builder()
.email(faker.internet().emailAddress())
.firstName(faker.name().firstName())
.lastName(faker.name().lastName())
.password("ValidPassword123!")
.age(faker.number().numberBetween(18, 65))
.build();
}
public static User createAdminUser() {
return User.builder()
.email("admin@testdomain.com")
.firstName("Admin")
.lastName("User")
.password("AdminPass123!")
.role(UserRole.ADMIN)
.age(35)
.build();
}
public static User createInvalidUser() {
return User.builder()
.email("invalid-email")
.firstName("")
.lastName(null)
.password("123") // Mot de passe faible
.age(10) // Âge invalide
.build();
}
public static List<User> createUserBatch(int count, UserType type) {
List<User> users = new ArrayList<>();
for (int i = 0; i < count; i++) {
switch (type) {
case VALID:
users.add(createValidUser());
break;
case ADMIN:
users.add(createAdminUser());
break;
case INVALID:
users.add(createInvalidUser());
break;
}
}
return users;
}
}
// Utilisation dans les tests
@Test
public void testUserRegistrationWithValidData() {
User testUser = TestUserFactory.createValidUser();
RegistrationPage registrationPage = new RegistrationPage(driver);
registrationPage.fillRegistrationForm(testUser);
Assert.assertTrue(registrationPage.isRegistrationSuccessful());
}
Architecture complète avec Factory Pattern
Bonnes pratiques essentielles
1. Interface claire et cohérente
Définissez des interfaces claires pour vos factories :
// ✅ Interface simple et expressive
public interface TestDataFactory<T> {
T createValid();
T createInvalid();
T createWithCustomData(Map<String, Object> customData);
List<T> createBatch(int count);
}
2. Gestion des erreurs robuste
Implémentez une gestion d'erreur appropriée :
public class DriverFactoryManager {
public static WebDriver createDriver(BrowserType browserType) {
try {
WebDriverFactory factory = factories.get(browserType);
if (factory == null) {
throw new UnsupportedBrowserException(
"Browser not supported: " + browserType
);
}
return factory.createDriver();
} catch (Exception e) {
logger.error("Failed to create driver for browser: " + browserType, e);
throw new DriverCreationException("Driver creation failed", e);
}
}
}
3. Configuration externalisée
Utilisez des fichiers de configuration pour la flexibilité :
// config.properties
browser.default=chrome
browser.headless=true
timeout.implicit=10
timeout.explicit=30
// Factory utilisant la configuration
public class ConfigurableDriverFactory {
private static final Properties config = loadConfiguration();
public static WebDriver createDriver() {
String browser = config.getProperty("browser.default", "chrome");
boolean headless = Boolean.parseBoolean(config.getProperty("browser.headless", "false"));
return createDriver(BrowserType.valueOf(browser.toUpperCase()), headless);
}
}
4. Pattern Singleton pour les factories
Optimisez les performances avec le pattern Singleton :
public class WebDriverFactoryManager {
private static volatile WebDriverFactoryManager instance;
private final Map<BrowserType, WebDriverFactory> factories;
private WebDriverFactoryManager() {
factories = initializeFactories();
}
public static WebDriverFactoryManager getInstance() {
if (instance == null) {
synchronized (WebDriverFactoryManager.class) {
if (instance == null) {
instance = new WebDriverFactoryManager();
}
}
}
return instance;
}
}
5. Factory Registry pour l'extensibilité
Permettez l'enregistrement dynamique de nouvelles factories :
public class FactoryRegistry {
private static final Map<String, WebDriverFactory> registeredFactories = new ConcurrentHashMap<>();
public static void registerFactory(String name, WebDriverFactory factory) {
registeredFactories.put(name, factory);
}
public static WebDriver createDriver(String factoryName) {
WebDriverFactory factory = registeredFactories.get(factoryName);
if (factory == null) {
throw new IllegalArgumentException("Unknown factory: " + factoryName);
}
return factory.createDriver();
}
}
FAQ
Q: Quand utiliser le Factory Pattern dans mes tests ?
R: Utilisez-le dès que vous créez plusieurs instances d'objets similaires avec des variations (différents navigateurs, jeux de données, configurations). C'est particulièrement utile pour les WebDrivers, les données de test, et les Page Objects avec paramètres.
Q: Factory Pattern vs Builder Pattern, quelle différence ?
R: Le Factory crée des objets complets d'un coup, tandis que le Builder construit des objets étape par étape. Factory = "créer différents types d'objets", Builder = "créer des objets complexes progressivement".
Q: Comment gérer les dépendances dans les factories ?
R: Utilisez l'injection de dépendances ou passez les dépendances en paramètres. Évitez les dépendances hardcodées dans les factories pour maintenir la flexibilité.
Q: Le Factory Pattern impacte-t-il les performances ?
R: L'impact est généralement négligeable. L'abstraction apporte plus de bénéfices (maintenabilité, flexibilité) que le coût minimal en performance. Pour optimiser, utilisez des caches ou le pattern Singleton si approprié.
Q: Comment tester mes factories ?
R: Créez des tests unitaires qui vérifient que chaque factory retourne le bon type d'objet avec les bonnes propriétés. Testez aussi la gestion des cas d'erreur et des paramètres invalides.
Q: Peut-on combiner Factory avec d'autres patterns ?
R: Absolument ! Factory se combine excellemment avec Singleton (pour l'instance unique), et Builder (pour des objets complexes).
Q: Peut-on utiliser plusieurs factories dans un même framework ?
R: Oui. Par exemple, une WebDriverFactory pour les navigateurs et une DataFactory pour générer des données de test.
A retenir
Le Factory Pattern est un outil simple mais puissant pour rendre vos tests plus robustes, maintenables et évolutifs.
Il permet de centraliser la création d’objets, d’éviter la duplication et d’ouvrir la voie à des frameworks de test professionnels.
Dans le prochain article, nous explorerons le Facade Pattern et comment il peut simplifier vos scénarios de test les plus complexes en masquant la complexité technique derrière une interface élégante.