“Before you test the system, test your own certainty.”
Most testing books start with code. This one insists you start with yourself. Every test you write is an artifact of belief. It’s a mirror that quietly reflects what you think is true, or what you want to be true, about the system you’re building. That means every failure in testing is, first, a failure in thinking. When you test, you are not merely verifying behavior. You are holding a mirror to your own thinking.
Testing isn’t just a technical act; it’s an epistemic one, a way of knowing. And the mind behind that knowledge is fragile, biased, and occasionally overconfident. That’s not a flaw in testers; that’s the human condition. So, before we can write reliable tests, we need to study the mind that writes them.
Also read chapter 3:
Why Psychology Belongs in Testing
Most testing books start with code. This one insists you start with yourself. Every test you write is an artifact of belief. It’s a mirror that quietly reflects what you think is true, or what you want to be true, about the system you’re building. That means every failure in testing is, first, a failure in thinking. When you test, you are not merely verifying behavior. You are holding a mirror to your own thinking.
Testing isn’t just a technical act; it’s an epistemic one, a way of knowing. And the mind behind that knowledge is fragile, biased, and occasionally overconfident. That’s not a flaw in testers; that’s the human condition. So, before we can write reliable tests, we need to study the mind that writes them.
The Origin of False Confidence
Let’s be honest: the first time a new test fails, it feels personal. You run it, it screams red, and your brain immediately whispers: Wait, that can’t be right. The test must be wrong. This is your defensive cognition, your brain protecting your prior belief. It might be true, or even it might be true for one experiment of your tests, or due to lack of enough experiments for your test. That moment is where true testing begins. It is the shift from defending your code to interrogating your assumptions.
In practice, this shows up everywhere: You believe rounding is trivial until a test exposes floating-point weirdness. You believe the input will always be valid until a test injects an empty string. You believe your async call returns fast until the test races it and wins. Each failure reveals not just a bug, but a bias.
Example: The Off-by-One Belief
Imagine you are building a hotel booking system’s utility for calculating stay length. You write:
JavaScript
function nightsBetween(checkIn, checkOut) {
return (new Date(checkOut) - new Date(checkIn)) / (1000 * 60 * 60 * 24);
}
The math seems fine. Then you write the test, verifying the result based on the hotel’s business logic:
test('two-night stay from Friday to Sunday', () => {
expect(nightsBetween('2025-11-07', '2025-11-09')).toBe(2);
});
It fails. The result is 2, but the accounting system bills for 3. After a long pause, you realize your hotel’s policy includes the check-out night for billing purposes. The code was right by mathematics, wrong by meaning.
It wasn’t the algorithm that failed, it was your assumption about what a night actually meant. That is not a bug; it is a semantic disagreement. You were not coding incorrectly. You were thinking incorrectly. The test acted like a cognitive MRI, exposing the misunderstanding buried inside your obvious logic.
The Invisible Biases Behind Every Assertion
We often imagine that a test fails because the code is wrong. But sometimes, the code is fine, it’s our model of reality that’s flawed. Testing exposes not just software defects, but cognitive defects.
Let’s look at the three most common psychological traps that silently shape how we test.
a. Confirmation Bias: The Desire to Be Right
When you already believe your code works, you subconsciously write tests that confirm it does. You check the happy path, skip the strange one, and feel relief when all is green. That green is comforting, dangerously so.
A test suite full of confirmations is not proof; it’s self-soothing. The real craft lies in writing tests that threaten your confidence.
Practice of Mind:
Write at least one test per feature whose purpose is to prove you wrong. If you can’t imagine how your code might fail, you don’t yet understand it.
Example
You believe your discount logic works, so you only test the happy path:
function applyDiscount(total, isMember) {
return isMember ? total * 0.9 : total;
}
// Your tests:
expect(applyDiscount(100, true)).toBe(90);
expect(applyDiscount(100, false)).toBe(100);
All green. You SMILE:). Everything seems fine. Then you run the edge case test you should have written:
expect(applyDiscount(0, true)).toBe(0);
It fails, your discount calculation allows negative totals when combined with other promotions.
Your initial tests confirmed what you wanted to be true, not what could actually go wrong. Confirmation bias made the code appear safe, but the strategic test exposed a flaw you hadn’t considered.
b. Anchoring: The First Idea That Blinds the Rest
The first version of a rule you hear becomes your anchor, and every new detail adjusts only slightly from it, even when it should have shifted completely.
Example
“Buy three, get one free” anchors the tester to “groups of four” and sometimes blinds them to promotions that might change next quarter (“buy five, get two”).
Anchoring hides evolution inside systems that must adapt. The psychological defense against anchoring is curiosity. Ask “What if this rule changes?” before assuming it never will.
Example: When Tests Teach You Design
You are adding a cancellation fee to the booking logic:
function calculateRefundAmount(nights, rate, cancelled) {
return nights * rate - (cancelled ? 20 : 0);
}
You run a simple test:
expect(calculateRefundAmount(3, 100, true)).toBe(280);
It passes. You smile. Then you run the strategic test you know you must write:
expect(calculateRefundAmount(1, 50, true)).toBe(50);
It fails. The cancellation fee makes the total negative. The test reveals a missing domain rule: Cancellation fee cannot exceed total price.
Now you add the missing logic:
if (cancelled && nights * rate < 20) return nights * rate;
The test discovered the rule, not the documentation. The test didn’t just verify behavior, it expanded your understanding of the domain.That is the psychology of learning through contradiction.
c. The Illusion of Completeness
After you’ve written a dozen tests, your brain releases a small dose of satisfaction. You feel done.
But the system, like reality, is not impressed by your sense of completion. This illusion is why bugs appear after the test suite turns green. Your sense of completeness is not truth; it’s just a psychological threshold.
Practice of Mind:
Replace the question Did I test everything? with What’s the most important thing I didn’t test yet?
Example
After writing many tests, you feel confident everything is covered:
function calculateShipping(weight) {
if (weight <= 5) return 5;
if (weight <= 20) return 10;
return 20;
}
// Tests for common cases
expect(calculateShipping(1)).toBe(5);
expect(calculateShipping(5)).toBe(5);
expect(calculateShipping(10)).toBe(10);
expect(calculateShipping(20)).toBe(10);
expect(calculateShipping(25)).toBe(20);
All green. You close your laptop. Done, right? Then a customer orders 0.5 lbs, or a negative weight due to a data entry error, and your system crashes.
3. The Tester’s Paradox: Trust and Doubt
Great testers live in a paradox: they must trust the system enough to explore it, and doubt it enough to question everything it shows.
Too much trust, and you become complacent. Too much doubt, and you drown in paranoia. Between those extremes lies discipline, the quiet balance between curiosity and skepticism.
That’s why mature testing is a psychological stance, not just a technique. It’s the art of holding two beliefs at once:
- The system might be correct.
- I could still be wrong.
It’s in that tension that discovery happens.
Great testers must trust the system enough to explore it, and doubt it enough to question everything. Here’s a concrete example:
// Function to calculate total booking cost
function calculateBookingTotal(nights, rate, discount = 0) {
return nights * rate - discount;
}
// Your first test – seems correct
expect(calculateBookingTotal(3, 100)).toBe(300); // passes
expect(calculateBookingTotal(3, 100, 20)).toBe(280); // passes
You feel confident. The system seems correct.
Then you write a doubt-driven test:
expect(calculateBookingTotal(0, 100, 20)).toBe(0); // fails: returns -20
This test forces you to confront a subtle flaw: the system might behave incorrectly for zero nights.
The paradox is real, you trust your code enough to build it, but you must also doubt it to discover hidden issues. Discovery happens in that tension.
4. Testing as a Mirror of Ego
Every developer has an ego, the desire to see their creation succeed. That’s natural. But ego and curiosity don’t coexist easily. The moment you take a test failure personally, you stop learning from it.
A failing test isn’t a judgment of your worth; it’s a message from reality. It’s saying, Your model no longer matches the world. That’s a gift.
When testers feel shame about failure, they stop experimenting. When they feel curiosity instead, they evolve.
Psychological Practice:
Replace I broke it with I learned something. Curiosity is the antidote to ego.
Example
Every failing test can feel personal. Here’s an example in the booking domain:
function calculateRefund(nights, rate, cancelled) {
return cancelled ? nights * rate * 0.5 : nights * rate;
}
// You run your tests
expect(calculateRefund(2, 100, false)).toBe(200); //
expect(calculateRefund(2, 100, true)).toBe(100); //
You feel good. Then you try a “cognitive chain” test, reflecting on edge cases:
expect(calculateRefund(1, 10, true)).toBe(5); // seems fine
expect(calculateRefund(0, 50, true)).toBe(0); // fails: returns NaN
- Belief: “Refund should always be half of the total if cancelled.”
- Expectation: “If the booking exists, I should get a numeric refund.”
- Assertion: “Run the test.”
- Reflection: “Wait, what happens for 0 nights? My belief wasn’t complete.”
A failing test is not a judgment of your skill; it’s feedback from reality. Replace “I broke it” with “I learned something”. Curiosity, not ego, drives refinement.
The Cognitive Chain: From Belief to Assertion
If we look closely, every test follows a psychological sequence, a chain that mirrors how we move from thought to proof:
- Belief: “I think this rule should hold.”
- Expectation: “If it holds, I should see X.”
- Assertion: “Let’s see if X is true.”
- Reflection: “Was my belief accurate, or incomplete?”
Most people stop at the third step. The best testers consciously add the fourth. They use failed assertions not just to fix bugs, but to refine beliefs. That’s what separates mechanical testing from epistemic testing, one seeks correctness; the other seeks truth.
Emotional Discipline: The Tester’s Mindset
Every test you run is an encounter with uncertainty. Uncertainty triggers anxiety. That’s why many developers avoid exploratory testing, they’d rather automate a green build than face a messy unknown.
But mastery begins when you make peace with uncertainty. Testing isn’t about eliminating doubt, it’s about learning to work within it.
In that sense, testing is psychological training. It teaches humility, patience, and emotional endurance. A long red test run isn’t punishment; it’s meditation. Each failure is a chance to sharpen your internal model of truth.
The Team’s Collective Psychology
Teams, like individuals, develop shared biases:
- Overconfidence in automation (The CI will catch it.)
- Diffusion of responsibility (Someone else must have tested that.)
- Fear of red builds (Don’t break the pipeline!)
A psychologically safe team treats testing not as a performance metric, but as a shared inquiry. A failing test is not to blame, it’s a conversation starter.
Cultural Practice:
When a test fails, don’t ask “Who broke it?” Ask “What did we just learn?”
That simple reframing transforms testing from policing to learning. Over time, it builds a team culture of epistemic humility, where people value discovering they were wrong.
The Inner Game of Testing
If you strip testing down to its essence, it’s not about tools or frameworks. It’s about metacognition, thinking about how you think.
A good tester questions their own assumptions before questioning the system’s. They know their brain’s shortcuts, the heuristics that can betray them. They slow down where others rush. They stay curious where others conclude.
Testing is not about doubt alone; it’s about disciplined doubt, doubt that leads to better understanding, not paralysis. “Testing is thinking, made visible”
Testing is the process of evaluating a product by learning about it through exploration and experimentation, which includes to some degree: questioning, study, modeling, observation, inference, etc.
James Bach & Michael Bolton
When you start to see your own thoughts as part of the system you’re testing, you begin to test reality itself. Yes, a customer ordering 0.5 lbs or booking 0 nights might sound absurd, that’s exactly why it’s a perfect test. Epistemic testing isn’t about likely inputs; it’s about challenging your assumptions and revealing what your model cannot handle.
When you next write a test, pause for one breath before typing. Ask yourself: Am I proving that I’m right, or discovering whether I might be wrong? That single question, asked sincerely, is the psychological foundation of every great tester.
Practice in Action: Testing the Tester
The Scenario
You’re reviewing a feature that automatically archives inactive users after 90 days.
You believe:
“A user who hasn’t logged in for 90 days should be archived.”
Simple enough.
But now, pause and examine your beliefs before writing any code.
- What does “inactive” really mean?
- No login? No API activity? No payments?
- What happens on the 90th day at 23:59?
- What if the user logged in once on the 89th day?
- What if the time zone shifts or the server clock drifts?
- What if an archived user tries to log in?
- What if the marketing team wants to send a “we miss you” email before archiving?
The exercise isn’t about code, it’s about awareness. Each question tests the tester’s own clarity of thought.
Goal: Identify which of your questions belong to the three hats:
- Discoverer’s (what does “inactive” mean?)
- Shaper’s (what’s measurable: days, hours, or minutes?)
- Engineer’s (how will the system enforce it?)
When you can classify your own thoughts, you’ve reached the meta-level where testing becomes self-aware.
Takeaways
- Every test starts in the mind, not the IDE.
- The hardest system to test is your own certainty.
- Green tests feel safe; red tests feel uncomfortable, and that discomfort is where growth happens.
- Testing is emotional work disguised as technical work.
- Biases (confirmation, anchoring, completeness) are the real sources of blind spots in test coverage.
- A mature tester welcomes failure as feedback, not as defeat.
- Teams that turn failure into shared learning build antifragile understanding.
- The real measure of a tester isn’t how many bugs they find, but how honestly they confront what they don’t yet know.
Leave a Reply