{"id":"unit-testing-troubleshooting","slug":"troubleshooting","title":"Bugs and troubleshooting step by step","text":"Programs rarely work right on the first try, and finding bugs is a big part of programming. This module is about doing it on purpose and step by step: reading what Java tells you, narrowing a problem down, and then writing tests that check your code for you, every time it changes.\n\n### Three kinds of errors\n\n| Kind | When it shows | What points to it |\n| --- | --- | --- |\n| A compile error | Before the program runs | javac names the file, the line and the problem |\n| A crash | While the program runs | The exception and its stack trace name the line |\n| A wrong result | After it runs, if you check | Nothing: you have to find it yourself |\n\nThe first two tell you where to look. The third is the sneaky one: the program ends normally, and what it prints just isn't right. Nobody may notice for a long time, and that's why most of this module is about catching it.\n\n### A stack trace through several classes\n\nModule 12 showed how to read a crash: the exception, its message, and the `at` lines, with the method that crashed at the top. That list of `at` lines is called a **stack trace**. In a program of several classes, each line names the class, the method and the file. An orchard has apple trees, and each tree tells how many of its apples are ripe:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Orchard orchard = new Orchard();\n        orchard.plant(new AppleTree(\"Old Gala\", 50, 20));\n        orchard.plant(new AppleTree(\"Sapling\", 0, 0));\n        orchard.plant(new AppleTree(\"Big Fuji\", 80, 60));\n        orchard.printReport();\n    }\n}\n// ==== Orchard.java ====\nimport java.util.ArrayList;\n\npublic class Orchard {\n    private ArrayList<AppleTree> trees;\n\n    public Orchard() {\n        this.trees = new ArrayList<>();\n    }\n\n    public void plant(AppleTree tree) {\n        this.trees.add(tree);\n    }\n\n    public void printReport() {\n        for (AppleTree tree : this.trees) {\n            System.out.println(tree.getName() + \": \" + tree.percentRipe() + \"% ripe\");\n        }\n    }\n}\n// ==== AppleTree.java ====\npublic class AppleTree {\n    private String name;\n    private int apples;\n    private int ripe;\n\n    public AppleTree(String name, int apples, int ripe) {\n        this.name = name;\n        this.apples = apples;\n        this.ripe = ripe;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int percentRipe() {\n        return 100 * this.ripe / this.apples;\n    }\n}\n```\n\n```output\nOld Gala: 40% ripe\n```\n\n```crash\nException in thread \"main\" java.lang.ArithmeticException: / by zero\n\tat AppleTree.percentRipe(AppleTree.java:17)\n\tat Orchard.printReport(Orchard.java:16)\n\tat Main.main(Main.java:7)\n```\n\nRead it from the top, one line at a time:\n\n- `at AppleTree.percentRipe(AppleTree.java:17)`: the division on line 17 of `AppleTree.java`, in the method `percentRipe`, divided by zero.\n- `at Orchard.printReport(Orchard.java:16)`: `percentRipe` was called by the orchard's `printReport`, on line 16 of `Orchard.java`.\n- `at Main.main(Main.java:7)`: and `printReport` was called on line 7 of `main`.\n\nEach line is a method that was still running, waiting for the method above it to return. The trace doesn't say which tree it was, but the output does: Old Gala was printed, so the crash came with the next tree, the sapling, which has no apples yet. A tree without apples is a real thing, so the fix belongs in `percentRipe`: return 0 when `apples` is 0, before dividing.\n\nOn your own computer, a stack trace often has lines from inside Java's own classes too, above yours. They start with `at java.base/`, for example when a list's `get` is given an index that doesn't exist. Java Arena leaves them out. When you see them, read down to the first line that names one of your own classes: that's the last place your code was.\n\n### A checklist for troubleshooting\n\nWhen a program does something wrong and you don't see why, changing things at random usually makes it worse. Work through it step by step:\n\n1. **Make the bug happen again.** Find an input that shows it every time, as small as you can. Write down what the program should print for it, and what it prints.\n2. **Read everything Java tells you.** The first line of a compile error or a crash, and the first `at` line in your own code.\n3. **Narrow it down.** Find the first place where a value goes wrong. Try one method at a time with a value you know the answer for, or print values along the way (module 12).\n4. **Change one thing, then run again.** If it didn't help, undo it before you try something else.\n5. **Try the other cases again.** A fix for one input can break another input that worked before.\n6. **Clean up.** Remove the debug prints.\n\nSteps 1 and 5 mean running the same inputs again and again. The rest of this module shows how to make that automatic.\n","task":"A pantry keeps supplies of food, and `gramsOf` tells how many grams of a food there are. The program crashes:\n\n```crash\nException in thread \"main\" java.lang.NullPointerException: Cannot invoke \"Supply.getGrams()\" because the return value of \"Pantry.find(String)\" is null\n\tat Pantry.gramsOf(Pantry.java:24)\n\tat Main.main(Main.java:7)\n```\n\n`find` returns `null` when the pantry has no supply of that food, and `gramsOf` calls `getGrams()` on what it got. Change `gramsOf` in `Pantry.java` so it returns 0 for a food that isn't in the pantry. `Main.java` is ready, and with it the program prints:\n\n```\n750\n0\n```\n","kind":"code","seed":"// ==== Pantry.java ====\nimport java.util.ArrayList;\n\npublic class Pantry {\n    private ArrayList<Supply> supplies;\n\n    public Pantry() {\n        this.supplies = new ArrayList<>();\n    }\n\n    public void add(Supply supply) {\n        this.supplies.add(supply);\n    }\n\n    public Supply find(String food) {\n        for (Supply supply : this.supplies) {\n            if (supply.getFood().equals(food)) {\n                return supply;\n            }\n        }\n        return null;\n    }\n\n    public int gramsOf(String food) {\n        return this.find(food).getGrams();\n    }\n}\n\n// ==== Supply.java ====\npublic class Supply {\n    private String food;\n    private int grams;\n\n    public Supply(String food, int grams) {\n        this.food = food;\n        this.grams = grams;\n    }\n\n    public String getFood() {\n        return this.food;\n    }\n\n    public int getGrams() {\n        return this.grams;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Pantry pantry = new Pantry();\n        pantry.add(new Supply(\"rice\", 500));\n        pantry.add(new Supply(\"oats\", 750));\n        System.out.println(pantry.gramsOf(\"oats\"));\n        System.out.println(pantry.gramsOf(\"sugar\"));\n    }\n}\n","solution":"// ==== Pantry.java ====\nimport java.util.ArrayList;\n\npublic class Pantry {\n    private ArrayList<Supply> supplies;\n\n    public Pantry() {\n        this.supplies = new ArrayList<>();\n    }\n\n    public void add(Supply supply) {\n        this.supplies.add(supply);\n    }\n\n    public Supply find(String food) {\n        for (Supply supply : this.supplies) {\n            if (supply.getFood().equals(food)) {\n                return supply;\n            }\n        }\n        return null;\n    }\n\n    public int gramsOf(String food) {\n        Supply supply = this.find(food);\n        if (supply == null) {\n            return 0;\n        }\n        return supply.getGrams();\n    }\n}\n\n// ==== Supply.java ====\npublic class Supply {\n    private String food;\n    private int grams;\n\n    public Supply(String food, int grams) {\n        this.food = food;\n        this.grams = grams;\n    }\n\n    public String getFood() {\n        return this.food;\n    }\n\n    public int getGrams() {\n        return this.grams;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Pantry pantry = new Pantry();\n        pantry.add(new Supply(\"rice\", 500));\n        pantry.add(new Supply(\"oats\", 750));\n        System.out.println(pantry.gramsOf(\"oats\"));\n        System.out.println(pantry.gramsOf(\"sugar\"));\n    }\n}\n","hints":["The first `at` line is `Pantry.gramsOf`, line 24 of `Pantry.java`, and the message says what was null there: the value `find` returned. So the fix goes in `gramsOf`, before `getGrams()` is called.","Keep what `find` returns in a variable, `Supply supply = this.find(food);`, then `if (supply == null) { return 0; }`, and after that `return supply.getGrams();`."],"tests":[{"name":"Test 1","stdin":"","call":"Pantry pantry = new Pantry();\npantry.add(new Supply(\"rice\", 500));\npantry.add(new Supply(\"oats\", 750));\nSystem.out.println(pantry.gramsOf(\"oats\"));\nSystem.out.println(pantry.gramsOf(\"sugar\"));\n","expect":"750\n0","hidden":false},{"name":"Test 2","stdin":"","call":"Pantry pantry = new Pantry();\nSystem.out.println(pantry.gramsOf(\"tea\"));\npantry.add(new Supply(\"tea\", 100));\nSystem.out.println(pantry.gramsOf(\"tea\"));\nSystem.out.println(pantry.gramsOf(\"Tea\"));\n","expect":"0\n100\n0","hidden":true},{"name":"Test 3","stdin":"","call":"Pantry pantry = new Pantry();\npantry.add(new Supply(\"flour\", 1000));\npantry.add(new Supply(\"salt\", 250));\nSystem.out.println(pantry.gramsOf(\"salt\") + pantry.gramsOf(\"flour\"));\nSystem.out.println(pantry.find(\"honey\") == null);\n","expect":"1250\ntrue","hidden":true}],"require":[],"forbid":[],"more":[{"task":"Here is a crash from a program you can't see:\n\n```crash\nException in thread \"main\" java.lang.IndexOutOfBoundsException: Index 4 out of bounds for length 4\n\tat Shelf.bookAt(Shelf.java:18)\n\tat Library.lastBookOn(Library.java:31)\n\tat Main.main(Main.java:9)\n```\n\nFill in the blanks: the class whose code was running when it crashed, the method of `Library` that called it, and the line of `Library.java` that made the call.\n","kind":"fill","seed":"public class Main {\n    public static void main(String[] args) {\n        String crashedClass = \"[[Shelf]]\";\n        String caller = \"[[lastBookOn]]\";\n        int callerLine = [[31]];\n        System.out.println(crashedClass + \" crashed, called by \" + caller + \" on line \" + callerLine);\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        String crashedClass = \"Shelf\";\n        String caller = \"lastBookOn\";\n        int callerLine = 31;\n        System.out.println(crashedClass + \" crashed, called by \" + caller + \" on line \" + callerLine);\n    }\n}\n","hints":["The top `at` line is where the crash happened, and the line under it is the method that called it.","It crashed in `Shelf` (in its method `bookAt`), which was called by `lastBookOn` on line 31 of `Library.java`."],"tests":[{"name":"Output","stdin":"","expect":"Shelf crashed, called by lastBookOn on line 31","hidden":false}],"require":[],"forbid":[]},{"task":"A bike shop rents bikes for 3 euros for the first hour and 2 euros for every hour after it. So 1 hour costs 3 euros, 2 hours cost 5 and 3 hours cost 7. Today three bikes were rented, for 1, 3 and 2 hours, and the program should print `Earnings: 15 euros`, but it prints `Earnings: 19 euros`. Nothing crashes, so no line is named for you.\n\nUse the checklist: try the methods one at a time with values you know the answer for, find the one wrong line, and fix it. The program then prints:\n\n```\nEarnings: 15 euros\n```\n","kind":"code","seed":"// ==== Rental.java ====\npublic class Rental {\n    private int hours;\n\n    public Rental(int hours) {\n        this.hours = hours;\n    }\n\n    public int fee() {\n        if (this.hours <= 1) {\n            return 3;\n        }\n        return 3 + this.hours * 2;\n    }\n}\n\n// ==== BikeShop.java ====\nimport java.util.ArrayList;\n\npublic class BikeShop {\n    private ArrayList<Rental> rentals;\n\n    public BikeShop() {\n        this.rentals = new ArrayList<>();\n    }\n\n    public void rent(int hours) {\n        this.rentals.add(new Rental(hours));\n    }\n\n    public int earnings() {\n        int total = 0;\n        for (Rental rental : this.rentals) {\n            total += rental.fee();\n        }\n        return total;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        BikeShop shop = new BikeShop();\n        shop.rent(1);\n        shop.rent(3);\n        shop.rent(2);\n        System.out.println(\"Earnings: \" + shop.earnings() + \" euros\");\n    }\n}\n","solution":"// ==== Rental.java ====\npublic class Rental {\n    private int hours;\n\n    public Rental(int hours) {\n        this.hours = hours;\n    }\n\n    public int fee() {\n        if (this.hours <= 1) {\n            return 3;\n        }\n        return 3 + (this.hours - 1) * 2;\n    }\n}\n\n// ==== BikeShop.java ====\nimport java.util.ArrayList;\n\npublic class BikeShop {\n    private ArrayList<Rental> rentals;\n\n    public BikeShop() {\n        this.rentals = new ArrayList<>();\n    }\n\n    public void rent(int hours) {\n        this.rentals.add(new Rental(hours));\n    }\n\n    public int earnings() {\n        int total = 0;\n        for (Rental rental : this.rentals) {\n            total += rental.fee();\n        }\n        return total;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        BikeShop shop = new BikeShop();\n        shop.rent(1);\n        shop.rent(3);\n        shop.rent(2);\n        System.out.println(\"Earnings: \" + shop.earnings() + \" euros\");\n    }\n}\n","hints":["Narrow it down: in `main`, print `new Rental(1).fee()`, `new Rental(2).fee()` and `new Rental(3).fee()`. You know they should be 3, 5 and 7. Which ones are wrong?","A 2-hour rental comes out as 7 instead of 5: `fee` charges 2 euros for every hour, the first one too, although the first hour is already in the 3 euros. The extra hours are `this.hours - 1`."],"tests":[{"name":"Test 1","stdin":"","call":"BikeShop shop = new BikeShop();\nshop.rent(1);\nshop.rent(3);\nshop.rent(2);\nSystem.out.println(\"Earnings: \" + shop.earnings() + \" euros\");\n","expect":"Earnings: 15 euros","hidden":false},{"name":"System.out.println(new Rental(1).fee() + \" \" + new Rental(2).fee() + \" \" + new Rental(5).fee());","stdin":"","call":"System.out.println(new Rental(1).fee() + \" \" + new Rental(2).fee() + \" \" + new Rental(5).fee());\n","expect":"3 5 11","hidden":true},{"name":"Test 3","stdin":"","call":"BikeShop shop = new BikeShop();\nSystem.out.println(shop.earnings());\nshop.rent(10);\nshop.rent(4);\nSystem.out.println(shop.earnings());\n","expect":"0\n30","hidden":true}],"require":[],"forbid":[]}]}