960 likes | 1.18k Views
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:
E N D
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