Saturday, August 23, 2025

The Great Testing Reframe: From Implementation-Driven to Value-Driven Development

A follow-up to "Descriptive vs Prescriptive Testing" - Why the industry needs tests-first contracts for stakeholder alignment

Building on the Foundation

In my previous post about descriptive vs prescriptive testing, we explored how behavior-focused tests transform code reviews and create more resilient software. But that insight reveals a much deeper problem with how our industry approaches development.

What if the entire "code first, test later" paradigm is fundamentally backwards?

Mind blown meme: Galaxy exploding with text "When you realize the entire industry has been doing development backwards"

The more I've worked with behavior-focused testing, the more convinced I've become that we need a complete reframing of the development process - one that puts value contracts before implementation details.

The Systemic Devaluation Problem

But here's the deeper issue: testing isn't just devalued by developers - the entire decision chain devalues it.

The False Equation: Good Coders = Quality Code

Stakeholders and management operate under a dangerous assumption:

"We can achieve quality by just hiring good developers."

This thinking is fundamentally flawed because:

  • ✨ Even the best developers write bugs - it's human nature
  • 🧠 Complexity exceeds individual cognitive capacity - systems are too complex for perfect mental models
  • 🏃 Delivery pressure corrupts judgment - time constraints lead to shortcuts
  • 🤝 Quality emerges from process, not just talent - individual brilliance doesn't scale

Homer Simpson disappearing into bushes meme: "Management thinking they can skip testing and still get quality" / Homer backing into bushes labeled "Technical debt"

The Quality vs. Delivery False Dilemma

Organizations create an artificial choice:

  • 📦 "We want quality code" (in theory)
  • 🚀 "But delivery rate is what matters" (in practice)
  • ⏰ "Everything takes time, so delivery trumps quality" (the inevitable conclusion)

This creates a toxic cycle where:

  1. Management pushes for both quality and speed
  2. Teams know they can't deliver both under time pressure
  3. Quality gets sacrificed because delivery is measurable and visible
  4. Technical debt accumulates leading to slower future delivery
  5. Pressure increases to deliver even faster to compensate
  6. Quality degrades further and the cycle accelerates

Vicious cycle meme: Bike rider putting stick in his own spokes, labeled "Management demanding both speed and quality", falling labeled "Blaming developers when quality suffers"

The Hidden Cost of Quality Shortcuts

What stakeholders don't see is that skipping quality practices doesn't save time - it borrows it:

Short-term (Weeks 1-4)

  • ✅ Faster initial delivery (no time spent on tests)
  • ✅ Visible progress (features ship quickly)
  • ✅ Happy stakeholders (getting what they asked for)

Medium-term (Months 2-6)

  • ⚠️ Bug reports increase (quality issues surface)
  • ⚠️ Hotfixes required (disrupting planned work)
  • ⚠️ Development slows (technical debt accumulates)

Long-term (Months 6+)

  • 🔴 Feature velocity crashes (codebase becomes unmaintainable)
  • 🔴 Developer burnout (constant firefighting)
  • 🔴 Customer satisfaction drops (reliability issues)
  • 🔴 Competitive disadvantage (can't adapt quickly to market changes)

The Organizational Blind Spot

The problem is that quality debt is invisible until it's catastrophic:

  • Delivery is measurable: "We shipped 12 features this quarter"
  • Quality debt is hidden: Technical debt doesn't appear on roadmaps
  • Bugs seem like isolated incidents: Each one gets treated as a one-off
  • Slowdown seems like team performance: "Why is development taking longer?"

Management sees the immediate delivery wins but doesn't connect them to the later quality costs.

Breaking the Cycle: Tests-First as Business Strategy

This is why tests-first contracts aren't just a development practice - they're a business strategy.

When tests define value upfront:

  • Stakeholders see exactly what they're getting before paying for it
  • Quality becomes measurable (how many promises are we keeping?)
  • Delivery becomes predictable (no surprise rework cycles)
  • Technical debt becomes visible (broken tests show system degradation)

The conversation shifts from:

  • ❌ "Why is this taking so long?" (adversarial)
  • ✅ "Are we delivering on our promises?" (collaborative)

The Revolutionary Insight

While refactoring tests to be more descriptive, I realized something profound:

The teams that struggle most with testing are the ones focused on "how" instead of "what."

When developers say "I don't know what to test until I write the code," they're revealing that they're thinking about implementation before understanding the value they're supposed to deliver.

This backwards thinking is the root cause of:

  • 🎯 Missed requirements (we build first, validate later)
  • 🔄 Endless rework (stakeholders see the wrong thing)
  • 💸 Wasted effort (features that don't deliver value)
  • 🤷 Unclear expectations (no shared understanding)
  • 🐛 Bug-driven development (testing becomes an afterthought)

The Paradigm Shift: Tests as Stakeholder Contracts

Imagine if we flipped the entire development process:

❌ Current Paradigm: Implementation-Driven

  1. Stakeholders describe what they want (vaguely)
  2. Engineers interpret and build something
  3. QA tests if the implementation works
  4. Stakeholders see it and say "This isn't what we meant"
  5. Repeat until exhausted or deadline hits

✅ New Paradigm: Value-Driven

  1. Tests written first as stakeholder agreements on value
  2. All parties review and agree on the behavioral contracts
  3. Implementation begins only after promise alignment
  4. Code reviews focus on "Does this fulfill our contracts?"
  5. Delivery means all promises are kept

Drake meme: Top panel (disapproval) - "Building features and hoping stakeholders like them" / Bottom panel (approval) - "Getting stakeholder agreement on test contracts before building anything"

From "I Don't Know What to Test" to "What Value Am I Promising?"

The most common pushback to tests-first approaches reveals the core problem:

"I don't know what to test until I write the code."

This statement shows implementation-first thinking. Let's reframe it:

Implementation-First Conversation

  • "I need to build a user authentication system"
  • "I'll use JWT tokens and a database"
  • "Now I need to test if my JWT implementation works"

Value-First Conversation

  • "Users need to securely access their accounts"
  • "Users should stay logged in across sessions"
  • "Users should get clear feedback when login fails"
  • "Unauthorized users should never access protected data"

Notice: The second approach immediately suggests what to test, and none of it mentions JWT tokens.

The value-first conversation creates natural test contracts that all stakeholders can understand and agree on.

Tests as a Common Language

When tests are written first as behavioral contracts, they become a shared language:

  • Product Managers: "These tests prove we deliver user value"
  • Business Stakeholders: "These tests validate our requirements"
  • Engineers: "These tests define our success criteria"
  • QA Teams: "These tests guide our validation strategy"
  • Support Teams: "These tests explain what the system does"

Real Example: Transforming Feature Requests

Business Request: "We need better data quality monitoring"

Traditional Approach:

  1. Engineers build a monitoring dashboard
  2. QA tests if the dashboard works
  3. Business sees it: "This isn't what we meant"

Tests-First Contract Approach:

def test_analysts_get_alerted_when_data_quality_degrades():
    """When data quality drops below thresholds, analysts receive 
    actionable alerts within 5 minutes."""

def test_business_users_can_see_data_health_trends():
    """Business users can view data quality trends over time 
    without technical knowledge required."""

def test_engineers_can_drill_down_to_root_causes():
    """When alerts fire, engineers can trace from symptom 
    to root cause in under 3 clicks."""

Now everyone knows exactly what "better monitoring" means before any code is written.

The Three-Phase Development Contract

Building on the descriptive testing foundation, here's how value-driven development works:

Phase 1: Value Definition (The Contract)

  1. Stakeholders define outcomes they need
  2. Tests written as behavioral contracts
  3. All parties review and agree on promises
  4. Edge cases identified before implementation
  5. Success criteria crystal clear

Phase 2: Implementation (Fulfilling the Contract)

  1. Engineers implement to fulfill test contracts
  2. Code reviews focus on quality, not requirements
  3. Refactoring is safe (contracts don't change)
  4. Progress is measurable (which contracts fulfilled?)

Phase 3: Delivery (Contract Validation)

  1. All tests pass = all promises kept
  2. Stakeholders validate against agreed contracts
  3. Documentation current (tests describe behavior)
  4. Maintenance predictable (contracts define boundaries)

Beyond TDD and BDD: Meta-Development

This isn't anti-TDD or anti-BDD - it's meta-TDD that operates at the stakeholder level:

  • TDD says: Write tests first for better code
  • BDD says: Write behavior specs for better requirements
  • Value-Driven says: Write stakeholder contracts for better outcomes

Expanding brain meme: Small brain - "Write code then test" / Medium brain - "Write tests then code (TDD)" / Large brain - "Write behavior specs (BDD)" / Galaxy brain - "Write stakeholder value contracts first"

The natural progression becomes:

  1. Tests-first contracts (stakeholder alignment)
  2. BDD specifications (behavior definition)
  3. TDD implementation (code quality)

Each layer reinforces the others, creating a value-driven development culture.

Practical Implementation: Start Small, Think Big

Week 1: Single Feature Experiment

  1. Pick one feature for the tests-first approach
  2. Write behavioral tests with stakeholders first
  3. Get unanimous agreement before implementation
  4. Implement only to fulfill contracts
  5. Measure the difference in rework and satisfaction

Month 1: Team Adoption

  1. Share experiment results with the team
  2. Train on value-first thinking patterns
  3. Update review processes to include test contracts
  4. Adjust tooling to support tests-first workflow

Quarter 1: Cultural Transformation

  1. Establish tests-first as standard practice
  2. Reward teams that deliver on contracts with minimal rework
  3. Make test contracts part of feature planning
  4. Celebrate when implementation matches promises exactly

The Reframing Questions

When teams resist with "We don't know what to test," help them reframe:

Instead of asking:

  • "How should this function work?"
  • "What should this API return?"
  • "How do we structure this data?"

Ask:

  • "What problem does this solve for users?"
  • "How will users know this is working?"
  • "What happens when this goes wrong?"
  • "What value does this create?"

The answers become your test contracts - before any implementation exists.

Why This Matters Beyond Better Code

This reframe addresses fundamental problems in software development:

For Organizations

  • Predictable delivery: Know what you're getting before you build it
  • Reduced waste: Stop building the wrong things
  • Stakeholder alignment: Everyone agrees on outcomes upfront

For Teams

  • Clear direction: Implementation has a target, not wandering
  • Protected refactoring: Contracts survive implementation changes
  • Faster reviews: Focus on code quality, not requirements interpretation

For Users

  • Better products: Built with clear understanding of user needs
  • Fewer bugs: Edge cases identified before implementation
  • Consistent experience: Behavior is defined and validated

Success kid meme: "Shipped feature on time" / "And it's exactly what stakeholders wanted because we agreed on test contracts first"

The Bottom Line: From Code-Driven to Value-Driven

The current paradigm treats tests as validation after the fact.

The new paradigm treats tests as the first draft of value.

When we write tests first as stakeholder contracts:

  • Requirements become concrete instead of abstract
  • Teams align on specific outcomes before building
  • Implementation has clear direction instead of wandering
  • Quality is designed-in instead of tested-in
  • Delivery becomes predictable instead of surprising

The Call to Revolution

This isn't just about better testing practices - it's about better software development.

We need an industry that asks "What value are we creating?" before "How should we build this?"

We need product managers who can articulate outcomes, not just features.

We need businesses that understand they're buying solutions, not code.

And we need tests that prove we delivered on our promises.

Tomorrow's Action Plan

  1. Pick your next feature
  2. Write the tests first with your stakeholders
  3. Get everyone to agree on what those tests promise
  4. Then and only then start coding
  5. Watch the transformation begin

Because when everyone agrees on what you're building before you build it, everything else becomes implementation details.

And implementation details should never drive the conversation. Value should.


This follow-up emerged from seeing how descriptive testing naturally leads to value-first thinking. The shift from "Does my code work?" to "Did I deliver what I promised?" changes everything.

Wednesday, August 20, 2025

Descriptive vs Prescriptive Testing: Transforming Code Reviews Through Better Test Design

How writing tests that describe behavior instead of implementation can revolutionize your code review process

The Traditional Code Review Problem

Most development teams follow a familiar code review pattern:

  1. Check if tests exist ✅
  2. Review implementation details line by line 👀
  3. Debate formatting and style choices 💬
  4. Try to understand what the code is supposed to do 🤔
  5. Hope the tests actually validate the right behavior 🤞

This process is slow, error-prone, and often misses the forest for the trees. But what if we could flip this script entirely?

The Game-Changing Insight

The tests should tell the story of what the code does, not how it does it.

When tests are written descriptively (focusing on behavior) rather than prescriptively (focusing on implementation), they become living documentation that transforms the entire code review workflow.

Prescriptive vs Descriptive Testing: A Real Example

❌ Prescriptive Testing (Implementation-Focused)

def test_get_dq_approach_examples():
    """Test that get_dq_approach_examples returns correct structure."""
    result = get_dq_approach_examples()
    
    # Testing implementation details
    assert isinstance(result, dict)
    assert "examples" in result
    assert len(result["examples"]) > 0
    assert all("scenario" in ex for ex in result["examples"])
    assert all("approach" in ex for ex in result["examples"])

Problems with this approach:

  • Tests break when internal structure changes
  • Reviewers learn nothing about intended behavior
  • No indication of what value this provides users
  • Focused on "how" instead of "what"

Distracted boyfriend meme: Boyfriend (Developer) looking at "Testing implementation details" while girlfriend "Testing user behavior" looks disapproving, and the woman he's looking at is labeled "Brittle tests that break on refactor"

✅ Descriptive Testing (Behavior-Focused)

def test_users_can_find_appropriate_dq_approach_for_their_scenario():
    """Users should be able to find the right data quality approach for their specific use case."""
    examples = get_dq_approach_examples()
    
    # Verify users get practical guidance
    assert "examples" in examples, "Users need concrete examples to learn from"
    example_list = examples["examples"]
    assert len(example_list) > 0, "Users need at least one example to understand the concept"
    
    # Verify each example provides actionable guidance
    for example in example_list:
        assert "scenario" in example, "Users need to understand when to apply each approach"
        assert "approach" in example, "Users need clear guidance on which approach to use"
        
        # Verify content quality for user value
        assert len(example["scenario"]) > 10, "Scenarios must be detailed enough to be useful"
        assert example["approach"] in ["pyspark", "json_dq_framework"], "Approach must be a valid option"

Benefits of this approach:

  • Tests survive refactoring because they focus on outcomes
  • Reviewers immediately understand user value
  • Tests document the intended behavior
  • Implementation can change without breaking tests

The Transformed Code Review Workflow

With descriptive tests, code reviews become a three-stage pipeline:


Galaxy brain meme: Small brain - "Manual code review for everything" / Medium brain - "Automated formatting checks" / Large brain - "Tests document behavior" / Galaxy brain - "Three-stage pipeline: Automation → Behavior → Implementation"

Stage 1: Automated Validation (CI/CD)

  • ✅ Tests exist and pass (automated)
  • ✅ Code formatting/style (automated via linting)
  • ✅ Build succeeds (automated)

Stage 2: Behavioral Review (Human - Fast)

  • 👀 Read the test descriptions to understand intended behavior
  • 🎯 Validate business logic makes sense
  • 📋 Check edge cases are covered

Stage 3: Implementation Review (Human - Focused)

  • 🔍 Review the "how" with full context of the "what"
  • 🚀 Focus on performance, security, maintainability
  • 💡 Suggest alternative implementations if needed

The BDD Connection: Behavior Is What Matters

This approach naturally borders on Behavior-Driven Development (BDD) because behavior is where the real value lies. Consider these test names:

# Prescriptive (focuses on process)
def test_function_returns_dict_with_examples_key():

# Descriptive/BDD-ish (focuses on behavior)  
def test_users_can_discover_appropriate_dq_approach_for_their_scenario():

The second approach tells you:

  • Who benefits (users)
  • What they can accomplish (discover appropriate approach)
  • When they'd use it (for their specific scenario)
  • Why it matters (appropriate choice is important)

Practical Implementation Tips

1. Write Test Names as User Stories

# Instead of: test_validate_input_parameters()
def test_users_get_clear_error_when_providing_invalid_input():

# Instead of: test_generate_json_rule()  
def test_analysts_can_create_monitoring_rules_for_data_quality():

2. Use Descriptive Assertion Messages

# Instead of: assert result is not None
assert result is not None, "Users must receive guidance, not empty responses"

# Instead of: assert len(items) > 0
assert len(items) > 0, "Users need concrete examples to understand the concept"

3. Group Tests by User Capability

class TestUserCanAnalyzeDataQuality:
    def test_identifies_quality_issues_in_sample_data(self):
    def test_provides_actionable_remediation_suggestions(self):
    def test_handles_edge_cases_gracefully(self):

4. Test Outcomes, Not Internals

# Don't test: "function calls database.query()"
# Do test: "users can retrieve their recent transactions"

# Don't test: "returns Response object with status 200"  
# Do test: "users receive confirmation when operation succeeds"

The Retroactive Testing Reality

While true TDD writes tests first, most teams write tests retroactively. That's okay! The key insight is that even retroactive tests should be descriptive, not prescriptive.

When writing tests after implementation:

  1. Forget the implementation temporarily
  2. Think like a user - what should this accomplish?
  3. Write the test name as a user capability
  4. Assert on outcomes that matter to users
  5. Add failure messages that explain user impact

Real-World Benefits We've Observed

From our recent refactoring of prescriptive tests to descriptive ones:

Before (Prescriptive)

  • 🐌 Slow reviews: "What does this code actually do?"
  • 🔴 Brittle tests: Break on every refactor
  • 😕 Poor documentation: Tests don't explain purpose
  • 🤷 Unclear value: Hard to justify features

After (Descriptive)

  • ⚡ Fast reviews: Behavior is clear from test names
  • 💚 Resilient tests: Survive implementation changes
  • 📚 Living docs: Tests explain user value
  • 🎯 Clear purpose: Easy to validate features matter

The Bottom Line

Tests are not just validation - they're communication.

When tests describe behavior instead of implementation, they become:

  • 📖 Documentation that stays current
  • 🛡️ Protection that survives refactoring
  • 🗺️ Roadmap for code reviewers
  • 💎 Specification of user value

Getting Started

  1. Audit existing tests: Look for implementation details being tested
  2. Rewrite test names: Focus on user capabilities
  3. Update assertions: Test outcomes, not internals
  4. Add context: Explain why failures matter to users
  5. Iterate: Each new test should follow descriptive patterns

The goal isn't perfect TDD or BDD - it's tests that make your code reviews faster, your refactoring safer, and your team's understanding clearer.

Because at the end of the day, we rarely care about the process. We care about the behavior.


This insight emerged from refactoring a test suite from prescriptive to descriptive patterns. The difference in code review speed and clarity was immediately apparent - and transformative.

The Great Testing Reframe: From Implementation-Driven to Value-Driven Development

A follow-up to "Descriptive vs Prescriptive Testing" - Why the industry needs tests-first contracts for stakeholder alignment Buil...