Lecture 5: Test-Driven Development Basics CSE 116/504 – Intro. To Computer Science for Majors II Lecture 5: Test-Driven Development Basics
Announcements Normal homework schedule starting with this one Always due Mondays at 12:45PM (15 min. before class) Use problems to learn concepts before quiz & tests Can always attempt problems before its lecture When the topics seem difficult, do not get too worried Wait for lecture to ask questions, will cover topics then
Today’s Goals Continue real-life TDD example: TopHat Learn test-driven development goals & benefits Think & discuss why do we care about testing? How TDD differs and why it improves code quality Discuss what is an annotation & how used in testing JUnit test case basics & best practices discussed
Good Code Key Concept #1
Good Code Key Concept #1 Good Code Works
Good Code Works
Good Code Works
Test-Driven Development (TDD) Very popular technique developing software
Test-Driven Development (TDD) Very popular technique developing software
Test-Driven Development (TDD) Very popular technique developing software Greatly increases programmer productivity Happier programmers also found when using TDD Requires very different approach to coding Requires tests written BEFORE implementations Write stubs (but no code) for class & methods Knowing correct outputs, write tests to check Implement methods to pass your tests (& check often)
Common Objection To TDD But… How could I write tests before I know what the method does?
Smart Programmer Responds… How could you write the code before you know what the method must do?
TDD Objections Feels weird to start with tests when you first try… .. but objections reflect poor programming habits Know method’s outcomes when starting to write… … but you may not know how it will be done
Thought Experiment Have new engine uses 100x less gas New power generation method provides this boost Of the following which test would you want: Belts and axels moved identically with new engine New engine still uses same carburetor & pistons
TDD Objections Feels weird to start with tests when you first try… .. but objections reflect poor programming habits Know method’s outcomes when starting to write… … but you may not know how it will be done Effects, not process, checked by good test cases It might be that you find faster code for method Other methods not rewritten as result of improvement Important that your tests show changes not needed
Check method’s results NOT method’s processes TDD Key Concept #1 Check method’s results NOT method’s processes
Why TDD Helps Many benefits when test cases created first Clarify what method does so coding becomes easier Since written for tests, methods become easier to fix Know when code done by checking if tests pass Studies have found that TDD simplifies debugging Know when bug created by running tests regularly Tests start to fail after writing 1 line: bug on that line Bugs… somewhere(?) when hours pass before testing
Unit Testing Library Nearly everyone uses JUnit for unit testing Tests written in Java so can run anywhere Strong support that make writing tests very easy JUnit support in most IDEs (e.g., Eclipse, IntelliJ, BlueJ) Not limited to Java – xUnit exists for many languages Automation important to allow frequent testing Testing easy – just select “Run As…” “Junit test” Green means good – checking results also very easy Easy to see problems – see red when tests fail
JUnit Test Cases Format Most often write test class for each class Identical to other classes; usually named ____Test As a Java class, can use inheritance & have fields Inheritance & fields not required like any Java class Since ver. 4.0, JUnit’s annotations identify tests Extra information added to show method is test case Annotation added immediately before method
JUnit Test Cases Format Most often write test class for each class Identical to other classes; usually named ____Test As a Java class, can use inheritance & have fields Inheritance & fields not required like any Java class Since ver. 4.0, JUnit’s annotations identify tests Extra information added to show method is test case Annotation added immediately before method No code or comments between annotation & method
Writing Test Methods Test methods MUST have @Test annotation Only non-static void methods can use annotation To be usable, test methods cannot have parameters Use normal Java code to write these test methods Just normal methods; can contain any legal Java code Conditionals and loop statements often are included Can instantiate objects and call methods on objects Other methods can use assert___ & be called by tests
JUnit Annotations Loop over each test case in the entire class JUnit executes all methods marked @Before Optional methods initialize any data before each test Will have exactly 1 method labeled @Test executed Pass if no crashes & no incorrect asserts occur during test Fails & stops running when assert____ does not pass Executes all methods marked @After Also optional, cleans mess made by @Before
JUnit Annotations Loop over each test case in the entire class JUnit executes all methods marked @Before Optional methods initialize any data before each test Will have exactly 1 method labeled @Test executed Pass if no crashes & no incorrect asserts occur during test Fails & stops running when assert____ does not pass Executes all methods marked @After Also optional, cleans mess made by @Before
assert____ Statements Java statements which actually check results: assertTrue(boolean test) assertFalse(boolean test) assertEquals(X expected, X actual) assertNull(Object objTest) assertNotNull(Object objTest) fail()
assert____ Statements Java statements which actually check results: assertTrue(String message, boolean test) assertFalse(String message, boolean test) assertEquals(String message,X expected, X actual) assertNull(String message, Object objTest) assertNotNull(String message, Object objTest) fail(String message) Can add optional message. Use it when you have additional information for failing test.
Outline of (Most) Test Methods Declare variable(s), create objects, & prep inputs Call the method being tested Use specification to verify returned value correct Check fields for changes matching requirements Should also verify other fields did not change Assert object arguments are correct
JUnit Tests Key Concept #2 Every @Test method should execute AT LEAST 1 assert_____()
JUnit Test Case Ordering 1 Place lines into proper order 1. assertEquals(2,actual); 2. al.add(s); al.add(s); 3. int actual = al.size(); 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”;
JUnit Test Case Ordering 1 Place lines into proper order 1. assertEquals(2,actual); 2. al.add(s); al.add(s); 3. int actual = al.size(); 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; Convince your neighbor your answer is correct
JUnit Test Case Ordering 1 Place lines into proper order 1. assertEquals(2,actual); 2. al.add(s); al.add(s); 3. int actual = al.size(); 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”;
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 3. int actual = al.size(); 1. assertEquals(2,actual); 2. al.add(s); al.add(s);
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 3. int actual = al.size(); 1. assertEquals(2,actual); 2. al.add(s); al.add(s);
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 3. int actual = al.size(); 1. assertEquals(2,actual); 2. al.add(s); al.add(s);
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 3. int actual = al.size(); 1. assertEquals(2,actual); 2. al.add(s); al.add(s);
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 3. int actual = al.size(); 1. assertEquals(2,actual); 2. al.add(s); al.add(s);
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 3. int actual = al.size(); 3. int actual = al.size(); 2. al.add(s); al.add(s);
Reminder
JUnit Test Case Ordering 1 4. ArrayList<String> al = new ArrayList<String>(); String s = “Testing”; 2. al.add(s); al.add(s); 3. int actual = al.size(); 1. assertEquals(2,actual);
Class To Be Tested public class Operational { private int a, b; public Operational(int fst, int snd) { a = fst; b = snd; } public String multiply() { int product = a * b; return “Product is ”+product; } public String sum() { return “Sum is ”+a+b; } }
Class To Be Tested public class Operational { private int a, b; public Operational(int fst, int snd) { a = fst; b = snd; } public String multiply() { int product = a * b; return “Product is ”+product; } public String sum() { return “Sum is ”+a+b; } } This concatenates a & b (like Strings) rather than adding them. So 3 + 2 will be 32 and not 5. Our tests need to find this!
Starting to Write Test Cases Traditionally, test class adds “Test” to class name Each case is method having @Test annotation public class OperationalTest { @Test public void testMult0() { Operational xByZero = new Operational(3, 0); Operational zeroByX = new Operational(0, 17); assertEquals(“Product is 0”, xByZero.multiply()); assertEquals(“Product is 0”, zeroByX.multiply()); }
Starting to Write Test Cases Traditionally, test class adds “Test” to class name Each case is method having @Test annotation public class OperationalTest { @Test public void testMult0() { Operational xByZero = new Operational(3, 0); Operational zeroByX = new Operational(0, 17); assertEquals(“Product is 0”, xByZero.multiply()); assertEquals(“Product is 0”, zeroByX.multiply()); }
Starting to Write Test Cases Traditionally, test class adds “Test” to class name Each case is method having @Test annotation public class OperationalTest { @Test public void testMult0() { Operational xByZero = new Operational(3, 0); Operational zeroByX = new Operational(0, 17); assertEquals(“Product is 0”, xByZero.multiply()); assertEquals(“Product is 0”, zeroByX.multiply()); }
Bad Tests to Write Only get to know test failed with assertTrue() @Test public void yuckyButLegal() { Operational xByOne = new Operational(-2, 1); Operational oneByX = new Operational(1, 4); assertTrue(xByOne.multiply() .equals(“Product is 2”)); }
Bad Tests to Write Only get to know test failed with assertTrue() @Test public void yuckyButLegal() { Operational xByOne = new Operational(-2, 1); Operational oneByX = new Operational(1, 4); assertTrue(xByOne.multiply() .equals(“Product is 2”)); }
Better Tests to Write Get more detailed results with assertEquals() @Test public void muchImproved() { Operational xByOne = new Operational(-2, 1); Operational oneByX = new Operational(1, 4); assertEquals(xByOne.multiply(), “Product is 2”); }
Better Tests to Write Get more detailed results with assertEquals() @Test public void muchImproved() { Operational xByOne = new Operational(-2, 1); Operational oneByX = new Operational(1, 4); assertEquals(xByOne.multiply(), “Product is 2”); }
JUnit Tests Key Concept #3 Use assertTrue() & assertFalse() only with boolean variables & methods
More Test Gotchas Must be certain assert___ checking code to test @Test public void whatAmITesting() { Operational xByOne = new Operational(-2, 1); assertEquals(“Product is -2”, “Product is ” + (-2 * 1)); }
More Test Gotchas Must be certain assert___ checking code to test @Test public void whatAmITesting() { Operational xByOne = new Operational(-2, 1); assertEquals(“Product is -2”, “Product is ” + (-2 * 1)); }
More Test Gotchas Must be certain assert___ checking code to test @Test public void whatAmITesting() { Operational xByOne = new Operational(-2, 1); assertEquals(“Product is -2”, “Product is ” + (-2 * 1)); }
More Test Gotchas Will this pass? @Test public void passingIsNotFailing() { Operational xByOne = new Operational(-2, 1); xByOne.sum(); }
More Test Gotchas Will this pass? @Test public void passingIsNotFailing() { Operational xByOne = new Operational(-2, 1); xByOne.sum(); }
More Test Gotchas Will this pass? @Test public void passingIsNotFailing() { Operational xByOne = new Operational(-2, 1); xByOne.sum(); }
JUnit Tests Key Concept #2 Every @Test method should call AT LEAST 1 assert_____()
Hardcode Your Checks Hardcode answer in assert___ whenever possible @Test public void shouldNotPassButDoesPass() { Operational xByOne = new Operational(-2, 1); assertEquals(“Sum is ” + -2 + 1, xByOne.sum()); }
Hardcode Your Checks Hardcode answer in assert___ whenever possible @Test public void shouldNotPassButDoesPass() { Operational xByOne = new Operational(-2, 1); assertEquals(“Sum is ” + -2 + 1, xByOne.sum()); }
Hardcode Your Checks Hardcode answer in assert___ whenever possible @Test public void canNowFindBug() { Operational xByOne = new Operational(-2, 1); assertEquals(“Sum is -1”, xByOne.sum()); }
Select Real JUnit Annotation(s) @Test @First @Fail @After @TestClass
Select Real JUnit Annotation(s) @Test @First @Fail @After @TestClass Convince your neighbor of the correct answer(s)
Select Real JUnit Annotation(s) @Test @First @Fail @After @TestClass
Select Real JUnit Annotation(s) @Test needed by JUnit to know methods it should run @Test @First @Fail @After @TestClass
Select Real JUnit Annotation(s) JUnit cannot guarantee order methods are run @Test @First @Fail @After @TestClass
Select Real JUnit Annotation(s) fail() is JUnit assertion that method calls to force test to fail @Test @First @Fail @After @TestClass
Select Real JUnit Annotation(s) @After used on method(s) run after each JUnit test method @Test @First @Fail @After @TestClass
Select Real JUnit Annotation(s) I just made this one up @Test @First @Fail @After @TestClass Image from: https://i.imgflip.com/nf5rh.jpg
Select Real JUnit Annotation(s) @Test @First @Fail @After @TestClass
Last Detail Often make assumptions when writing code True writing code and still true writing test cases Passing != correct; could be no tests were run May not check what you think; care needed writing tests Tests should be written & run before coding Tests better fail, since code has not yet been written! Before wasting time, always check assumptions
Last Detail Often make assumptions when writing code True writing code and still true writing test cases Passing != correct; could be no tests were run May not check what you think; care needed writing tests Tests should be written & run before coding Tests better fail, since code has not yet been written! Before wasting time, always check assumptions
Good Code Key Concept #1 Good Code Works
Check method’s results NOT method’s processes TDD Key Concept #1 Check method’s results NOT method’s processes
JUnit Tests Key Concept #2 Every @Test method should execute AT LEAST 1 assert_____()
JUnit Tests Key Concept #3 Use assertTrue() & assertFalse() only with boolean variables & methods
For Next Lecture Read webpage & section 2.4 – 2.7 for Monday Going more in depth into writing good JUnit tests How to decide code done when grading no longer occurs Will discuss shortcuts for developing suites of test cases Week #2 homework posted and ready to finish Past add/drop so will always be due on Monday at 12:45