Skip to course content
Free LLMOps course

LLMOps for Reliable AI Applications

Unit 04.02: Tool tests that need no model

Tools are the one part of an LLM application where testing is free, deterministic and exhaustive.

Every refusal branch, directly

A tool is a function. Call it with each argument combination and assert on the result.

The code covers five cases including four refusals.

MAX_REFUND = 500.0


def issue_refund(account_id, amount, approved_by):
    if not approved_by:
        return {"ok": False, "error": "no approver recorded"}
    if not str(account_id).startswith("ACC-"):
        return {"ok": False, "error": "malformed account id"}
    if not isinstance(amount, (int, float)) or amount <= 0:
        return {"ok": False, "error": "amount must be a positive number"}
    if amount > MAX_REFUND:
        return {"ok": False, "error": f"exceeds the {MAX_REFUND} ceiling"}
    return {"ok": True, "receipt": "rcpt-001"}


CASES = [
    ({"account_id": "ACC-1187", "amount": 120.0, "approved_by": "mgr-2"}, True),
    ({"account_id": "ACC-1187", "amount": 120.0, "approved_by": ""}, False),
    ({"account_id": "1187", "amount": 120.0, "approved_by": "mgr-2"}, False),
    ({"account_id": "ACC-1187", "amount": -5, "approved_by": "mgr-2"}, False),
    ({"account_id": "ACC-1187", "amount": 9_000, "approved_by": "mgr-2"}, False),
]
passed = 0
for args, expected_ok in CASES:
    result = issue_refund(**args)
    ok = result["ok"] == expected_ok
    passed += ok
    print(f"{'PASS' if ok else 'FAIL'} {str(args)[:60]:62} {result}")

print(f"\n{passed}/{len(CASES)} -- every refusal branch covered, no model needed")

Four of the five test refusal branches, and those are precisely the branches a successful run never reaches. A well-behaved agent does not produce a negative amount or a malformed account id, so the code handling them runs for the first time when something goes wrong.

The approver check is the one worth being strict about. approved_by being required at the tool boundary means an approval cannot be skipped by any path that reaches the tool, including one added later by someone who did not read this course.

The mistake this prevents

The mistake is testing tools through the agent. It is slow, costs tokens, is non-deterministic, and systematically fails to reach the refusal branches - which is most of the code you wrote.

Takeaway

Test tools directly, covering every refusal branch. It is the only part of the system where exhaustive testing is affordable, so be exhaustive.