{"id":"unit-testing-tdd","slug":"test-driven-development","title":"Test-driven development","text":"So far, the code came first and its tests afterwards. **Test-driven development**, or TDD for short, turns that around: before you write a piece of code, you write a test for it. The work goes in small rounds:\n\n1. Write one test for something the class doesn't do yet.\n2. Run the tests and see the new one fail. A test that passes before the code even exists isn't testing anything.\n3. Write just enough code to make it pass, and no more.\n4. Run all the tests. They should all pass, the old ones too.\n5. Tidy up the code if it needs it, running the tests after each change. Then start the next round.\n\nHere's a countdown for a kitchen timer, built this way.\n\n### Round 1: the starting time\n\nThe first test says that a new countdown of 90 seconds has 90 seconds left. For the test to compile, `Countdown` needs a constructor and a method `secondsLeft()`, so they're written first as empty shells, with `secondsLeft()` returning 0 for now:\n\n```java\n// ==== Countdown.java ====\npublic class Countdown {\n    public Countdown(int seconds) {\n    }\n\n    public int secondsLeft() {\n        return 0;\n    }\n}\n// ==== CountdownTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CountdownTest {\n    @Test\n    public void startsWithItsSeconds() {\n        Countdown countdown = new Countdown(90);\n        assertEquals(90, countdown.secondsLeft());\n    }\n}\n```\n\n```output\nCountdownTest: 1 test\n  startsWithItsSeconds: FAILED\n    java.lang.AssertionError: expected:<90> but was:<0>\n    at CountdownTest.startsWithItsSeconds(CountdownTest.java:9)\n0 of 1 test passed\n```\n\nThe test fails, as it should. Now just enough code to pass it: the countdown keeps its seconds in an instance variable, and returns them.\n\n```java\nprivate int seconds;\n\npublic Countdown(int seconds) {\n    this.seconds = seconds;\n}\n\npublic int secondsLeft() {\n    return this.seconds;\n}\n```\n\nRun the tests again, and `startsWithItsSeconds` passes.\n\n### Round 2: a tick\n\nThe next test: after one `tick()`, there's a second less.\n\n```java\n@Test\npublic void tickTakesOneSecond() {\n    Countdown countdown = new Countdown(90);\n    countdown.tick();\n    assertEquals(89, countdown.secondsLeft());\n}\n```\n\nIt doesn't even compile, since there's no `tick` yet. That counts as failing too. An empty `public void tick() { }` makes it compile, and the test fails with `expected:<89> but was:<90>`. Then `this.seconds--;` inside `tick` makes it pass, and the first test still passes too.\n\n### Round 3: never below zero\n\nA timer that has run out stays at zero. That's the next test, and it fails:\n\n```java\n// ==== Countdown.java ====\npublic class Countdown {\n    private int seconds;\n\n    public Countdown(int seconds) {\n        this.seconds = seconds;\n    }\n\n    public void tick() {\n        this.seconds--;\n    }\n\n    public int secondsLeft() {\n        return this.seconds;\n    }\n}\n// ==== CountdownTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CountdownTest {\n    @Test\n    public void startsWithItsSeconds() {\n        Countdown countdown = new Countdown(90);\n        assertEquals(90, countdown.secondsLeft());\n    }\n\n    @Test\n    public void tickTakesOneSecond() {\n        Countdown countdown = new Countdown(90);\n        countdown.tick();\n        assertEquals(89, countdown.secondsLeft());\n    }\n\n    @Test\n    public void stopsAtZero() {\n        Countdown countdown = new Countdown(1);\n        countdown.tick();\n        countdown.tick();\n        assertEquals(0, countdown.secondsLeft());\n    }\n}\n```\n\n```output\nCountdownTest: 3 tests\n  startsWithItsSeconds: passed\n  stopsAtZero: FAILED\n    java.lang.AssertionError: expected:<0> but was:<-1>\n    at CountdownTest.stopsAtZero(CountdownTest.java:24)\n  tickTakesOneSecond: passed\n2 of 3 tests passed\n```\n\nJust enough code to pass it is one `if`:\n\n```java\npublic void tick() {\n    if (this.seconds > 0) {\n        this.seconds--;\n    }\n}\n```\n\nNow all three tests pass. The next round could add `isDone()`, then a way to add a minute, and so on: one test, then the code for it.\n\n### Why work this way\n\n- You decide what the code should do before you decide how. The test is the class's first user, so an awkward method name or parameter shows up early.\n- Every piece of code has a test from the start, and the tests stay. When a later change breaks something that used to work, a test fails right away.\n- The rounds are small. At any moment at most one test fails, and you know that the problem is in the few lines you just wrote.\n\nNot everyone works test first. Many programmers write the tests right after the code, and that works too. What matters is that the tests get written, and that you have seen each one fail at least once, so you know it can.\n","task":"A coffee machine has a water tank that holds a number of milliliters. `CoffeeMachineTest` is written already, and your job is to make its tests pass. `CoffeeMachine.java` has empty methods, so the tests compile, and three of the four fail at first. `cannotUseMoreThanIsLeft` passes by luck, because `left()` returns 0 for now. It starts failing once `brew` takes water without checking how much is left: that's the mistake it's there to catch. Run the tests, pick one failing test, write just enough code for it, and run them again, until all of them pass:\n\n- A new machine's tank is full: `left()` returns its capacity.\n- `brew(int ml)` uses that much water, but never more than is left.\n- `refill()` fills the tank back up.\n\nThe checks run the tests of `CoffeeMachineTest` as they are given here (changing them won't help), and more tests of their own.\n","kind":"code","seed":"// ==== CoffeeMachine.java ====\npublic class CoffeeMachine {\n    public CoffeeMachine(int capacity) {\n    }\n\n    public void brew(int ml) {\n    }\n\n    public void refill() {\n    }\n\n    public int left() {\n        return 0;\n    }\n}\n\n// ==== CoffeeMachineTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CoffeeMachineTest {\n    @Test\n    public void newMachineIsFull() {\n        CoffeeMachine machine = new CoffeeMachine(750);\n        assertEquals(750, machine.left());\n    }\n\n    @Test\n    public void brewingUsesWater() {\n        CoffeeMachine machine = new CoffeeMachine(750);\n        machine.brew(200);\n        assertEquals(550, machine.left());\n    }\n\n    @Test\n    public void cannotUseMoreThanIsLeft() {\n        CoffeeMachine machine = new CoffeeMachine(500);\n        machine.brew(300);\n        machine.brew(300);\n        assertEquals(0, machine.left());\n    }\n\n    @Test\n    public void refillFillsItUp() {\n        CoffeeMachine machine = new CoffeeMachine(500);\n        machine.brew(450);\n        machine.refill();\n        assertEquals(500, machine.left());\n    }\n}\n","solution":"// ==== CoffeeMachine.java ====\npublic class CoffeeMachine {\n    private int capacity;\n    private int left;\n\n    public CoffeeMachine(int capacity) {\n        this.capacity = capacity;\n        this.left = capacity;\n    }\n\n    public void brew(int ml) {\n        if (ml > this.left) {\n            this.left = 0;\n        } else {\n            this.left = this.left - ml;\n        }\n    }\n\n    public void refill() {\n        this.left = this.capacity;\n    }\n\n    public int left() {\n        return this.left;\n    }\n}\n\n// ==== CoffeeMachineTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CoffeeMachineTest {\n    @Test\n    public void newMachineIsFull() {\n        CoffeeMachine machine = new CoffeeMachine(750);\n        assertEquals(750, machine.left());\n    }\n\n    @Test\n    public void brewingUsesWater() {\n        CoffeeMachine machine = new CoffeeMachine(750);\n        machine.brew(200);\n        assertEquals(550, machine.left());\n    }\n\n    @Test\n    public void cannotUseMoreThanIsLeft() {\n        CoffeeMachine machine = new CoffeeMachine(500);\n        machine.brew(300);\n        machine.brew(300);\n        assertEquals(0, machine.left());\n    }\n\n    @Test\n    public void refillFillsItUp() {\n        CoffeeMachine machine = new CoffeeMachine(500);\n        machine.brew(450);\n        machine.refill();\n        assertEquals(500, machine.left());\n    }\n}\n","hints":["Start with `newMachineIsFull`: the machine needs to remember how much water its tank has, in an instance variable set in the constructor. `refill` needs the capacity too, so keep both: `capacity` and `left`.","In `brew`: `if (ml > this.left) { this.left = 0; } else { this.left = this.left - ml; }`. In `refill`: `this.left = this.capacity;`."],"tests":[{"name":"Your CoffeeMachine with the tests given","stdin":"","junit":"CoffeeMachineTest","outcome":"pass","replace":{"CoffeeMachineTest.java":"import static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CoffeeMachineTest {\n    @Test\n    public void newMachineIsFull() {\n        CoffeeMachine machine = new CoffeeMachine(750);\n        assertEquals(750, machine.left());\n    }\n\n    @Test\n    public void brewingUsesWater() {\n        CoffeeMachine machine = new CoffeeMachine(750);\n        machine.brew(200);\n        assertEquals(550, machine.left());\n    }\n\n    @Test\n    public void cannotUseMoreThanIsLeft() {\n        CoffeeMachine machine = new CoffeeMachine(500);\n        machine.brew(300);\n        machine.brew(300);\n        assertEquals(0, machine.left());\n    }\n\n    @Test\n    public void refillFillsItUp() {\n        CoffeeMachine machine = new CoffeeMachine(500);\n        machine.brew(450);\n        machine.refill();\n        assertEquals(500, machine.left());\n    }\n}\n"},"expect":"","hidden":false},{"name":"Your CoffeeMachine with more tests","stdin":"","junit":"CoffeeMachineTest","outcome":"pass","replace":{"CoffeeMachineTest.java":"import static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CoffeeMachineTest {\n    @Test\n    public void smallMachineStartsFull() {\n        CoffeeMachine machine = new CoffeeMachine(330);\n        assertEquals(330, machine.left());\n    }\n\n    @Test\n    public void cupsAddUp() {\n        CoffeeMachine machine = new CoffeeMachine(1000);\n        machine.brew(120);\n        machine.brew(80);\n        assertEquals(800, machine.left());\n    }\n\n    @Test\n    public void brewingExactlyAllLeavesNothing() {\n        CoffeeMachine machine = new CoffeeMachine(400);\n        machine.brew(400);\n        assertEquals(0, machine.left());\n    }\n\n    @Test\n    public void refilledMachineWorksAgain() {\n        CoffeeMachine machine = new CoffeeMachine(600);\n        machine.brew(900);\n        machine.refill();\n        machine.brew(100);\n        assertEquals(500, machine.left());\n    }\n}\n"},"expect":"","hidden":true}],"require":[],"forbid":[],"more":[{"task":"The countdown from this step gets a new method, `reset()`, which starts it over from the seconds it was made with. It's in `Countdown.java` as an empty shell. Work test first:\n\n1. In `CountdownTest`, write a new test that makes a countdown, ticks a few times, calls `reset()` and checks `secondsLeft()`. Run the tests: your new test should fail.\n2. Then write `reset()`, and run the tests again: now all of them should pass.\n\nThe checks run your tests on your Countdown, where all should pass, and on the Countdown with the empty `reset()`, where your new test should fail. Then they run tests of their own on your `reset()`.\n","kind":"code","seed":"// ==== CountdownTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CountdownTest {\n    @Test\n    public void startsWithItsSeconds() {\n        Countdown countdown = new Countdown(90);\n        assertEquals(90, countdown.secondsLeft());\n    }\n\n    @Test\n    public void tickTakesOneSecond() {\n        Countdown countdown = new Countdown(90);\n        countdown.tick();\n        assertEquals(89, countdown.secondsLeft());\n    }\n\n    @Test\n    public void stopsAtZero() {\n        Countdown countdown = new Countdown(1);\n        countdown.tick();\n        countdown.tick();\n        assertEquals(0, countdown.secondsLeft());\n    }\n}\n\n// ==== Countdown.java ====\npublic class Countdown {\n    private int seconds;\n\n    public Countdown(int seconds) {\n        this.seconds = seconds;\n    }\n\n    public void tick() {\n        if (this.seconds > 0) {\n            this.seconds--;\n        }\n    }\n\n    public void reset() {\n    }\n\n    public int secondsLeft() {\n        return this.seconds;\n    }\n}\n","solution":"// ==== CountdownTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CountdownTest {\n    @Test\n    public void startsWithItsSeconds() {\n        Countdown countdown = new Countdown(90);\n        assertEquals(90, countdown.secondsLeft());\n    }\n\n    @Test\n    public void tickTakesOneSecond() {\n        Countdown countdown = new Countdown(90);\n        countdown.tick();\n        assertEquals(89, countdown.secondsLeft());\n    }\n\n    @Test\n    public void stopsAtZero() {\n        Countdown countdown = new Countdown(1);\n        countdown.tick();\n        countdown.tick();\n        assertEquals(0, countdown.secondsLeft());\n    }\n\n    @Test\n    public void resetStartsOver() {\n        Countdown countdown = new Countdown(60);\n        countdown.tick();\n        countdown.tick();\n        countdown.reset();\n        assertEquals(60, countdown.secondsLeft());\n    }\n}\n\n// ==== Countdown.java ====\npublic class Countdown {\n    private int start;\n    private int seconds;\n\n    public Countdown(int seconds) {\n        this.start = seconds;\n        this.seconds = seconds;\n    }\n\n    public void tick() {\n        if (this.seconds > 0) {\n            this.seconds--;\n        }\n    }\n\n    public void reset() {\n        this.seconds = this.start;\n    }\n\n    public int secondsLeft() {\n        return this.seconds;\n    }\n}\n","hints":["The test: `Countdown countdown = new Countdown(60);`, two `countdown.tick();` calls, `countdown.reset();` and `assertEquals(60, countdown.secondsLeft());`. Run it before you touch `Countdown.java`: it fails with `expected:<60> but was:<58>`.","Once `tick` has changed `seconds`, the starting value is gone, so the countdown needs a second instance variable, say `start`, set in the constructor. Then `reset()` is `this.seconds = this.start;`."],"tests":[{"name":"Your Countdown with your tests","stdin":"","junit":"CountdownTest","outcome":"pass","expect":"","hidden":false},{"name":"The Countdown with an empty reset, with your tests","stdin":"","junit":"CountdownTest","outcome":"fail","replace":{"Countdown.java":"public class Countdown {\n    private int seconds;\n\n    public Countdown(int seconds) {\n        this.seconds = seconds;\n    }\n\n    public void tick() {\n        if (this.seconds > 0) {\n            this.seconds--;\n        }\n    }\n\n    public void reset() {\n    }\n\n    public int secondsLeft() {\n        return this.seconds;\n    }\n}\n"},"expect":"","hidden":false},{"name":"Your Countdown with tests of reset","stdin":"","junit":"CountdownTest","outcome":"pass","replace":{"CountdownTest.java":"import static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CountdownTest {\n    @Test\n    public void resetStartsOver() {\n        Countdown countdown = new Countdown(45);\n        countdown.tick();\n        countdown.tick();\n        countdown.tick();\n        countdown.reset();\n        assertEquals(45, countdown.secondsLeft());\n    }\n\n    @Test\n    public void resetAfterRunningOut() {\n        Countdown countdown = new Countdown(2);\n        countdown.tick();\n        countdown.tick();\n        countdown.tick();\n        countdown.reset();\n        assertEquals(2, countdown.secondsLeft());\n    }\n\n    @Test\n    public void countsDownAgainAfterReset() {\n        Countdown countdown = new Countdown(10);\n        countdown.tick();\n        countdown.reset();\n        countdown.tick();\n        assertEquals(9, countdown.secondsLeft());\n    }\n}\n"},"expect":"","hidden":false}],"require":[{"pattern":"@Test\\b","min":4,"message":"Write a new test, a method with `@Test` before it, for `reset()`."},{"pattern":"\\.reset\\s*\\(\\s*\\)","message":"Call `reset()` in your new test."}],"forbid":[]},{"task":"A carpool shares a car to work, and keeps the names of its riders, up to the number of seats. `CarpoolTest` is written, and your job is to write `Carpool` so its tests pass, one failing test at a time:\n\n- `new Carpool(seats)` makes an empty carpool with that many seats.\n- `join(String name)` adds the rider and returns `true`. It returns `false` and adds nobody when every seat is taken, or when the same name is in the carpool already.\n- `leave(String name)` takes the rider out of the carpool, if they're in it.\n- `riders()` returns how many riders there are, and `isFull()` whether every seat is taken.\n\nThe checks run the tests of `CarpoolTest` as they are given here (changing them won't help), and more tests of their own.\n","kind":"code","seed":"// ==== Carpool.java ====\nimport java.util.ArrayList;\n\npublic class Carpool {\n    public Carpool(int seats) {\n    }\n\n    public boolean join(String name) {\n        return false;\n    }\n\n    public void leave(String name) {\n    }\n\n    public int riders() {\n        return 0;\n    }\n\n    public boolean isFull() {\n        return false;\n    }\n}\n\n// ==== CarpoolTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CarpoolTest {\n    @Test\n    public void joiningAddsARider() {\n        Carpool carpool = new Carpool(3);\n        assertTrue(carpool.join(\"Mia\"));\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void sameNameCannotJoinTwice() {\n        Carpool carpool = new Carpool(3);\n        carpool.join(\"Mia\");\n        assertFalse(carpool.join(\"Mia\"));\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void fullCarTakesNobodyMore() {\n        Carpool carpool = new Carpool(2);\n        carpool.join(\"Ann\");\n        carpool.join(\"Ben\");\n        assertTrue(carpool.isFull());\n        assertFalse(carpool.join(\"Cy\"));\n    }\n\n    @Test\n    public void leavingMakesRoom() {\n        Carpool carpool = new Carpool(1);\n        carpool.join(\"Ann\");\n        carpool.leave(\"Ann\");\n        assertFalse(carpool.isFull());\n        assertTrue(carpool.join(\"Ben\"));\n    }\n}\n","solution":"// ==== Carpool.java ====\nimport java.util.ArrayList;\n\npublic class Carpool {\n    private int seats;\n    private ArrayList<String> names;\n\n    public Carpool(int seats) {\n        this.seats = seats;\n        this.names = new ArrayList<>();\n    }\n\n    public boolean join(String name) {\n        if (this.isFull() || this.names.contains(name)) {\n            return false;\n        }\n        this.names.add(name);\n        return true;\n    }\n\n    public void leave(String name) {\n        this.names.remove(name);\n    }\n\n    public int riders() {\n        return this.names.size();\n    }\n\n    public boolean isFull() {\n        return this.names.size() >= this.seats;\n    }\n}\n\n// ==== CarpoolTest.java ====\nimport static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CarpoolTest {\n    @Test\n    public void joiningAddsARider() {\n        Carpool carpool = new Carpool(3);\n        assertTrue(carpool.join(\"Mia\"));\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void sameNameCannotJoinTwice() {\n        Carpool carpool = new Carpool(3);\n        carpool.join(\"Mia\");\n        assertFalse(carpool.join(\"Mia\"));\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void fullCarTakesNobodyMore() {\n        Carpool carpool = new Carpool(2);\n        carpool.join(\"Ann\");\n        carpool.join(\"Ben\");\n        assertTrue(carpool.isFull());\n        assertFalse(carpool.join(\"Cy\"));\n    }\n\n    @Test\n    public void leavingMakesRoom() {\n        Carpool carpool = new Carpool(1);\n        carpool.join(\"Ann\");\n        carpool.leave(\"Ann\");\n        assertFalse(carpool.isFull());\n        assertTrue(carpool.join(\"Ben\"));\n    }\n}\n","hints":["The carpool needs two instance variables: the number of seats, and a list of names made in the constructor, `this.names = new ArrayList<>();`. `riders()` is the list's size, and `isFull()` compares it with the seats.","In `join`, turn the rider away first: `if (this.isFull() || this.names.contains(name)) { return false; }`, then add the name and return `true`. The list's `contains` and `remove` compare names with `equals`, so `leave` is just `this.names.remove(name);`."],"tests":[{"name":"Your Carpool with the tests given","stdin":"","junit":"CarpoolTest","outcome":"pass","replace":{"CarpoolTest.java":"import static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CarpoolTest {\n    @Test\n    public void joiningAddsARider() {\n        Carpool carpool = new Carpool(3);\n        assertTrue(carpool.join(\"Mia\"));\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void sameNameCannotJoinTwice() {\n        Carpool carpool = new Carpool(3);\n        carpool.join(\"Mia\");\n        assertFalse(carpool.join(\"Mia\"));\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void fullCarTakesNobodyMore() {\n        Carpool carpool = new Carpool(2);\n        carpool.join(\"Ann\");\n        carpool.join(\"Ben\");\n        assertTrue(carpool.isFull());\n        assertFalse(carpool.join(\"Cy\"));\n    }\n\n    @Test\n    public void leavingMakesRoom() {\n        Carpool carpool = new Carpool(1);\n        carpool.join(\"Ann\");\n        carpool.leave(\"Ann\");\n        assertFalse(carpool.isFull());\n        assertTrue(carpool.join(\"Ben\"));\n    }\n}\n"},"expect":"","hidden":false},{"name":"Your Carpool with more tests","stdin":"","junit":"CarpoolTest","outcome":"pass","replace":{"CarpoolTest.java":"import static org.junit.Assert.*;\n\nimport org.junit.Test;\n\npublic class CarpoolTest {\n    @Test\n    public void newCarpoolIsEmpty() {\n        Carpool carpool = new Carpool(4);\n        assertEquals(0, carpool.riders());\n        assertFalse(carpool.isFull());\n    }\n\n    @Test\n    public void leavingWhenNotInChangesNothing() {\n        Carpool carpool = new Carpool(2);\n        carpool.join(\"Eve\");\n        carpool.leave(\"Zed\");\n        assertEquals(1, carpool.riders());\n    }\n\n    @Test\n    public void namesAreComparedByTheirLetters() {\n        Carpool carpool = new Carpool(5);\n        String start = \"Li\";\n        carpool.join(start + \"am\");\n        assertFalse(carpool.join(\"Liam\"));\n        carpool.leave(\"Liam\");\n        assertEquals(0, carpool.riders());\n    }\n\n    @Test\n    public void carpoolWithNoSeatsIsFull() {\n        Carpool carpool = new Carpool(0);\n        assertTrue(carpool.isFull());\n        assertFalse(carpool.join(\"Kai\"));\n        assertEquals(0, carpool.riders());\n    }\n}\n"},"expect":"","hidden":true}],"require":[],"forbid":[]}]}