+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 219 of 365

๐Ÿ“˜ Test Data Management: Factories and Builders

Master test data management: factories and builders in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on Test Data Management using Factories and Builders! ๐ŸŽ‰ In this guide, weโ€™ll explore how to create clean, maintainable test data that makes your tests readable and reliable.

Youโ€™ll discover how factories and builders can transform your testing experience. Whether youโ€™re building web applications ๐ŸŒ, APIs ๐Ÿ–ฅ๏ธ, or libraries ๐Ÿ“š, understanding test data management is essential for writing robust, maintainable tests.

By the end of this tutorial, youโ€™ll feel confident creating sophisticated test data patterns in your own projects! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Test Data Management

๐Ÿค” What are Factories and Builders?

Test factories and builders are like recipe cards for your test data ๐ŸŽจ. Think of them as templates that help you create consistent, realistic test objects without repetitive code.

In Python testing terms, factories and builders are patterns that help you:

  • โœจ Create test data with minimal boilerplate
  • ๐Ÿš€ Generate variations of test objects easily
  • ๐Ÿ›ก๏ธ Maintain consistency across your test suite

๐Ÿ’ก Why Use Factories and Builders?

Hereโ€™s why developers love these patterns:

  1. DRY Principle ๐Ÿ”’: Donโ€™t repeat yourself - define once, use everywhere
  2. Flexibility ๐Ÿ’ป: Easily create variations of test data
  3. Readability ๐Ÿ“–: Tests focus on behavior, not data setup
  4. Maintainability ๐Ÿ”ง: Change data structure in one place

Real-world example: Imagine testing an e-commerce system ๐Ÿ›’. With factories, you can quickly create users, products, and orders without copying code everywhere!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Factory Pattern

Letโ€™s start with a friendly example:

# ๐Ÿ‘‹ Hello, Test Factories!
from dataclasses import dataclass
from datetime import datetime, timedelta
import random

# ๐ŸŽจ Our domain model
@dataclass
class User:
    name: str      # ๐Ÿ‘ค User's name
    email: str     # ๐Ÿ“ง User's email
    age: int       # ๐ŸŽ‚ User's age
    is_active: bool = True  # โœ… Active by default

# ๐Ÿญ Simple factory function
def create_user(name=None, email=None, age=None, is_active=True):
    """Create a test user with sensible defaults! ๐ŸŽฏ"""
    return User(
        name=name or f"TestUser{random.randint(1000, 9999)}",
        email=email or f"test{random.randint(1000, 9999)}@example.com",
        age=age or random.randint(18, 65),
        is_active=is_active
    )

# ๐ŸŽฎ Let's use it!
user1 = create_user()
print(f"Created: {user1.name} ({user1.email}) ๐ŸŽ‰")

# ๐ŸŽจ Create specific user
admin = create_user(name="Admin", email="[email protected]", age=30)
print(f"Admin: {admin.name} is ready! ๐Ÿ›ก๏ธ")

๐Ÿ’ก Explanation: Notice how we provide sensible defaults while allowing overrides. This makes tests readable and flexible!

๐ŸŽฏ Builder Pattern

Hereโ€™s a more sophisticated builder pattern:

# ๐Ÿ—๏ธ Builder pattern for complex objects
class OrderBuilder:
    """Build test orders step by step! ๐Ÿš€"""
    
    def __init__(self):
        self.order_id = f"ORD-{random.randint(10000, 99999)}"
        self.customer = None
        self.items = []
        self.status = "pending"
        self.created_at = datetime.now()
        
    def with_customer(self, customer):
        """Add a customer ๐Ÿ‘ค"""
        self.customer = customer
        return self
        
    def with_item(self, name, price, quantity=1):
        """Add an item to the order ๐Ÿ“ฆ"""
        self.items.append({
            "name": name,
            "price": price,
            "quantity": quantity,
            "emoji": "๐Ÿ“ฆ"
        })
        return self
        
    def with_status(self, status):
        """Set order status ๐Ÿ“Š"""
        self.status = status
        return self
        
    def build(self):
        """Build the final order! ๐ŸŽ‰"""
        return {
            "id": self.order_id,
            "customer": self.customer,
            "items": self.items,
            "total": sum(item["price"] * item["quantity"] for item in self.items),
            "status": self.status,
            "created_at": self.created_at
        }

# ๐ŸŽฎ Using the builder
order = (OrderBuilder()
         .with_customer(create_user(name="Alice"))
         .with_item("Python Book", 29.99)
         .with_item("Coffee", 4.99, quantity=2)
         .with_status("processing")
         .build())

print(f"Order {order['id']} total: ${order['total']:.2f} ๐Ÿ’ฐ")

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce Test Factory

Letโ€™s build a complete test data factory system:

# ๐Ÿ›๏ธ E-commerce test factory
from typing import List, Optional
from faker import Faker

fake = Faker()

class Product:
    def __init__(self, name: str, price: float, category: str, stock: int):
        self.id = f"PROD-{random.randint(1000, 9999)}"
        self.name = name
        self.price = price
        self.category = category
        self.stock = stock
        self.emoji = self._get_emoji()
    
    def _get_emoji(self):
        """Assign fun emojis based on category! ๐ŸŽจ"""
        emojis = {
            "electronics": "๐Ÿ“ฑ",
            "books": "๐Ÿ“š",
            "food": "๐Ÿ•",
            "clothing": "๐Ÿ‘•",
            "toys": "๐ŸŽฎ"
        }
        return emojis.get(self.category, "๐Ÿ“ฆ")

class TestDataFactory:
    """Your one-stop shop for test data! ๐Ÿญ"""
    
    @staticmethod
    def create_product(name=None, price=None, category=None, stock=None):
        """Create a test product ๐Ÿ“ฆ"""
        categories = ["electronics", "books", "food", "clothing", "toys"]
        return Product(
            name=name or fake.catch_phrase(),
            price=price or round(random.uniform(9.99, 299.99), 2),
            category=category or random.choice(categories),
            stock=stock or random.randint(0, 100)
        )
    
    @staticmethod
    def create_shopping_cart(user=None, items=None):
        """Create a test shopping cart ๐Ÿ›’"""
        return {
            "id": f"CART-{random.randint(10000, 99999)}",
            "user": user or create_user(),
            "items": items or [],
            "created_at": datetime.now(),
            "updated_at": datetime.now()
        }
    
    @staticmethod
    def create_order_batch(count=5):
        """Create multiple test orders at once! ๐Ÿš€"""
        orders = []
        for _ in range(count):
            user = create_user()
            builder = OrderBuilder().with_customer(user)
            
            # Add random products
            for _ in range(random.randint(1, 5)):
                product = TestDataFactory.create_product()
                builder.with_item(
                    product.name, 
                    product.price, 
                    random.randint(1, 3)
                )
            
            orders.append(builder.build())
        return orders

# ๐ŸŽฎ Let's test our factory!
print("Creating test data... ๐Ÿ—๏ธ")

# Create products
laptop = TestDataFactory.create_product(
    name="Gaming Laptop",
    category="electronics",
    price=1299.99
)
print(f"{laptop.emoji} {laptop.name}: ${laptop.price}")

# Create batch of orders
orders = TestDataFactory.create_order_batch(3)
for order in orders:
    print(f"๐Ÿ“ฆ Order {order['id']}: {len(order['items'])} items, ${order['total']:.2f}")

๐ŸŽฏ Try it yourself: Add a method to create related data (user with their order history)!

๐ŸŽฎ Example 2: Test Fixture Builder

Letโ€™s create a sophisticated fixture builder:

# ๐Ÿ† Advanced test fixture builder
class TestFixtures:
    """Build complete test scenarios! ๐ŸŒŸ"""
    
    def __init__(self):
        self.users = []
        self.products = []
        self.orders = []
        self.reviews = []
    
    def create_scenario(self, name):
        """Create named test scenarios ๐ŸŽฌ"""
        scenarios = {
            "happy_path": self._happy_path_scenario,
            "edge_case": self._edge_case_scenario,
            "stress_test": self._stress_test_scenario
        }
        
        if name in scenarios:
            print(f"๐ŸŽฌ Creating '{name}' scenario...")
            return scenarios[name]()
        else:
            raise ValueError(f"Unknown scenario: {name}")
    
    def _happy_path_scenario(self):
        """Normal user flow ๐Ÿ˜Š"""
        # Create active user
        user = create_user(name="Happy User", is_active=True)
        self.users.append(user)
        
        # Create some products
        products = [
            TestDataFactory.create_product(category="books", stock=50),
            TestDataFactory.create_product(category="electronics", stock=20)
        ]
        self.products.extend(products)
        
        # Create successful order
        order = (OrderBuilder()
                .with_customer(user)
                .with_item(products[0].name, products[0].price)
                .with_status("delivered")
                .build())
        self.orders.append(order)
        
        return {
            "users": self.users,
            "products": self.products,
            "orders": self.orders,
            "emoji": "โœ…"
        }
    
    def _edge_case_scenario(self):
        """Edge cases and boundaries ๐Ÿ”"""
        # User with special characters
        user = create_user(
            name="Josรฉ O'Brien-Smith",
            email="[email protected]"
        )
        self.users.append(user)
        
        # Product with zero stock
        out_of_stock = TestDataFactory.create_product(
            name="Rare Item",
            stock=0,
            price=9999.99
        )
        self.products.append(out_of_stock)
        
        # Empty order
        empty_order = (OrderBuilder()
                      .with_customer(user)
                      .with_status("cancelled")
                      .build())
        self.orders.append(empty_order)
        
        return {
            "users": self.users,
            "products": self.products,
            "orders": self.orders,
            "emoji": "๐Ÿ”"
        }
    
    def _stress_test_scenario(self):
        """Large volume test data ๐Ÿš€"""
        # Create many users
        print("Creating 100 users... ๐Ÿ‘ฅ")
        self.users = [create_user() for _ in range(100)]
        
        # Create many products
        print("Creating 50 products... ๐Ÿ“ฆ")
        self.products = [TestDataFactory.create_product() for _ in range(50)]
        
        # Create many orders
        print("Creating 200 orders... ๐Ÿ›’")
        self.orders = TestDataFactory.create_order_batch(200)
        
        return {
            "users": self.users,
            "products": self.products,
            "orders": self.orders,
            "emoji": "๐Ÿš€"
        }

# ๐ŸŽฎ Using fixtures in tests
fixtures = TestFixtures()

# Create different scenarios
happy = fixtures.create_scenario("happy_path")
print(f"{happy['emoji']} Happy path: {len(happy['orders'])} orders")

edge = fixtures.create_scenario("edge_case")
print(f"{edge['emoji']} Edge cases ready for testing!")

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Factory with Traits

When youโ€™re ready to level up, try this advanced pattern:

# ๐ŸŽฏ Advanced factory with traits
class UserFactory:
    """Flexible user factory with traits! โœจ"""
    
    base_defaults = {
        "name": lambda: fake.name(),
        "email": lambda: fake.email(),
        "age": lambda: random.randint(18, 65),
        "is_active": True,
        "role": "user"
    }
    
    traits = {
        "admin": {
            "role": "admin",
            "is_active": True,
            "permissions": ["read", "write", "delete"]
        },
        "inactive": {
            "is_active": False,
            "deactivated_at": lambda: datetime.now()
        },
        "premium": {
            "role": "premium_user",
            "subscription_expires": lambda: datetime.now() + timedelta(days=365),
            "features": ["no_ads", "priority_support", "extra_storage"]
        },
        "new": {
            "created_at": lambda: datetime.now() - timedelta(hours=1),
            "onboarding_completed": False
        }
    }
    
    @classmethod
    def create(cls, *traits, **overrides):
        """Create user with traits and overrides! ๐Ÿช„"""
        # Start with base defaults
        attrs = {}
        for key, value in cls.base_defaults.items():
            attrs[key] = value() if callable(value) else value
        
        # Apply traits
        for trait in traits:
            if trait in cls.traits:
                trait_attrs = cls.traits[trait]
                for key, value in trait_attrs.items():
                    attrs[key] = value() if callable(value) else value
        
        # Apply overrides
        attrs.update(overrides)
        
        return attrs

# ๐ŸŽฎ Using traits
admin = UserFactory.create("admin", name="Super Admin")
print(f"๐Ÿ›ก๏ธ Admin: {admin['name']} with permissions: {admin['permissions']}")

premium_inactive = UserFactory.create("premium", "inactive")
print(f"๐Ÿ’Ž Premium but inactive: {premium_inactive['is_active']}")

new_user = UserFactory.create("new", age=25)
print(f"๐ŸŒŸ New user: onboarding = {new_user['onboarding_completed']}")

๐Ÿ—๏ธ Nested Builder Pattern

For the brave developers working with complex data:

# ๐Ÿš€ Nested builders for complex structures
class CompanyBuilder:
    """Build complex company test data! ๐Ÿข"""
    
    def __init__(self):
        self.name = fake.company()
        self.employees = []
        self.departments = []
        self.projects = []
    
    def with_department(self, name, manager=None):
        """Add a department ๐Ÿ›๏ธ"""
        dept = {
            "id": f"DEPT-{random.randint(100, 999)}",
            "name": name,
            "manager": manager or create_user(),
            "employees": [],
            "emoji": "๐Ÿ›๏ธ"
        }
        self.departments.append(dept)
        return self
    
    def with_employee_in_department(self, dept_name, **employee_kwargs):
        """Add employee to specific department ๐Ÿ‘ฅ"""
        employee = create_user(**employee_kwargs)
        
        # Find or create department
        dept = next((d for d in self.departments if d["name"] == dept_name), None)
        if not dept:
            self.with_department(dept_name)
            dept = self.departments[-1]
        
        dept["employees"].append(employee)
        self.employees.append(employee)
        return self
    
    def with_project(self, name, team_size=5):
        """Add a project with team ๐Ÿš€"""
        project = {
            "id": f"PROJ-{random.randint(1000, 9999)}",
            "name": name,
            "team": [create_user() for _ in range(team_size)],
            "status": random.choice(["planning", "active", "completed"]),
            "emoji": "๐Ÿš€"
        }
        self.projects.append(project)
        return self
    
    def build(self):
        """Build the company! ๐ŸŽ‰"""
        return {
            "name": self.name,
            "employees": self.employees,
            "departments": self.departments,
            "projects": self.projects,
            "total_employees": len(self.employees),
            "founded": datetime.now() - timedelta(days=random.randint(365, 3650))
        }

# ๐ŸŽฎ Building a complex test company
tech_company = (CompanyBuilder()
                .with_department("Engineering")
                .with_employee_in_department("Engineering", name="Alice", role="developer")
                .with_employee_in_department("Engineering", name="Bob", role="developer")
                .with_department("Marketing")
                .with_employee_in_department("Marketing", name="Carol", role="manager")
                .with_project("New Feature", team_size=3)
                .with_project("Bug Fixes", team_size=2)
                .build())

print(f"๐Ÿข {tech_company['name']}: {tech_company['total_employees']} employees")
for dept in tech_company['departments']:
    print(f"  {dept['emoji']} {dept['name']}: {len(dept['employees'])} people")

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Hardcoded Test Data

# โŒ Wrong way - brittle hardcoded data!
def test_user_creation():
    user = {
        "name": "John Doe",  # What if this conflicts? ๐Ÿ˜ฐ
        "email": "[email protected]",  # Already exists? ๐Ÿ’ฅ
        "age": 30
    }
    # Test might fail due to data conflicts!

# โœ… Correct way - use factories!
def test_user_creation():
    user = create_user()  # Always unique! ๐Ÿ›ก๏ธ
    # Test focuses on behavior, not data setup

๐Ÿคฏ Pitfall 2: Overly Complex Factories

# โŒ Dangerous - too many responsibilities!
class MegaFactory:
    def create_everything(self, scenario_type, with_history=True, 
                         include_analytics=True, generate_reports=True):
        # 500 lines of complex logic... ๐Ÿ’ฅ
        pass

# โœ… Safe - focused factories!
class UserFactory:
    """Just creates users! ๐Ÿ‘ค"""
    pass

class OrderFactory:
    """Just creates orders! ๐Ÿ“ฆ"""
    pass

# Compose them as needed
user = UserFactory.create()
order = OrderFactory.create(customer=user)

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Keep It Simple: Factories should do one thing well
  2. ๐Ÿ“ Use Sensible Defaults: Make the common case easy
  3. ๐Ÿ›ก๏ธ Ensure Uniqueness: Avoid test data conflicts
  4. ๐ŸŽจ Make It Readable: Test code should tell a story
  5. โœจ Support Overrides: Allow customization when needed

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Blog Test Data System

Create a complete test data system for a blogging platform:

๐Ÿ“‹ Requirements:

  • โœ… Users with different roles (author, editor, reader)
  • ๐Ÿท๏ธ Blog posts with categories and tags
  • ๐Ÿ’ฌ Comments with nested replies
  • ๐Ÿ“Š Analytics data (views, likes, shares)
  • ๐ŸŽจ Each entity needs appropriate test variations!

๐Ÿš€ Bonus Points:

  • Add state machines (draft โ†’ published โ†’ archived)
  • Create related data graphs (user โ†’ posts โ†’ comments)
  • Generate realistic time-based data

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
# ๐ŸŽฏ Blog test data system!
from datetime import datetime, timedelta
import random
from faker import Faker

fake = Faker()

class BlogFactory:
    """Complete blog test data factory! ๐Ÿ“"""
    
    @staticmethod
    def create_user(role="reader", **kwargs):
        """Create blog users with roles ๐Ÿ‘ค"""
        base_user = {
            "id": f"USER-{random.randint(10000, 99999)}",
            "username": fake.user_name(),
            "email": fake.email(),
            "role": role,
            "created_at": fake.date_time_between(start_date="-2y", end_date="now"),
            "is_active": True
        }
        
        # Role-specific attributes
        if role == "author":
            base_user["bio"] = fake.text(max_nb_chars=200)
            base_user["verified"] = True
            base_user["emoji"] = "โœ๏ธ"
        elif role == "editor":
            base_user["permissions"] = ["edit", "publish", "delete"]
            base_user["emoji"] = "๐Ÿ“"
        else:
            base_user["emoji"] = "๐Ÿ‘ค"
        
        base_user.update(kwargs)
        return base_user
    
    @staticmethod
    def create_post(author=None, status="published", **kwargs):
        """Create blog posts ๐Ÿ“„"""
        statuses = {
            "draft": "๐Ÿ“",
            "published": "โœ…",
            "archived": "๐Ÿ“ฆ"
        }
        
        post = {
            "id": f"POST-{random.randint(10000, 99999)}",
            "title": fake.sentence(nb_words=6),
            "content": fake.text(max_nb_chars=1000),
            "author": author or BlogFactory.create_user(role="author"),
            "status": status,
            "emoji": statuses.get(status, "๐Ÿ“„"),
            "created_at": fake.date_time_between(start_date="-1y", end_date="now"),
            "updated_at": datetime.now(),
            "categories": random.sample(["Tech", "Travel", "Food", "Lifestyle"], k=2),
            "tags": [fake.word() for _ in range(random.randint(2, 5))],
            "views": random.randint(0, 10000) if status == "published" else 0,
            "likes": random.randint(0, 500) if status == "published" else 0
        }
        
        post.update(kwargs)
        return post
    
    @staticmethod
    def create_comment(post=None, author=None, parent=None):
        """Create comments with replies ๐Ÿ’ฌ"""
        return {
            "id": f"COMM-{random.randint(10000, 99999)}",
            "post": post or BlogFactory.create_post(),
            "author": author or BlogFactory.create_user(),
            "content": fake.text(max_nb_chars=200),
            "parent": parent,  # For nested replies
            "created_at": fake.date_time_between(start_date="-6m", end_date="now"),
            "likes": random.randint(0, 50),
            "emoji": "๐Ÿ’ฌ" if not parent else "โ†ฉ๏ธ"
        }
    
    @staticmethod
    def create_blog_with_content(num_posts=5):
        """Create complete blog with related data! ๐ŸŒŸ"""
        # Create authors and editor
        authors = [BlogFactory.create_user(role="author") for _ in range(3)]
        editor = BlogFactory.create_user(role="editor", username="chief_editor")
        readers = [BlogFactory.create_user(role="reader") for _ in range(10)]
        
        # Create posts
        posts = []
        for _ in range(num_posts):
            author = random.choice(authors)
            status = random.choice(["draft", "published", "published", "published"])  # More published
            post = BlogFactory.create_post(author=author, status=status)
            posts.append(post)
            
            # Add comments to published posts
            if post["status"] == "published":
                num_comments = random.randint(0, 10)
                for _ in range(num_comments):
                    commenter = random.choice(readers + authors)
                    comment = BlogFactory.create_comment(post=post, author=commenter)
                    
                    # Sometimes add replies
                    if random.random() > 0.7:
                        reply_author = random.choice(readers + authors)
                        BlogFactory.create_comment(
                            post=post, 
                            author=reply_author, 
                            parent=comment
                        )
        
        return {
            "authors": authors,
            "editor": editor,
            "readers": readers,
            "posts": posts,
            "stats": {
                "total_posts": len(posts),
                "published": len([p for p in posts if p["status"] == "published"]),
                "total_views": sum(p["views"] for p in posts),
                "total_likes": sum(p["likes"] for p in posts)
            }
        }

# ๐ŸŽฎ Test it out!
blog_data = BlogFactory.create_blog_with_content(num_posts=10)

print("๐Ÿ“Š Blog Statistics:")
print(f"  โœ๏ธ Authors: {len(blog_data['authors'])}")
print(f"  ๐Ÿ“ Total Posts: {blog_data['stats']['total_posts']}")
print(f"  โœ… Published: {blog_data['stats']['published']}")
print(f"  ๐Ÿ‘€ Total Views: {blog_data['stats']['total_views']:,}")
print(f"  โค๏ธ Total Likes: {blog_data['stats']['total_likes']:,}")

# Show some posts
print("\n๐Ÿ“ฐ Recent Posts:")
for post in blog_data['posts'][:3]:
    print(f"  {post['emoji']} {post['title'][:50]}...")
    print(f"     by {post['author']['username']} | {post['likes']} likes")

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create test factories with confidence ๐Ÿ’ช
  • โœ… Build complex test data using builder patterns ๐Ÿ›ก๏ธ
  • โœ… Apply factory traits for flexible variations ๐ŸŽฏ
  • โœ… Avoid common pitfalls in test data management ๐Ÿ›
  • โœ… Write maintainable tests with clean data setup! ๐Ÿš€

Remember: Good test data is the foundation of reliable tests. Factories and builders are your friends! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered test data management with factories and builders!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the blog factory exercise above
  2. ๐Ÿ—๏ธ Refactor your existing tests to use factories
  3. ๐Ÿ“š Explore libraries like factory_boy or pytest-factoryboy
  4. ๐ŸŒŸ Share your test data patterns with your team!

Remember: Every testing expert started by writing better test data. Keep practicing, keep improving, and most importantly, have fun! ๐Ÿš€


Happy testing! ๐ŸŽ‰๐Ÿš€โœจ