Synaptic Labs Blog

Vibe Coding: Testing & Quality Assurance

Written by Professor Synapse | Aug 20, 2025 4:00:00 PM

Greetings, meticulous testers of digital magic! Professor Synapse here with the essential art of validating your spells before they're released into the world. Today we explore the testing principles that ensure your AI-generated code works correctly, consistently, and safely.

When your AI familiar creates code at lightning speed, the temptation is to trust its output and deploy immediately. But wise wizards know that even the most powerful magic must be tested thoroughly. Think of testing as your quality control laboratory where you verify that every enchantment performs exactly as intended.

Great testing is like having a team of dedicated apprentices who methodically check every aspect of your spells, ensuring they work in all conditions and handle unexpected situations gracefully.

Test-Driven Development (TDD) - Testing Before Casting

"Write tests first, then make them pass"

TDD is like sketching the blueprint for your magical artifact before you start building it. By defining what success looks like first, you create focused, purposeful code that solves real problems.

❌ Code-First Approach - Building Without a Plan

// BAD: Writing code first, testing as an afterthought
class ShoppingCart {
    constructor() {
        this.items = []
        this.discounts = []
    }
    
    function addItem(item) {
        // Implemented without clear requirements
        this.items.push(item)
        // What about duplicate items?
        // What about invalid items?
        // What about quantity limits?
    }
    
    function calculateTotal() {
        // Complex logic written without tests
        let total = 0
        for (let item of this.items) {
            total += item.price * item.quantity
        }
        
        // Discount logic added later
        for (let discount of this.discounts) {
            if (discount.type === 'percentage') {
                total *= (1 - discount.value)
            } else if (discount.type === 'fixed') {
                total -= discount.value
            }
        }
        
        // Edge cases discovered later in production
        return total < 0 ? 0 : total
    }
}

// Problems:
// - Requirements unclear during implementation
// - Edge cases discovered too late
// - Tests don't drive design decisions
// - Difficult to refactor safely

The Problem: Without tests to guide development, you write code that might work for the happy path but fails to handle edge cases or changing requirements gracefully.

✅ Test-Driven Development - Blueprint First

// GOOD: Tests written first to define behavior

// STEP 1: Write failing tests that define requirements
describe("ShoppingCart", function() {
    
    describe("addItem", function() {
        test("should add new item to empty cart", function() {
            // Arrange
            const cart = new ShoppingCart()
            const item = { id: "book1", name: "Book", price: 10, quantity: 1 }
            
            // Act
            cart.addItem(item)
            
            // Assert
            expect(cart.getItems()).toHaveLength(1)
            expect(cart.getItems()[0]).toEqual(item)
        })
        
        test("should increase quantity when adding duplicate item", function() {
            // Arrange
            const cart = new ShoppingCart()
            const item = { id: "book1", name: "Book", price: 10, quantity: 1 }
            
            // Act
            cart.addItem(item)
            cart.addItem({ ...item, quantity: 2 })
            
            // Assert
            expect(cart.getItems()).toHaveLength(1)
            expect(cart.getItems()[0].quantity).toBe(3)
        })
        
        test("should throw error for invalid item", function() {
            // Arrange
            const cart = new ShoppingCart()
            
            // Act & Assert
            expect(() => cart.addItem(null)).toThrow("Item cannot be null")
            expect(() => cart.addItem({ name: "Book" })).toThrow("Item must have valid price")
            expect(() => cart.addItem({ name: "Book", price: -5 })).toThrow("Price must be positive")
        })
    })
})

AI Prompt Example:

"Use Test-Driven Development to implement this feature. First write comprehensive tests that define the expected behavior, including edge cases and error conditions. Then implement just enough code to make the tests pass."

Test Pyramid - Balanced Magical Validation

"Many unit tests, some integration tests, few end-to-end tests"

The test pyramid is like organizing your magical defenses - many quick, focused ward spells at the foundation, some broader protective enchantments in the middle, and a few comprehensive shield spells covering the entire realm.

Synaptic Labs AI education attribution required

❌ Inverted Pyramid - Top-Heavy Testing

// BAD: Too many slow, brittle end-to-end tests, few unit tests

// Mostly E2E tests (slow, brittle, expensive)
describe("E2E Shopping Tests", function() {
    test("complete shopping flow", function() {
        // Slow: Boots up entire application
        browser.visit('/login')
        browser.fill('email', 'test@example.com')
        browser.fill('password', 'password123')
        browser.click('Login')
        
        browser.visit('/products')
        browser.click('.product[data-id="book1"] .add-to-cart')
        browser.visit('/cart')
        browser.click('.checkout-button')
        
        browser.fill('card-number', '4111111111111111')
        browser.fill('expiry', '12/25')
        browser.click('.submit-payment')
        
        // Brittle: Breaks when UI changes
        expect(browser.text('.success-message')).toContain('Order confirmed')
    })
})

// Problems:
// - Tests are slow (minutes to run)
// - Tests are brittle (break on UI changes)
// - Hard to debug failures
// - Expensive to maintain
// - Poor feedback loop

✅ Proper Test Pyramid - Balanced Coverage

// GOOD: Many fast unit tests, some integration tests, few E2E tests

// FOUNDATION: Many unit tests (fast, focused, reliable)
describe("ShoppingCart Unit Tests", function() {
    test("addItem should handle valid item", function() {
        // Fast: No external dependencies
        const cart = new ShoppingCart()
        const item = { id: "book1", name: "Book", price: 10, quantity: 1 }
        
        cart.addItem(item)
        
        expect(cart.getItems()).toHaveLength(1)
        expect(cart.getItems()[0]).toEqual(item)
    })
    
    test("calculateTotal should handle empty cart", function() {
        const cart = new ShoppingCart()
        expect(cart.calculateTotal()).toBe(0)
    })
    
    test("calculateTotal should apply percentage discount", function() {
        const cart = new ShoppingCart()
        cart.addItem({ id: "1", price: 100, quantity: 1 })
        cart.applyDiscount({ type: "percentage", value: 0.10 })
        
        expect(cart.calculateTotal()).toBe(90)
    })
})

// MIDDLE: Some integration tests (moderate speed, broader scope)
describe("Shopping Integration Tests", function() {
    test("should complete order workflow", function() {
        // Tests interaction between services
        const cart = new ShoppingCart()
        const paymentService = new PaymentService(mockPaymentGateway)
        const orderService = new OrderService(cart, paymentService, mockDatabase)
        
        // Add items to cart
        cart.addItem({ id: "book1", price: 20, quantity: 1 })
        cart.addItem({ id: "book2", price: 15, quantity: 1 })
        
        // Process order
        const order = orderService.createOrder({
            customerEmail: "test@example.com",
            paymentMethod: { type: "card", number: "4111111111111111" }
        })
        
        expect(order.status).toBe("confirmed")
        expect(order.total).toBe(35)
        expect(mockDatabase.orders).toHaveLength(1)
    })
})

// TOP: Few E2E tests (slow, but comprehensive)
describe("E2E Critical User Journeys", function() {
    test("user can complete purchase of single item", function() {
        // Only test critical happy path end-to-end
        browser.visit('/products')
        browser.click('[data-testid="add-to-cart-book1"]')
        browser.visit('/checkout')
        
        browser.fill('[data-testid="email"]', 'test@example.com')
        browser.fill('[data-testid="card-number"]', '4111111111111111')
        browser.click('[data-testid="submit-order"]')
        
        expect(browser.text('[data-testid="order-confirmation"]'))
            .toContain('Order confirmed')
    })
})

F.I.R.S.T. Principles - Quality Test Characteristics

"Fast, Independent, Repeatable, Self-Validating, Timely"

F.I.R.S.T. principles are like the fundamental qualities of good magical experiments - they must be quick to perform, work in isolation, give consistent results, clearly show success or failure, and be conducted at the right time.

✅ Following F.I.R.S.T. - Quality Tests

// GOOD: Tests that follow F.I.R.S.T. principles

describe("User Service Tests", function() {
    
    // FAST: No external dependencies, runs in milliseconds
    test("should create user with valid data", function() {
        // Fast: Uses mock dependencies
        const mockDatabase = new MockDatabase()
        const mockEmailService = new MockEmailService()
        const userService = new UserService(mockDatabase, mockEmailService)
        
        const userData = {
            email: "test@example.com",
            password: "password123",
            firstName: "John",
            lastName: "Doe"
        }
        
        const user = userService.createUser(userData)
        
        expect(user.email).toBe("test@example.com")
        expect(user.displayName).toBe("John Doe")
        expect(mockDatabase.users).toHaveLength(1)
    })
    
    // INDEPENDENT: Each test sets up its own data
    test("should throw error for duplicate email", function() {
        // Independent: Creates its own fresh mock database
        const mockDatabase = new MockDatabase()
        mockDatabase.addUser({ email: "existing@example.com" })
        
        const userService = new UserService(mockDatabase, new MockEmailService())
        
        expect(() => userService.createUser({
            email: "existing@example.com",
            password: "password123"
        })).toThrow("Email already exists")
    })
    
    // REPEATABLE: Uses mocks, always produces same result
    test("should send welcome email on user creation", function() {
        // Repeatable: Uses mock that always behaves the same way
        const mockEmailService = new MockEmailService()
        const userService = new UserService(new MockDatabase(), mockEmailService)
        
        userService.createUser({
            email: "test@example.com",
            password: "password123"
        })
        
        expect(mockEmailService.sentEmails).toHaveLength(1)
        expect(mockEmailService.sentEmails[0].to).toBe("test@example.com")
        expect(mockEmailService.sentEmails[0].subject).toBe("Welcome!")
    })
    
    // SELF-VALIDATING: Clear pass/fail with assertions
    test("should format display name correctly", function() {
        const userService = new UserService(new MockDatabase(), new MockEmailService())
        
        const user = userService.createUser({
            firstName: "John",
            lastName: "Doe",
            email: "john@example.com",
            password: "password123"
        })
        
        // Self-validating: Clear assertion that computer can verify
        expect(user.displayName).toBe("John Doe")
    })
})

Given-When-Then Pattern - Clear Test Structure

"Given a context, when an action occurs, then expect specific outcomes"

Given-When-Then is like writing a clear magical experiment protocol - first establish the conditions, then perform the action, then verify the results.

✅ Structured Test Scenarios

// GOOD: Clear Given-When-Then structure

describe("Shopping Cart Discount Application", function() {
    
    test("should apply percentage discount to cart with multiple items", function() {
        // GIVEN: A cart with multiple items
        const cart = new ShoppingCart()
        cart.addItem({ id: "book1", name: "Programming Book", price: 50, quantity: 1 })
        cart.addItem({ id: "book2", name: "Design Book", price: 30, quantity: 2 })
        // Cart total: $110 (50 + 30*2)
        
        // WHEN: A 20% discount is applied
        cart.applyDiscount({ type: "percentage", value: 0.20 })
        
        // THEN: The total should be reduced by 20%
        const total = cart.calculateTotal()
        expect(total).toBe(88)  // $110 * 0.8 = $88
    })
    
    test("should not apply discount below zero", function() {
        // GIVEN: A cart with a small total and large fixed discount
        const cart = new ShoppingCart()
        cart.addItem({ id: "book1", name: "Cheap Book", price: 5, quantity: 1 })
        
        // WHEN: A fixed discount larger than the total is applied
        cart.applyDiscount({ type: "fixed", value: 10 })
        
        // THEN: The total should not go below zero
        const total = cart.calculateTotal()
        expect(total).toBe(0)
    })
})

Quality Gates for AI Collaboration

"Automated checks that prevent poor quality code from advancing"

Quality gates are like magical checkpoints that automatically verify your spells meet certain standards before they can be used in production.

✅ Comprehensive Quality Pipeline

// Quality gate configuration for AI-generated code
const qualityGates = {
    // Gate 1: Code Coverage Requirements
    coverage: {
        statements: 85,    // 85% of statements must be tested
        branches: 80,      // 80% of code branches must be tested
        functions: 90,     // 90% of functions must be tested
        lines: 85          // 85% of lines must be tested
    },
    
    // Gate 2: Test Quality Requirements
    testQuality: {
        minimumTests: 10,           // At least 10 tests per module
        maxTestDuration: 5000,      // Tests must run in under 5 seconds
        noSkippedTests: true,       // No tests marked as "skip" or "todo"
        noFocusedTests: true        // No tests marked as "only" or "fit"
    },
    
    // Gate 3: Code Quality Requirements
    codeQuality: {
        complexity: {
            cyclomatic: 10,         // Maximum cyclomatic complexity
            cognitive: 15           // Maximum cognitive complexity
        },
        maintainability: 70,        // Minimum maintainability index
        duplicatedLines: 3,         // Maximum % of duplicated lines
        technicalDebt: 30           // Maximum minutes of technical debt
    },
    
    // Gate 4: Security Requirements
    security: {
        vulnerabilities: 0,         // No high/critical vulnerabilities
        securityHotspots: 5,        // Maximum security hotspots
        noHardcodedSecrets: true    // No hardcoded passwords/keys
    }
}

Building Quality into AI-Generated Code

When working with your AI familiar on testing:

For TDD:

"Use Test-Driven Development. Write failing tests first to define the expected behavior, then implement just enough code to make the tests pass."

For Test Pyramid:

"Follow the test pyramid pattern. Create many fast unit tests, some integration tests for service interactions, and only a few end-to-end tests for critical paths."

For F.I.R.S.T.:

"Make tests Fast (no external dependencies), Independent (each stands alone), Repeatable (use mocks), Self-Validating (clear assertions), and Timely (written during development)."

For Given-When-Then:

"Structure tests with Given-When-Then pattern. Clearly separate setup, action, and verification phases."

For Quality Gates:

"Define quality standards for code coverage, complexity, and security. Create automated checks that enforce these standards."

The Foundation of Reliable Magic

When you combine these testing principles, you create:

  • Confidence: Comprehensive tests give you faith in your code
  • Speed: Fast feedback loops accelerate development
  • Quality: Automated gates maintain high standards
  • Documentation: Tests serve as living documentation
  • Safety: Refactoring becomes safe with good test coverage

Your Next Magical Steps

These testing principles ensure your AI-generated code works correctly and safely. In our next scroll, we'll explore Performance & Optimization Principles - the techniques that keep your magical systems running efficiently at scale.

Remember: Testing is not about finding bugs - it's about preventing them and building confidence in your magical creations.

Until next time, may your tests be fast and your coverage complete!

This scroll is part of our Vibe Coding Principles series, exploring how fundamental software principles enhance AI-assisted development.