Test Driven Development Testing Cooking Book
Test Driven Development Testing Cooking Book. Jonathan Lalou Software Architect Agile Development Scrum Master January 24th, 2012. Disclaimer on this training session. Expected attendees level: beginner / middle Java as well as C++, DotNet, etc. No difficulty on Java Slow rise Targets:
Test Driven Development Testing Cooking Book
E N D
Presentation Transcript
Test Driven DevelopmentTesting Cooking Book Jonathan Lalou Software Architect Agile Development Scrum Master January 24th, 2012
Disclaimer on this training session • Expected attendees level: beginner / middle • Java as well as C++, DotNet, etc. • No difficulty on Java • Slow rise • Targets: • Search, think, try, investigate… no solution “out of the box” • Transmit best practices • Give return of experiences • Improve your skills • Needed: • Brain, pencil, paper • IDE (Eclipse, IDEA) • Jars: JUnit, EasyMock, Log4j • Planned duration: 2h
Test Driven Development Concepts Interest Among other Agile methods Unit Test Cooking Book Basics Methods within methods Layers / Mocks Exceptions Private methods Other Tests Integration tests Runtime tests Stress tests Limitations and Vulnerabilities Plan
MySelf! • Jonathan LALOU • Mines Nancy 2000-2003 • Cadextan / SunGard since 2005 • BNP Arbitrage since 2007: • PW • European PB
BKN Agility • Business Knowledge Network • Agility • Scrum • General (2 sessions, once a month) • Scrum master • Product Owner • TDD (1 session, once a month) • Continuous Integration • Lean • Etc. • For Sungard consultants • For Sungard customers
Who we are Test Driven Development
Concepts • Write test before implementation • Lifecycle • Write a test • Check it fails • Write minimum code to make the test succeed • Check it succeeds • Refactor
Interest • Improve specifications • Provide validation for development • Less debug • Less regression • Safe refactoring • Automatization
Among Other Agile Methods • Other famous Agile methods: Scrum, XP, Continuous Integration • XP loves TDD • One codes the test • One codes the needed program • Continuous Integration • Runs complete test set • Raises errors soon • Version Control System easy roll back
Experience… • Covertures: 80% • Need update • To be run before each commit • Strategy • Quick and dirty: • With private methods • At the bottom of the very class • Need compilation • “No private” methods • In the same package, in another folder
Who we are Unit Test Cooking Book
Who we are Basics for Beginners
Basic (1) Example: “write a service that sums two integers”
Basic (2) Mode “I’ve just got out of school”: 1 publicclass Summer { 2 public Integer sum(Integer a, Integer b){ 3 return a+b; 4 } 5 6 publicstaticvoid main(String[] args){ 7 int foo = new Summer().sum(12, 5); 8 if (foo != 17){ 9 System.err.println("There is an error"); 10 }else{ 11 System.out.println("this is OK"); 12 } 13 14 } 15 }
Basic (3) Mode “TDD” 1/ Create empty class Summer: 1 publicclass Summer { 2 public Integer sum(Integer a, Integer b){ 3 return null; 4 } 2/ Create unit test: 1 publicclass SummerUnitTest extends TestCase { 2 publicvoid testSum() throws Exception { 3 assertEquals(17, new Summer().sum(15, 2)); 4 } 5 } 3/ Complete class Summer: 1 publicclass Summer { 2 public Integer sum(Integer a, Integer b){ 3 return a+b; 4 }
Basic (4) • Framework JUnit • Open source, quasi standard • Integration within IntelliJ IDEA, Eclipse, Netbeans, etc. • Integration with Maven (plugin Surefire) • Galaxy of “XUnit”: NUnit, PHPUnit, PyUnit, etc. • Other framework: TestNG, etc. • JUnit 3 • setUp() • tearDown() • void test*() • JUnit 4 • @Before • @After • @Test
Basic (5): Expectations • Expectations: • Success • Failure • Error • Asserts: • assertEquals • assertSame • assertTrue / assertFalse • assertNull / assertNotNull • fail
Exercise 1 Write a class with following services • get the maximum of two integers • get the maximum of an array of integers • flip an array of integers • says whether a String is a valid email address (please do not use Apache Commons, Spring, etc. in unit tests exercises)
Exercise 1: correction publicclass Exercise1 { public Integer maxOfTwo(Integer a, Integer b) { return a > b ? a : b; } public Integer maxOfArray(int[] args) { /*if (args == null || args.length == 0) { return null; }*/ Integer max = Integer.MIN_VALUE; for (int i = 0; i < args.length; i++) { max = maxOfTwo(max, args[i]); } return max; } publicint[] flip(int[] source) { finalint[] answer = newint[source.length]; for (int i = 0; i < source.length; i++) { answer[i] = source[source.length - i - 1]; } return answer; } }
Exercise 1: correction (JUnit 3) publicclassExercise1UnitTestJUnit3extendsTestCase{ privateExercise1exercise1; protectedvoid setUp() { exercise1 = newExercise1(); } protectedvoid tearDown() { exercise1 = null; } publicvoid testMaxOfTwo() throws Exception { Assert.assertEquals(5, exercise1.maxOfTwo(4, 5).intValue()); } publicvoid testMaxOfArray() { Assert.assertEquals(15, exercise1.maxOfArray(newint[]{5, 6, 3, 15, 4, 9, 14}).intValue()); } publicvoid testFlip() { finalint[] source = newint[]{1, 2, 5, 4, 6}; finalint[] expectedAnswer = newint[]{6, 4, 5, 2, 1}; finalint[] actualAnswer = exercise1.flip(source); Assert.assertNotNull(actualAnswer); Assert.assertEquals(actualAnswer.length, source.length); for (int i = 0; i < actualAnswer.length; i++) { Assert.assertEquals(expectedAnswer[i], actualAnswer[i]); } } }
Exercise 1: correction (JUnit 4) publicclass Exercise1UnitTest { private Exercise1 exercise1; @org.junit.Before publicvoid methodBefore() { exercise1 = new Exercise1(); } @org.junit.After publicvoid methodAfter() { exercise1 = null; } @Test publicvoid maxOfTwo() throws Exception { Assert.assertEquals(5, exercise1.maxOfTwo(4, 5).intValue()); } @Test publicvoid maxOfArray() { Assert.assertEquals(15, exercise1.maxOfArray(newint[]{5, 6, 3, 15, 4, 9, 14}).intValue()); } @Test publicvoid flip() { finalint[] source = newint[]{1, 2, 5, 4, 6}; finalint[] expectedAnswer = newint[]{6, 4, 5, 2, 1}; finalint[] actualAnswer = exercise1.flip(source); Assert.assertNotNull(actualAnswer); Assert.assertEquals(actualAnswer.length, source.length); for (int i = 0; i < actualAnswer.length; i++) { Assert.assertEquals(expectedAnswer[i], actualAnswer[i]); } } }
Best Practices (1) • For each class X, create a class XUnitTest extends TestCase • X in src/ folder, XUnitTest in test/unit • Use any value, rather than basic values (0, 1) • 546541 • 95.1215 • “myVariableName” • Beware of: • Arrays and Collections asserts on elements
Exercise 2 Write following services: • Get the absolute value of a Double • Get f: x x² • Get g: (x,y) y(xy) • Reminder: • x < 0 |x| = -x • x >= 0 |x| = +x
Exercise 2: Correction (1) package lalou.jonathan.tdd; /** * User: JonathanLalou * Date: 9/22/11 * Time: 1:53 PM * $Id$ */ publicclass Exercise2 { public Double absoluteValue(Double x) { if (x > 0) return x; elsereturn -x; } public Double f(Double x){ return Math.sqrt(Math.pow(x, 2)); } public Double g (Double x, Double y){ return Math.pow(Math.pow(x, y), 1/y); } }
Exercise 2: Correction (2) publicclass Exercise2UnitTest { private Exercise2 exercise2; @Before publicvoid setUp(){ exercise2 = new Exercise2(); } @Test publicvoid testAbsoluteValue_positive() throws Exception { Assert.assertEquals(3.2, exercise2.absoluteValue(3.2)); } // the second test IS needed @Test publicvoid testAbsoluteValue_negative() throws Exception { Assert.assertEquals(4.5, exercise2.absoluteValue(-4.5)); } @Test publicvoid testF() throws Exception { finaldouble expected = 6.9999; Assert.assertEquals(expected, exercise2.f(expected)); } // … }
Exercise 2: Correction (3) @Test publicvoid testG() throws Exception { finaldouble expected = 6.9999999; final Double power = 99.99987654; Assert.assertEquals(expected, exercise2.g(expected, power)); } … junit.framework.AssertionFailedError: expected:<6.9999999> but was:<6.999999899999999> at lalou.jonathan.tdd.Exercise2UnitTest.testG(Exercise2UnitTest.java:41) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) (…) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) (…) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) Process finished with exit code -1
Exercise 2: Correction (4) @Test publicvoid testG() throws Exception { finaldouble expected = 6.9999999; final Double power = 99.99987654; final Double epsilon = 0.1; Assert.assertEquals( expected, exercise2.g(expected, power), epsilon); }
Best Practices (2) • Test every possible switch • Beware of: • Double, Float, etc. add an epsilon
Exercise 3 • A complex number: (x, y) / (ρ,θ) • Write the method that gives the square of the module • Write unit test
Exercise 3: Correction (1) 1 publicclass Complex { 2 privatefinal Double x, y, rho, theta; 3 4 public Complex(Double x, Double y, Double rho, Double theta) { 5 this.rho = rho; 6 this.theta = theta; 7 this.x = x; 8 this.y = y; 9 } 10 11 public Double getSquaredModule(){ 12 returnx*x + y*y; 13 } 14 }
Exercise 3: Correction (2) 1 publicvoid testGetSquaredModule_firstAttempt() { 2 final Double expected = 5.0, epsilon = 0.01; 3 final Complex complex = new Complex(1.0, 2.0, Math.sqrt(5), 1.10714872); 4 final Double actual = complex.getSquaredModule(); 5 assertEquals(expected, actual, epsilon); 6 } 7 8 publicvoid testGetSquaredModule_secondAttempt() { 9 final Double expected = 5.0, epsilon = 0.01; 10 final Complex complex = new Complex(0.0, 0.0, Math.sqrt(5), 1.10714872); 11 final Double actual = complex.getSquaredModule(); 12 assertNotSame(expected, actual); 13 }
Exercise 3: Correction (3) 1 publicvoid testGetSquaredModule_right() { 2 final Double expected = 5.0, epsilon = 0.01; • final Complex complex = new Complex(1.0, 2.0, 0.0, 0.0); 4 final Double actual = complex.getSquaredModule(); 5 assertEquals(expected, actual, epsilon); 6 }
Best Practices (3) • Setup the minimum of needed fields
Methods within a method (1) • Errors can offset each other • Idea: • test only one method at a time • override / mock other methods • check other methods were called with rights parameters • Not “pure-TDD” (tests before code) • Many different ways to test the same code
Methods within a method (2) 1 publicclass MethodWithinMethod { 2 3 public Integer sum(Integer... params) { 4 Integer answer = 0; 5 for (Integer param : params) { 6 answer += param; 7 } 8 return answer; 9 } 10 11 public Integer sumAndQuadruple(Integer... params) { 12 final Integer summed = sum(params); 13 return4 * summed; 14 } 15 }
Methods within a method (3) 1 @Test 2 publicvoid testSum() throws Exception { 3 assertEquals(15, methodWithinMethod.sum(1, 2, 3, 4, 5).intValue()); 4 } 5 6 @Test 7 publicvoid testSumAndQuadruple_bad() throws Exception { 8 assertEquals(60, methodWithinMethod.sumAndQuadruple(1, 2, 3, 4, 5).intValue()); 9 }
Methods within a method (4) 1 @Test 2 publicvoid testSumAndQuadruple_good() throws Exception { 3 final String OK = "OK"; 4 final Integer[] params = new Integer[]{1, 2, 3, 4, 5}; 5 final Integer summed = -1; 6 final StringBuffer sb = new StringBuffer(); 7 methodWithinMethod = new MethodWithinMethod(){ 8 public Integer sum(Integer... _params) { 9 assertEquals(_params, params); 10 sb.append(OK); 11 returnsummed; 12 } • }; • assertEquals(0, sb.toString().length()); • // before calling method • assertEquals("", sb.toString()); • assertEquals(-4, methodWithinMethod.sumAndQuadruple(params).intValue()); • assertEquals(OK, sb.toString()); • }
Exercise 5: method within method • Write a class with two methods • First: to log input/outputs • Second: logs and inverses a non null Integer (and returns 0 for 0) • (excercice#5 is before #4 ;-) )
Exercise 5 : correction (1) 1 publicclass Exercise5 { 2 publicvoid log(String message) { 3 System.out.println(message); 4 } 5 6 public Double inverse(Integer param) { 7 final Double answer; 8 log("called with parameter: " + param); 9 answer = param == 0 ? 0.0 : 1 / (double) param; 10 log("exit with result: " + answer); 11 return answer; 12 } 13 }
Exercise 5 : correction (2) publicvoid testInverse() { final List<String> witness = new ArrayList<String>(2); final Exercise5 exercise5 = new Exercise5() { publicvoid log(String message) { assertTrue(message.equalsIgnoreCase("called with parameter: 4") || message.equalsIgnoreCase("exit with result: 0.25")); witness.remove(message); } }; final Integer param = 4; final Double expectedAnswer = 0.25; final Double actualAnswer; witness.add("called with parameter: 4"); witness.add("exit with result: 0.25"); // check before run assertFalse(witness.isEmpty()); actualAnswer = exercise5.inverse(param); assertNotNull(actualAnswer); assertEquals(expectedAnswer, actualAnswer, 0.000001); // check after run assertTrue(witness.isEmpty()); }
Who we are Advanced
Layers • Testing layers should deserve a complete training session! • Unit test: test only one class behavior: • Service • DAO • Logic • EJB • Presentation • Form • Action • Architecture in layers: • Unit test strategy: • Unit test each layer • Mock other layers • Integration tests
Mocks • Use case / interest: • Non-deterministic results • Precalculate state • Fastness • Not yet implemented actual class • Mocks ~ AOP • Mocks Time consuming! Caution
Mocks / Stubs / Fakes • Mock • Stub • Provide minimal implementation • Fake • Emulate a service, eg: client/server
Mocks: frameworks in Java • Mockit • Mockito • EasyMock • JMock • PowerMock • …
Mocks: EasyMock (old): setup 1 private TreeNodeDetailVarDao treeNodeDetailVarDao; 3 private MockControl<TreeNodeDetailVarDao> treeNodeDetailVarDaoMockControl; 10 @Before() 11 publicvoid setUp() throws Exception { 12 treeNodeDetailVarDaoMockControl = MockControl.createStrictControl(TreeNodeDetailVarDao.class); 13 treeNodeDetailVarDao = treeNodeDetailVarDaoMockControl.getMock(); 14 }
Mocks: EasyMock (old): test treeNodeDetailVarDao.getTreeNodeDetailVarByTreeNodeId(treeNodeId); treeNodeDetailVarDaoMockControl.setReturnValue(12345); treeNodeDetailVarDaoMockControl.replay(); genericDtoService.getVarDistributionTOByTreeNodeId(treeNodeId, numberOfBars); treeNodeDetailVarDaoMockControl.verify();
Mocks: EasyMock (new) • “A la” Mockito 1 expect( 2 shiftListDao.findLastUpdateDateForShiftTypeAndValueDate(ShiftType.STRESS_VAR, valueDate)). 3 andReturn(updateDateInDB); 4 5 // shiftListDao.findLastUpdateDateForShiftTypeAndValueDate(ShiftType.STRESS_VAR, valueDate); 6 // shiftListDaoMockControl.setReturnValue(updateDateInDB);
Exercice 4 • Three DAOs • PeopleDAO • PortfolioDAO • InstrumentPriceDAO • One service that gets the value of someone’s portfolio • One service that gets the total value all portfolios