0Pricing
AI Agents · Lesson

Why Testing Agents Is Different

Non-determinism, LLM cost, and why standard unit tests fall short.

Why Testing Agents Is Different is a free AI Agents lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Testing Software vs. Testing Agents

Traditional software is deterministic: give it the same input, get the same output. Unit tests rely on this property to assert exact expected values.

AI agents break this assumption. The same prompt can produce different outputs each run, making standard testing approaches insufficient on their own.

Non-Determinism: Same Input, Different Output

LLMs are probabilistic by nature. The temperature parameter controls randomness — even at temperature=0, outputs can vary across model versions or infrastructure changes.

This means an agent test that passes today may fail tomorrow with no code change.

import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

# Same prompt, potentially different outputs each run
for i in range(3):
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': 'Name a planet.'}],
        temperature=0.9  # High randomness
    )
    print(f'Run {i+1}: {response.choices[0].message.content}')
# Run 1: Mars
# Run 2: Jupiter
# Run 3: Saturn

The Cost Problem: Real LLM Calls Are Expensive

Running a test suite that makes real API calls to OpenAI or Anthropic can cost dollars per run. A CI pipeline running 100 tests × 10 iterations could cost hundreds of dollars per month.

This makes it impractical to run agent tests the same way you run unit tests — you need strategies to control costs.

# A test that calls the real API costs tokens every run
# 100 tests x 500 tokens each x 10 CI runs/day = 500,000 tokens/day
# At $0.15/1M tokens (gpt-4o-mini): ~$0.075/day = ~$27/year for a tiny suite
# For gpt-4o: 15x more expensive = ~$400/year

# This is why mocking and recording API responses is essential
print('Real API calls in tests = expensive and slow')
print('Solution: Mock or record LLM responses in unit tests')
print('Reserve real calls for scheduled integration tests')

The Latency Problem

Real LLM API calls typically take 2-20 seconds. A test suite with 50 tests would take 100-1000 seconds to run. This kills developer productivity — fast feedback is a core value of good tests.

Mocking LLM calls makes tests run in milliseconds.

import time

# Simulating what a test suite looks like with real vs mocked calls
num_tests = 50

# Real API calls
real_time = num_tests * 5  # avg 5 seconds per call
print(f'With real API calls: {real_time}s = {real_time/60:.1f} minutes')

# Mocked calls
mock_time = num_tests * 0.001  # <1ms per mock
print(f'With mocked calls: {mock_time:.3f}s = nearly instant')

# Conclusion: mock in unit tests, use real calls in integration tests

External Dependencies in Agent Tests

Agents often call external tools: search APIs, databases, file systems, web scrapers. In tests, these dependencies can:

  • Be unavailable (network outage, API downtime)
  • Return different data on each run
  • Have rate limits that block CI pipelines

These must be controlled or mocked in unit tests.

# An agent might call multiple external services
# Each is a potential test failure point

def agent_pipeline(query: str) -> str:
    search_results = search_web(query)       # External: Tavily/Serper API
    documents = fetch_documents(search_results)  # External: HTTP calls
    answer = llm_summarize(documents)        # External: OpenAI API
    saved = database_store(answer)           # External: PostgreSQL
    return answer

# In unit tests: mock ALL of these
# In integration tests: use sandboxed versions of real services
print('Each external call is a test reliability risk')

What Standard Unit Tests Assume

Standard unit testing frameworks like pytest assume:

  • Tests are fast (milliseconds)
  • Tests are deterministic
  • Tests have no external side effects
  • Tests can run in any order

Agent tests violate all four of these assumptions unless you explicitly design around them.

# Standard unit test — works perfectly for deterministic code
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5  # Always passes — deterministic

# Agent 'unit test' that calls a real LLM — problematic
# def test_agent_answers_question():
#     response = agent.run('What is 2+2?')
#     assert response == '4'  # Might return 'The answer is 4' or 'Four'

print('Exact string matching fails for LLM outputs')
print('Need structural or semantic assertions instead')

The Testing Pyramid for Agents

A practical testing strategy for agents follows a pyramid:

  • Unit tests (many, fast): Test individual tools and functions with mocked LLM calls
  • Integration tests (fewer, slower): Test the agent pipeline end-to-end with sandboxed services
  • Evaluation tests (rare, expensive): Test output quality with real LLM calls and human-style scoring

Structural vs. Semantic Assertions

Instead of exact string matching, agent tests should use structural assertions (did the agent call the right tool?) or semantic checks (does the output contain the relevant concept?).

# Fragile: exact string match
# assert response.content == 'The capital of France is Paris.'

# Better: structural assertion
# assert response.tool_calls[0]['function']['name'] == 'search_web'

# Better: semantic check
def test_capital_in_response(response_text: str) -> bool:
    key_words = ['paris', 'france', 'capital']
    lower = response_text.lower()
    return all(word in lower for word in key_words)

response = 'Paris is the capital city of France.'
print(test_capital_in_response(response))  # True

Evaluation Harnesses and LLM-as-Judge

For quality evaluation, the industry uses LLM-as-judge: ask a second LLM to rate the agent's output. Frameworks like DeepEval and RAGAS automate this pattern.

This is reserved for expensive evaluation runs, not routine CI.

import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

def llm_judge(question: str, answer: str) -> dict:
    prompt = f'Question: {question}\nAnswer: {answer}\nRate the answer 1-5 for accuracy. Reply with only a number.'
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}]
    )
    score = int(response.choices[0].message.content.strip())
    return {'score': score, 'pass': score >= 4}

# result = llm_judge('What is the capital of France?', 'Paris')
# print(result)  # {'score': 5, 'pass': True}

Regression Testing for Agents

When you update a prompt or change agent logic, regression tests verify you didn't break existing behavior. Record golden examples (input → expected structure) and run them automatically on every commit.

# golden_examples.py
GOLDEN_EXAMPLES = [
    {
        'input': 'Search for the weather in Paris',
        'expected_tool': 'get_weather',
        'expected_args': {'city': 'Paris'}
    },
    {
        'input': 'Calculate 15% tip on $45',
        'expected_tool': 'calculate',
        'expected_args': {'expression': '45 * 0.15'}
    }
]

def run_regressions(agent, examples: list) -> int:
    failures = 0
    for ex in examples:
        result = agent.plan(ex['input'])  # mocked LLM
        if result['tool'] != ex['expected_tool']:
            print(f'FAIL: expected {ex["expected_tool"]}, got {result["tool"]}')
            failures += 1
    return failures

# --- demo: a stub agent whose .plan() mimics an LLM's tool choice ---
class _StubAgent:
    def plan(self, text):
        if 'weather' in text.lower():
            return {'tool': 'get_weather'}
        if 'tip' in text.lower() or 'calculate' in text.lower():
            return {'tool': 'wrong_tool'}  # simulate a regression
        return {'tool': 'unknown'}

failures = run_regressions(_StubAgent(), GOLDEN_EXAMPLES)
print(f'{failures} of {len(GOLDEN_EXAMPLES)} golden examples failed')

Setting Up a Basic Agent Test File

Here is a minimal pytest test file structure for an agent. It separates fast unit tests (with mocks) from slow integration tests (with real calls), letting you run only what you need.

# tests/test_agent.py
import pytest

# Fast unit tests — run on every commit
class TestAgentTools:
    def test_tool_returns_dict(self, mock_llm):
        result = my_tool(query='test')
        assert isinstance(result, dict)
        assert 'data' in result

    def test_agent_selects_correct_tool(self, mock_llm):
        response = agent.run('Search for Python tutorials')
        assert response['tool_used'] == 'web_search'

# Slow integration tests — run nightly or on release
@pytest.mark.integration
class TestAgentIntegration:
    def test_full_pipeline_with_real_api(self):
        # Uses real OpenAI + sandboxed services
        result = agent.run('Summarize the Python docs')
        assert len(result['answer']) > 50

Knowledge Check: Why Agent Testing Is Different

Test your understanding of the unique challenges of testing AI agents.

Recap: Why Testing Agents Is Different

AI agent testing requires a different mindset from standard unit testing:

  • Non-determinism: Same input can produce different valid outputs
  • Cost: Real LLM calls are expensive — mock them in unit tests
  • Latency: Real API calls take seconds — mocks run in milliseconds
  • External dependencies: Tools and APIs must be controlled in tests
  • Assertions: Use structural and semantic checks, not exact string matching

Use a testing pyramid: many cheap unit tests with mocks, fewer expensive integration tests with real calls.

Frequently asked questions

Is the “Why Testing Agents Is Different” lesson free?

Yes — the full text of “Why Testing Agents Is Different” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.

What will I learn in “Why Testing Agents Is Different”?

Non-determinism, LLM cost, and why standard unit tests fall short. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Agents?

No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Why Testing Agents Is Different” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Agents lesson?

Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Why Testing Agents Is Different
  2. Mocking LLM Calls in Tests
  3. Assertion-Based Agent Testing
  4. Integration Tests for Agent Pipelines
← Back to AI Agents