4.5.14 · D5Software Engineering
Question bank — TDD — Red-Green-Refactor cycle

True or false — justify
TDD means writing all your tests before any production code exists.
False — you write one small failing test, make it pass, then repeat. It is a tight interleaved loop, not a big "tests-first, code-later" phase.
The Red step is only about seeing a red bar on the screen.
False — Red must fail for the right reason (the assertion), not because of a typo or missing import. A test that fails for the wrong reason proves nothing about whether it can catch real bugs.
If a test passes the first time you run it (before writing code), that's a good sign.
False — it means the test has no teeth: it cannot distinguish "works" from "broken," so it will never catch a regression. You want it to fail first.
Hardcoding return "1" in a Green step is sloppy and should be avoided.
False — early hardcoding is a deliberate move; the next failing test forces generalization. This triangulation is how design grows only as much as tests demand.
You are allowed to add new behavior during the Refactor step.
False — Refactor changes structure only; observable behavior must stay identical, which is exactly why all tests must remain green throughout.
TDD guarantees your code is bug-free.
False — it guarantees the behaviors you tested work and stay working. Untested paths and wrong specifications can still hide bugs; TDD shrinks the risk, it doesn't erase it.
Refactoring is safe even without tests, as long as you're careful.
False — "careful" is not verifiable. Without a green test suite you have no automatic proof that behavior was preserved, so a refactor becomes a leap of faith. See Refactoring.
A test that reproduces a fixed bug can be deleted once the bug is fixed.
False — keeping it is the whole point: it becomes a permanent regression guard so the bug can never silently return.
TDD and BDD are the same thing under different names.
False — BDD reframes tests as human-readable behavior specifications (Given/When/Then), often at a higher level. TDD is the underlying red-green-refactor discipline; BDD builds on that idea but shifts vocabulary and audience.
High Code Coverage proves your tests are good.
False — coverage only shows which lines ran, not whether meaningful assertions checked them. You can execute a line with zero assertions and score coverage while catching nothing.
A test that passes 9 runs out of 10 is still basically fine.
False — that is a flaky test. Intermittent failure breaks the whole promise of Red/Green: you can no longer trust a red bar to mean "broken" or a green bar to mean "works," so the loop's feedback signal is poisoned.
Spot the error
"I wrote the code, ran it manually, it worked, then I wrote a test that passed — that's TDD."
The error is the order: code came first, so the test never proved it could fail. That's tests-after, not TDD; the Red step (proving the test has teeth) was skipped.
"My test failed with an ImportError, so I'm safely in the Red phase."
Wrong reason for Red — an import error is a setup failure, not a genuine assertion failure. You must reach the point where the assertion is what fails.
"I refactored and three tests went red, but the feature still looks fine, so I'll commit."
Red tests during refactor mean behavior changed (or a test is now stale). You must not commit; either restore green or, if the spec truly changed, that's new work needing its own Red step first.
"To save typing, I put five unrelated assertions in one test."
This destroys failure localization — when it fails you can't tell which behavior broke. One assert-worthy behavior per test is the rule (see Unit Testing).
"My Green step took 40 lines because I implemented the whole algorithm at once."
That over-shoots the minimum; you built behavior no failing test demanded. Write the simplest thing that passes the current test and let later tests pull the rest.
"The test is painful to write because I need to construct ten dependencies — the test framework is bad."
The pain is design feedback, not a framework fault. Heavy setup signals tight coupling; refactor for testability, possibly using test doubles to isolate the unit.
"My unit test hits the real database, so it's a proper end-to-end check — best of both worlds."
A test that touches a real database, network, or file system is no longer a unit test; it's slow and non-deterministic, so it doesn't belong in the fast inner TDD loop. Replace the dependency with a stub or fake for the unit loop, and keep the real-dependency check as a separate, slower integration suite run by Continuous Integration.
"A test failed only because another test ran first and left leftover data — I'll just re-run it."
Re-running hides the real bug: test ordering dependence. Each test must set up and tear down its own state so it passes in isolation and in any order; otherwise your suite is flaky by construction.
Why questions
Why must the test fail before you write the production code?
So you prove the test can actually detect the missing behavior. A test that never failed might be asserting nothing and would silently pass forever.
Why is it fine — even encouraged — to write "ugly" hardcoded code in Green?
Green's only job is to reach a known-good state fast. Cleanliness is Refactor's job, and triangulation from the next test will force real logic anyway, so you lose nothing.
Why does TDD naturally push toward loosely-coupled, testable designs?
Because painful-to-test code fails the very first thing you do (write a test), the friction shows up immediately and cheaply, nudging you toward smaller, injectable units aligned with SOLID Principles.
Why do short cycles matter economically?
The cost of a defect grows with time-to-detection. A bug caught in the Red step costs seconds; the same bug in production costs orders of magnitude more, so tiny loops keep the detection window minimal.
Why write one behavior per test instead of one big test per feature?
A tiny test localizes failure to a single behavior, so a red bar tells you exactly what broke. A giant test only tells you "something in here is wrong."
Why does TDD pair so well with Continuous Integration?
TDD produces a fast, always-green test suite that CI can run on every push, catching regressions the moment they enter shared code rather than days later.
Why are flaky tests especially corrosive to a TDD workflow?
TDD's entire value rests on trusting the red/green signal. A flaky test that sometimes fails for no reason trains you to ignore red bars — so when a real red appears you shrug it off, and the safety net silently rots.
Why keep external dependencies out of the fast inner loop?
Databases, networks, and clocks are slow and non-deterministic; putting them in your seconds-long loop makes it minutes-long and unreliable, which kills the tight rhythm that makes TDD worth doing.
Edge cases
What should you do if you genuinely cannot make a test fail first (e.g. the behavior already exists)?
Then there's nothing new to drive — either the test is redundant, or you're documenting existing behavior (a characterization test), which is a different, deliberate purpose from driving new code.
Is it valid to refactor when some tests are currently red?
No — you only refactor on green. With red tests you can't tell whether a change preserved behavior, so refactoring first requires getting back to green.
What if the "simplest code to pass" makes an earlier test fail?
That's a signal your minimal change wasn't actually minimal or consistent; the failing earlier test is doing its job. Adjust the code so all tests are green before moving on — never leave a passing test broken.
How does TDD handle a spec that turns out to be wrong (the test asserts incorrect behavior)?
You change the test deliberately in a Red step — a failing test that now encodes the corrected spec — then make code match. The test is the spec, so fixing the spec means editing the test on purpose, not silently.
What happens if you skip Refactor "just this once" repeatedly?
Duplication and mess accumulate, tests get harder to write, and eventually the design pain slows you down — the discipline only pays off if Refactor is a non-optional third of the loop.
Can a passing test suite ever give false confidence?
Yes — green only means the checked behaviors hold. Missing tests, weak assertions, or inflated Code Coverage can make a green bar hide real gaps.
How do you TDD code that depends on the current time or a random number?
Inject the clock or random source as a dependency and pass a fixed fake value in the test (e.g. always return
2024-01-01 or seed 0). That removes the non-determinism so the same test always gives the same red/green result.How should integration tests fit alongside your unit-level TDD loop?
Keep a small number of slower integration tests that exercise the real database/network in a separate suite; drive the design with fast, isolated unit tests in the inner loop and run the integration suite less often (typically in Continuous Integration). This is the classic test pyramid: many fast unit tests, few slow integration tests.
What do you do the moment you discover a test is flaky?
Stop and fix or quarantine it immediately — never leave it in the green-required suite. A tolerated flaky test erodes trust in every other test, so it is treated as a bug in its own right, not a nuisance to ignore.