Law #7: Setup Honesty (Setup Gravity)

Every line of test setup is a smell. If you can't tell what's relevant to your assertion, neither can anyone else. Irrelevant setup is noise. Noise hides bugs.

The Failure Mode: Setup Gravity

Tests accumulate setup over time. Someone adds a field. Someone else adds another. No one removes anything "just in case."

Soon you have 50 lines of setup and 1 line of assertion:

def test_discount_applies_for_premium_users():
    user = create_user(
        name="Alice Johnson",
        email="alice.johnson@example.com",
        phone="555-0100",
        address="123 Main St",
        city="Springfield",
        state="IL",
        zip="62701",
        country="USA",
        age=34,
        gender="F",
        preferences={"newsletter": True, "sms": False},
        created_at=datetime(2023, 1, 15),
        last_login=datetime(2024, 12, 1),
        membership_tier="premium",  # ← Only this matters
        payment_methods=[
            {"type": "credit_card", "last4": "1234", "exp": "12/25"},
            {"type": "paypal", "email": "alice@paypal.com"}
        ],
        billing_history=[...],  # 20 more lines
    )
    cart = create_cart(user)
    add_item(cart, "widget", price=100.00, quantity=1)
    
    discount = apply_discount(cart)  # ← The actual test
    
    assert discount == 10.00  # 10% for premium users

What's relevant? You can't tell. Is the email relevant? The billing history? The age?

When this test fails, you'll waste 10 minutes reading irrelevant setup trying to understand what broke.

Why It Kills You

  1. You can't tell what's being tested. Is this testing discounts? Email validation? Billing history? No idea.
  2. Debugging takes forever. When it fails, you read 50 lines of setup looking for the bug.
  3. Tests become write-only. No one understands them. They just copy-paste and hope.
  4. False confidence. You think you're testing complex scenarios. You're just testing setup bloat.
  5. Coupling everywhere. Every setup change breaks dozens of tests.

Examples: Minimal, Meaningful Setup

Bad: Kitchen Sink Setup
// 😱 BAD: Everything and the kitchen sink
test('validates email format', () => {
  const user = createUser({
    firstName: 'John',
    lastName: 'Doe',
    email: 'invalid-email',  // ← Only this matters
    password: 'SecureP@ssw0rd!',
    age: 28,
    phone: '555-0123',
    address: {
      street: '456 Oak Ave',
      city: 'Portland',
      state: 'OR',
      zip: '97201',
    },
    preferences: {
      theme: 'dark',
      notifications: true,
      language: 'en-US',
    },
    roles: ['user', 'contributor'],
    metadata: {
      source: 'web',
      campaign: 'spring2024',
    },
  });
  
  const result = validateUser(user);
  
  expect(result.valid).toBe(false);
  expect(result.errors).toContain('Invalid email format');
});

// 30 lines of setup. 1 field matters. Which one? Who knows.

Good: Minimal, Explicit Setup

// ✅ GOOD: Only what's necessary
test('validates email format', () => {
  const user = {
    email: 'invalid-email',  // ← Crystal clear what matters
  };
  
  const result = validateEmail(user.email);
  
  expect(result.valid).toBe(false);
  expect(result.error).toBe('Invalid email format');
});

// 1 line of setup. Zero confusion. Obvious what's being tested.
Bad: Magic Factory Functions
# 😱 BAD: Critical data hidden in factories
def test_cannot_delete_active_subscription():
    user = create_premium_user()  # What does this create?
    
    result = user.cancel_subscription()
    
    assert result.error == "Cannot cancel active subscription"

# What's a "premium_user"? 
# - Do they have billing info?
# - Are they on a trial?
# - Do they have payment history?
# You have to read create_premium_user() to find out.

def create_premium_user():
    # Hidden in a factory, 100 lines away
    return User(
        membership="premium",
        trial_ends=None,  # ← This is why cancellation fails!
        billing_date=datetime.now(),
        payment_method="credit_card",
        # ... 50 more fields
    )

Good: Inline What Matters

# ✅ GOOD: Critical data in the test
def test_cannot_cancel_active_subscription():
    user = User(
        membership="premium",
        trial_ends=None,  # ← Right here, obvious
    )
    
    result = user.cancel_subscription()
    
    assert result.error == "Cannot cancel active subscription"

# Now it's obvious: trial_ends=None means active subscription.
# No hunting through factory functions.

# If you need defaults for other tests, make them explicit:
def user_with_defaults(**overrides):
    return User(
        membership="free",
        trial_ends=None,
        **overrides  # Caller overrides what matters
    )
Bad: Meaningless Test Data
// 😱 BAD: Generic, meaningless data
func TestBlocksSuspendedUsers(t *testing.T) {
    user := User{
        ID:    1,  // What's special about user 1?
        Email: "test@test.com",  // Why this email?
        Name:  "Test User",
        Status: "suspended",  // ← This is what matters, but it's buried
    }
    
    result := authenticateUser(user.Email, "password")
    
    assert.False(t, result.Allowed)
}

// The test passes, but you don't know WHY from reading it.

Good: Meaningful Test Data

// ✅ GOOD: Data tells the story
func TestBlocksSuspendedUsers(t *testing.T) {
    user := User{
        Email: "suspended@example.com",  // ← Name tells you what's special
        Status: "suspended",  // ← Front and center
    }
    
    result := authenticateUser(user.Email, "password")
    
    assert.False(t, result.Allowed)
    assert.Equal(t, "Account suspended", result.Reason)
}

// Now you know EXACTLY why the test should fail auth.
// The email itself documents the test case.

Rules of Thumb

✓ Good Setup
  • Minimal: only what's needed
  • Explicit: visible in the test
  • Meaningful: data tells a story
  • Scoped: local to each test
  • Obvious: anyone can understand it
✗ Bad Setup
  • Kitchen sink: creates everything
  • Hidden: buried in helper functions
  • Generic: "test@test.com", user ID 1
  • Shared: global fixtures
  • Mysterious: takes 10 minutes to understand

When Factories Are OK