Navigation

Programming

Unit Testing Tutorial for Beginners Jest Python pytest JavaScript Testing Best Practices

Learn unit testing from a developer who went from zero tests to 95% coverage at Microsoft, covering Jest, pytest, mocking, and building confidence in your code changes.

Unit Testing: How I Stopped Breaking Production on Fridays

Three months into my job at Microsoft, I deployed what seemed like a harmless one-line fix on a Friday afternoon. By Monday morning, our Azure DevOps service was partially down, affecting thousands of developers worldwide. That one line had broken a critical authentication flow, and I had no tests to catch it.

My manager didn't fire me, but she did say something that changed my career: "Maya, code without tests is just code that hasn't broken yet." That week, I learned to write tests, and I haven't deployed on Friday without them since.

The Friday Afternoon Disaster

Here's the innocent-looking code that brought down production:

// The "harmless" change
function validateUserPermissions(user, resource) {
    // Changed from === to == (seemed innocent enough)
    if (user.role == 'admin') {  // This was the bug!
        return true;
    }
    
    return user.permissions.includes(resource);
}

// What I didn't realize:
// user.role was sometimes the string "admin"
// user.role was sometimes the number 1 (for admin users)
// The == operator made 1 == 'admin' evaluate to false
// Thousands of admin users suddenly couldn't access anything

If I'd had a test, this would have been caught immediately:

// The test that would have saved my weekend
describe('validateUserPermissions', () => {
    it('should grant access to admin users regardless of role type', () => {
        const stringAdminUser = { role: 'admin', permissions: [] };
        const numericAdminUser = { role: 1, permissions: [] };
        
        expect(validateUserPermissions(stringAdminUser, 'any-resource')).toBe(true);
        expect(validateUserPermissions(numericAdminUser, 'any-resource')).toBe(true);
    });
});

Testing: The Coffee Quality Control Analogy

I finally understood testing when I thought about my favorite coffee shops. Good baristas taste every shot before serving it. They don't just hope the espresso is good - they verify it.

Unit testing is the same concept:

  • Unit: Individual coffee shot (function)
  • Test: Taste test (verification)
  • Assertion: "This tastes right" (expected outcome)
  • Mock: Using decaf when testing late at night (fake dependencies)

Setting Up Testing in JavaScript (Jest)

Let's start with a simple payment calculator for our coffee shop:

// src/paymentCalculator.js
class PaymentCalculator {
    constructor(taxRate = 0.101) {  // Seattle tax rate
        this.taxRate = taxRate;
    }
    
    calculateSubtotal(items) {
        if (!Array.isArray(items) || items.length === 0) {
            return 0;
        }
        
        return items.reduce((total, item) => {
            if (!item.price || !item.quantity) {
                throw new Error('Invalid item: missing price or quantity');
            }
            return total + (item.price * item.quantity);
        }, 0);
    }
    
    calculateTax(subtotal) {
        if (subtotal < 0) {
            throw new Error('Subtotal cannot be negative');
        }
        return Math.round(subtotal * this.taxRate * 100) / 100;
    }
    
    calculateTotal(items, discountPercent = 0) {
        const subtotal = this.calculateSubtotal(items);
        const discount = (subtotal * discountPercent) / 100;
        const discountedSubtotal = subtotal - discount;
        const tax = this.calculateTax(discountedSubtotal);
        
        return Math.round((discountedSubtotal + tax) * 100) / 100;
    }
    
    applyLoyaltyDiscount(total, loyaltyPoints) {
        const maxDiscount = total * 0.5; // Max 50% discount
        const pointsValue = loyaltyPoints * 0.01; // 1 point = $0.01
        const discount = Math.min(pointsValue, maxDiscount);
        
        return Math.round((total - discount) * 100) / 100;
    }
}

module.exports = PaymentCalculator;

Now let's test it thoroughly:

// tests/paymentCalculator.test.js
const PaymentCalculator = require('../src/paymentCalculator');

describe('PaymentCalculator', () => {
    let calculator;
    
    beforeEach(() => {
        calculator = new PaymentCalculator();
    });
    
    describe('calculateSubtotal', () => {
        it('should calculate subtotal for valid items', () => {
            const items = [
                { price: 4.50, quantity: 2 },  // 2 lattes
                { price: 3.25, quantity: 1 }   // 1 muffin
            ];
            
            const result = calculator.calculateSubtotal(items);
            
            expect(result).toBe(12.25);
        });
        
        it('should return 0 for empty array', () => {
            expect(calculator.calculateSubtotal([])).toBe(0);
        });
        
        it('should return 0 for null or undefined', () => {
            expect(calculator.calculateSubtotal(null)).toBe(0);
            expect(calculator.calculateSubtotal(undefined)).toBe(0);
        });
        
        it('should throw error for items with missing price', () => {
            const items = [{ quantity: 1 }];  // Missing price
            
            expect(() => {
                calculator.calculateSubtotal(items);
            }).toThrow('Invalid item: missing price or quantity');
        });
        
        it('should throw error for items with missing quantity', () => {
            const items = [{ price: 4.50 }];  // Missing quantity
            
            expect(() => {
                calculator.calculateSubtotal(items);
            }).toThrow('Invalid item: missing price or quantity');
        });
    });
    
    describe('calculateTax', () => {
        it('should calculate tax correctly', () => {
            const result = calculator.calculateTax(100);
            
            expect(result).toBe(10.10);  // 100 * 0.101
        });
        
        it('should round tax to 2 decimal places', () => {
            const result = calculator.calculateTax(12.34);
            
            expect(result).toBe(1.25);  // 12.34 * 0.101 = 1.2534, rounded to 1.25
        });
        
        it('should handle zero subtotal', () => {
            expect(calculator.calculateTax(0)).toBe(0);
        });
        
        it('should throw error for negative subtotal', () => {
            expect(() => {
                calculator.calculateTax(-10);
            }).toThrow('Subtotal cannot be negative');
        });
    });
    
    describe('calculateTotal', () => {
        it('should calculate total with tax', () => {
            const items = [{ price: 10, quantity: 1 }];
            
            const result = calculator.calculateTotal(items);
            
            expect(result).toBe(11.01);  // 10 + (10 * 0.101)
        });
        
        it('should apply discount before tax', () => {
            const items = [{ price: 100, quantity: 1 }];
            
            const result = calculator.calculateTotal(items, 10); // 10% discount
            
            // 100 - 10 (discount) = 90
            // 90 + 9.09 (tax) = 99.09
            expect(result).toBe(99.09);
        });
        
        it('should handle 100% discount', () => {
            const items = [{ price: 100, quantity: 1 }];
            
            const result = calculator.calculateTotal(items, 100);
            
            expect(result).toBe(0);
        });
    });
    
    describe('applyLoyaltyDiscount', () => {
        it('should apply loyalty points as discount', () => {
            const result = calculator.applyLoyaltyDiscount(20, 500); // 500 points = $5
            
            expect(result).toBe(15.00);
        });
        
        it('should cap discount at 50% of total', () => {
            const result = calculator.applyLoyaltyDiscount(20, 5000); // 5000 points = $50
            
            expect(result).toBe(10.00);  // Max 50% discount = $10 off
        });
        
        it('should handle zero loyalty points', () => {
            const result = calculator.applyLoyaltyDiscount(20, 0);
            
            expect(result).toBe(20.00);
        });
    });
});

Testing in Python with pytest

Now let's look at the same concepts in Python:

# src/payment_calculator.py
from typing import List, Dict, Union
import math

class PaymentCalculator:
    def __init__(self, tax_rate: float = 0.101):
        self.tax_rate = tax_rate
    
    def calculate_subtotal(self, items: List[Dict[str, Union[float, int]]]) -> float:
        """Calculate subtotal from list of items."""
        if not items:
            return 0.0
        
        total = 0
        for item in items:
            if 'price' not in item or 'quantity' not in item:
                raise ValueError("Invalid item: missing price or quantity")
            
            if item['price'] < 0 or item['quantity'] < 0:
                raise ValueError("Price and quantity must be non-negative")
            
            total += item['price'] * item['quantity']
        
        return round(total, 2)
    
    def calculate_tax(self, subtotal: float) -> float:
        """Calculate tax on subtotal."""
        if subtotal < 0:
            raise ValueError("Subtotal cannot be negative")
        
        return round(subtotal * self.tax_rate, 2)
    
    def calculate_total(self, items: List[Dict], discount_percent: float = 0) -> float:
        """Calculate total with discount and tax."""
        subtotal = self.calculate_subtotal(items)
        discount = (subtotal * discount_percent) / 100
        discounted_subtotal = subtotal - discount
        tax = self.calculate_tax(discounted_subtotal)
        
        return round(discounted_subtotal + tax, 2)
    
    def apply_loyalty_discount(self, total: float, loyalty_points: int) -> float:
        """Apply loyalty points as discount."""
        max_discount = total * 0.5  # Max 50% discount
        points_value = loyalty_points * 0.01  # 1 point = $0.01
        discount = min(points_value, max_discount)
        
        return round(total - discount, 2)
# tests/test_payment_calculator.py
import pytest
from src.payment_calculator import PaymentCalculator

class TestPaymentCalculator:
    
    @pytest.fixture
    def calculator(self):
        """Create a fresh calculator instance for each test."""
        return PaymentCalculator()
    
    def test_calculate_subtotal_valid_items(self, calculator):
        """Test subtotal calculation with valid items."""
        items = [
            {'price': 4.50, 'quantity': 2},  # 2 lattes
            {'price': 3.25, 'quantity': 1}   # 1 muffin
        ]
        
        result = calculator.calculate_subtotal(items)
        
        assert result == 12.25
    
    def test_calculate_subtotal_empty_list(self, calculator):
        """Test subtotal with empty items list."""
        assert calculator.calculate_subtotal([]) == 0.0
    
    def test_calculate_subtotal_missing_price(self, calculator):
        """Test error handling for missing price."""
        items = [{'quantity': 1}]  # Missing price
        
        with pytest.raises(ValueError, match="Invalid item: missing price or quantity"):
            calculator.calculate_subtotal(items)
    
    def test_calculate_subtotal_missing_quantity(self, calculator):
        """Test error handling for missing quantity."""
        items = [{'price': 4.50}]  # Missing quantity
        
        with pytest.raises(ValueError, match="Invalid item: missing price or quantity"):
            calculator.calculate_subtotal(items)
    
    def test_calculate_subtotal_negative_price(self, calculator):
        """Test error handling for negative price."""
        items = [{'price': -4.50, 'quantity': 1}]
        
        with pytest.raises(ValueError, match="Price and quantity must be non-negative"):
            calculator.calculate_subtotal(items)
    
    def test_calculate_tax_normal(self, calculator):
        """Test normal tax calculation."""
        result = calculator.calculate_tax(100)
        
        assert result == 10.10
    
    def test_calculate_tax_rounding(self, calculator):
        """Test tax calculation with rounding."""
        result = calculator.calculate_tax(12.34)
        
        assert result == 1.25  # 12.34 * 0.101 = 1.2534, rounded to 1.25
    
    def test_calculate_tax_zero(self, calculator):
        """Test tax calculation with zero subtotal."""
        assert calculator.calculate_tax(0) == 0
    
    def test_calculate_tax_negative_subtotal(self, calculator):
        """Test error handling for negative subtotal."""
        with pytest.raises(ValueError, match="Subtotal cannot be negative"):
            calculator.calculate_tax(-10)
    
    def test_calculate_total_with_tax(self, calculator):
        """Test total calculation with tax."""
        items = [{'price': 10, 'quantity': 1}]
        
        result = calculator.calculate_total(items)
        
        assert result == 11.01  # 10 + (10 * 0.101)
    
    def test_calculate_total_with_discount(self, calculator):
        """Test total calculation with discount applied before tax."""
        items = [{'price': 100, 'quantity': 1}]
        
        result = calculator.calculate_total(items, 10)  # 10% discount
        
        # 100 - 10 (discount) = 90
        # 90 + 9.09 (tax) = 99.09
        assert result == 99.09
    
    @pytest.mark.parametrize("points,expected", [
        (500, 15.00),   # 500 points = $5 discount
        (1000, 10.00),  # 1000 points = $10 discount (50% cap)
        (5000, 10.00),  # 5000 points = $50, but capped at 50% = $10
        (0, 20.00),     # No points = no discount
    ])
    def test_apply_loyalty_discount_parametrized(self, calculator, points, expected):
        """Test loyalty discount with various point values."""
        result = calculator.apply_loyalty_discount(20, points)
        assert result == expected
    
    def test_custom_tax_rate(self):
        """Test calculator with custom tax rate."""
        calculator = PaymentCalculator(tax_rate=0.08)  # 8% tax
        
        result = calculator.calculate_tax(100)
        
        assert result == 8.00

Mocking: Testing with Fake Dependencies

Real applications have dependencies - APIs, databases, external services. We mock these to test our code in isolation:

// src/paymentService.js
const stripe = require('stripe');
const emailService = require('./emailService');
const database = require('./database');

class PaymentService {
    constructor(stripeClient, emailClient, db) {
        this.stripe = stripeClient;
        this.email = emailClient;
        this.db = db;
    }
    
    async processPayment(paymentData) {
        try {
            // Validate payment data
            if (!paymentData.amount || paymentData.amount <= 0) {
                throw new Error('Invalid payment amount');
            }
            
            // Save to database first
            const payment = await this.db.payments.create({
                amount: paymentData.amount,
                customer_id: paymentData.customerId,
                status: 'pending'
            });
            
            // Process with Stripe
            const charge = await this.stripe.charges.create({
                amount: paymentData.amount * 100, // Stripe uses cents
                currency: 'usd',
                customer: paymentData.customerId,
                description: paymentData.description
            });
            
            // Update payment status
            await this.db.payments.update(payment.id, {
                status: 'completed',
                stripe_charge_id: charge.id
            });
            
            // Send confirmation email
            await this.email.sendPaymentConfirmation({
                to: paymentData.customerEmail,
                amount: paymentData.amount,
                chargeId: charge.id
            });
            
            return {
                success: true,
                paymentId: payment.id,
                chargeId: charge.id
            };
            
        } catch (error) {
            // Update payment status to failed if we created a record
            if (payment && payment.id) {
                await this.db.payments.update(payment.id, {
                    status: 'failed',
                    error_message: error.message
                });
            }
            
            throw error;
        }
    }
}

module.exports = PaymentService;
// tests/paymentService.test.js
const PaymentService = require('../src/paymentService');

describe('PaymentService', () => {
    let paymentService;
    let mockStripe;
    let mockEmail;
    let mockDb;
    
    beforeEach(() => {
        // Create mock objects
        mockStripe = {
            charges: {
                create: jest.fn()
            }
        };
        
        mockEmail = {
            sendPaymentConfirmation: jest.fn()
        };
        
        mockDb = {
            payments: {
                create: jest.fn(),
                update: jest.fn()
            }
        };
        
        paymentService = new PaymentService(mockStripe, mockEmail, mockDb);
    });
    
    describe('processPayment', () => {
        const validPaymentData = {
            amount: 99.99,
            customerId: 'cus_123',
            customerEmail: 'maya@example.com',
            description: 'Coffee order'
        };
        
        it('should process payment successfully', async () => {
            // Setup mocks
            mockDb.payments.create.mockResolvedValue({ id: 'pay_123' });
            mockStripe.charges.create.mockResolvedValue({ id: 'ch_123' });
            mockDb.payments.update.mockResolvedValue();
            mockEmail.sendPaymentConfirmation.mockResolvedValue();
            
            const result = await paymentService.processPayment(validPaymentData);
            
            // Verify result
            expect(result).toEqual({
                success: true,
                paymentId: 'pay_123',
                chargeId: 'ch_123'
            });
            
            // Verify database calls
            expect(mockDb.payments.create).toHaveBeenCalledWith({
                amount: 99.99,
                customer_id: 'cus_123',
                status: 'pending'
            });
            
            expect(mockDb.payments.update).toHaveBeenCalledWith('pay_123', {
                status: 'completed',
                stripe_charge_id: 'ch_123'
            });
            
            // Verify Stripe call
            expect(mockStripe.charges.create).toHaveBeenCalledWith({
                amount: 9999, // 99.99 * 100
                currency: 'usd',
                customer: 'cus_123',
                description: 'Coffee order'
            });
            
            // Verify email sent
            expect(mockEmail.sendPaymentConfirmation).toHaveBeenCalledWith({
                to: 'maya@example.com',
                amount: 99.99,
                chargeId: 'ch_123'
            });
        });
        
        it('should handle Stripe failure', async () => {
            mockDb.payments.create.mockResolvedValue({ id: 'pay_123' });
            mockStripe.charges.create.mockRejectedValue(new Error('Card declined'));
            
            await expect(paymentService.processPayment(validPaymentData))
                .rejects.toThrow('Card declined');
            
            // Should update payment status to failed
            expect(mockDb.payments.update).toHaveBeenCalledWith('pay_123', {
                status: 'failed',
                error_message: 'Card declined'
            });
            
            // Should not send email
            expect(mockEmail.sendPaymentConfirmation).not.toHaveBeenCalled();
        });
        
        it('should reject invalid payment amount', async () => {
            const invalidData = { ...validPaymentData, amount: -10 };
            
            await expect(paymentService.processPayment(invalidData))
                .rejects.toThrow('Invalid payment amount');
            
            // Should not call any external services
            expect(mockDb.payments.create).not.toHaveBeenCalled();
            expect(mockStripe.charges.create).not.toHaveBeenCalled();
            expect(mockEmail.sendPaymentConfirmation).not.toHaveBeenCalled();
        });
    });
});

Test-Driven Development (TDD)

After my production incident, I started practicing TDD - writing tests before code:

// Step 1: Write a failing test
describe('CoffeeLoyaltyProgram', () => {
    it('should calculate points earned from purchase', () => {
        const program = new CoffeeLoyaltyProgram();
        
        const points = program.calculatePointsEarned(25.50);
        
        expect(points).toBe(26); // 1 point per dollar + bonus point
    });
});

// Step 2: Write minimal code to make it pass
class CoffeeLoyaltyProgram {
    calculatePointsEarned(amount) {
        return Math.floor(amount) + 1; // Base points + bonus
    }
}

// Step 3: Refactor and add more tests
describe('CoffeeLoyaltyProgram', () => {
    let program;
    
    beforeEach(() => {
        program = new CoffeeLoyaltyProgram();
    });
    
    it('should calculate points earned from purchase', () => {
        expect(program.calculatePointsEarned(25.50)).toBe(26);
    });
    
    it('should give bonus points for purchases over $20', () => {
        expect(program.calculatePointsEarned(20.00)).toBe(20); // No bonus
        expect(program.calculatePointsEarned(20.01)).toBe(22); // Bonus
    });
    
    it('should handle zero amount', () => {
        expect(program.calculatePointsEarned(0)).toBe(0);
    });
});

// Step 4: Implement full logic
class CoffeeLoyaltyProgram {
    calculatePointsEarned(amount) {
        if (amount <= 0) return 0;
        
        const basePoints = Math.floor(amount);
        const bonusPoints = amount > 20 ? 2 : 1;
        
        return basePoints + bonusPoints;
    }
}

Integration Testing

Testing how components work together:

// tests/integration/orderFlow.test.js
const request = require('supertest');
const app = require('../../src/app');
const database = require('../../src/database');

describe('Order Flow Integration', () => {
    beforeEach(async () => {
        await database.clearTestData();
        await database.seedTestData();
    });
    
    afterEach(async () => {
        await database.clearTestData();
    });
    
    it('should complete full order process', async () => {
        // Create customer
        const customerResponse = await request(app)
            .post('/api/customers')
            .send({
                name: 'Maya Chen',
                email: 'maya@example.com'
            })
            .expect(201);
        
        const customerId = customerResponse.body.customer.id;
        
        // Create order
        const orderResponse = await request(app)
            .post('/api/orders')
            .send({
                customerId,
                items: [
                    { productId: 'latte', quantity: 2 },
                    { productId: 'muffin', quantity: 1 }
                ]
            })
            .expect(201);
        
        const orderId = orderResponse.body.order.id;
        
        // Process payment
        const paymentResponse = await request(app)
            .post(`/api/orders/${orderId}/payment`)
            .send({
                paymentMethod: 'credit_card',
                cardToken: 'tok_visa'
            })
            .expect(200);
        
        expect(paymentResponse.body.status).toBe('completed');
        
        // Verify order status updated
        const orderCheck = await request(app)
            .get(`/api/orders/${orderId}`)
            .expect(200);
        
        expect(orderCheck.body.order.status).toBe('paid');
    });
});

Testing Best Practices from Production

Test Structure (Arrange, Act, Assert)

it('should apply student discount correctly', () => {
    // Arrange - Set up test data
    const calculator = new PaymentCalculator();
    const items = [{ price: 20, quantity: 1 }];
    const discountPercent = 15; // Student discount
    
    // Act - Execute the code under test
    const result = calculator.calculateTotal(items, discountPercent);
    
    // Assert - Verify the outcome
    expect(result).toBe(18.72); // 20 - 3 (discount) + 1.72 (tax)
});

Descriptive Test Names

// Bad test names
it('should work', () => {});
it('test payment', () => {});
it('should return true', () => {});

// Good test names
it('should reject payment when amount is negative', () => {});
it('should send confirmation email after successful payment', () => {});
it('should apply loyalty discount before calculating tax', () => {});

Test Data Management

// tests/helpers/testData.js
const createTestCustomer = (overrides = {}) => ({
    id: 'cust_123',
    name: 'Test Customer',
    email: 'test@example.com',
    loyaltyPoints: 100,
    ...overrides
});

const createTestOrder = (overrides = {}) => ({
    id: 'ord_456',
    customerId: 'cust_123',
    items: [
        { productId: 'latte', quantity: 1, price: 4.50 }
    ],
    status: 'pending',
    ...overrides
});

module.exports = { createTestCustomer, createTestOrder };

Code Coverage and Quality

// jest.config.js
module.exports = {
    testEnvironment: 'node',
    collectCoverage: true,
    coverageDirectory: 'coverage',
    coverageReporters: ['text', 'lcov', 'html'],
    collectCoverageFrom: [
        'src/**/*.js',
        '!src/**/*.test.js',
        '!src/config/**',
        '!src/migrations/**'
    ],
    coverageThreshold: {
        global: {
            branches: 80,
            functions: 80,
            lines: 80,
            statements: 80
        }
    }
};

Testing Anti-Patterns (Don't Do This)

Testing Implementation Details

// Bad - testing internal methods
it('should call validateCardNumber method', () => {
    const spy = jest.spyOn(paymentService, 'validateCardNumber');
    paymentService.processPayment(paymentData);
    expect(spy).toHaveBeenCalled();
});

// Good - testing behavior
it('should reject payment with invalid card number', () => {
    const invalidPayment = { ...paymentData, cardNumber: '1234' };
    expect(() => paymentService.processPayment(invalidPayment))
        .toThrow('Invalid card number');
});

Overly Complex Tests

// Bad - doing too much in one test
it('should handle complete order flow with multiple edge cases', () => {
    // 50 lines of setup
    // Testing 10 different scenarios
    // Multiple assertions that could fail for different reasons
});

// Good - focused tests
it('should calculate tax on discounted amount', () => {
    // Simple, focused test
});

it('should apply loyalty discount after tax calculation', () => {
    // Another focused test
});

Continuous Integration with Tests

# .github/workflows/test.yml
name: Test Suite

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_PASSWORD: password
          POSTGRES_DB: test_db
        ports:
          - 5432:5432
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run linter
      run: npm run lint
    
    - name: Run unit tests
      run: npm test
      env:
        DATABASE_URL: postgresql://postgres:password@localhost:5432/test_db
    
    - name: Run integration tests
      run: npm run test:integration
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v1

Final Thoughts: Tests as Documentation

That Friday afternoon disaster taught me something crucial: tests aren't just about catching bugs. They're living documentation of how your code should behave. When I read my payment processing tests, I understand exactly what the business rules are, what edge cases we handle, and what could go wrong.

Six months after implementing comprehensive testing at Microsoft, our team's confidence skyrocketed. We deployed more frequently, refactored fearlessly, and actually enjoyed Friday afternoon deployments (okay, that's a stretch, but we weren't terrified).

Whether you're building a coffee shop POS system or processing millions in payments, tests give you the confidence to change code without fear. Start small - test one function, then another. Before you know it, you'll have a safety net that catches problems before your users do.

Remember: code without tests is just code that hasn't broken yet. And in production, everything breaks eventually.


Writing this from Seattle Public Library's 10th floor, where I can see the Space Needle and our deployment dashboard showing all green tests. Share your testing wins (or disasters) @maya_codes_pnw - we've all been there! 🧪✅

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Programming