View
38
Download
3
Category
Preview:
DESCRIPTION
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: - PowerPoint PPT Presentation
Citation preview
Presenter Name
Presenter Title
Presentation
Date Jonathan LalouSoftware ArchitectAgile DevelopmentScrum Master
January 24th, 2012
Test Driven DevelopmentTesting Cooking Book
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
1. Test Driven Development1. Concepts
2. Interest
3. Among other Agile methods
2. Unit Test Cooking Book1. Basics
2. Methods within methods
3. Layers / Mocks
4. Exceptions
5. Private methods
3. Other Testsa. Integration tests
b. Runtime tests
c. Stress tests
4. Limitations and Vulnerabilities
Plan
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
MySelf!
Jonathan LALOU Mines Nancy 2000-2003 Cadextan / SunGard since 2005 BNP Arbitrage since 2007:
PW European PB
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Test Driven Development
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Concepts
Write test before implementation Lifecycle
1. Write a test2. Check it fails3. Write minimum code to make the test succeed4. Check it succeeds5. Refactor
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Interest
Improve specifications Provide validation for development Less debug Less regression Safe refactoring Automatization
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Unit Test Cooking Book
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Basics for Beginners
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Basic (1)
Example: “write a service that sums two integers”
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Basic (2)
Mode “I’ve just got out of school”:1 public class Summer {
2 public Integer sum(Integer a, Integer b){
3 return a+b;
4 }
5
6 public static void 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 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Basic (3)
Mode “TDD”
1/ Create empty class Summer:1 public class Summer {
2 public Integer sum(Integer a, Integer b){
3 return null;
4 }
2/ Create unit test:
1 public class SummerUnitTest extends TestCase {
2 public void testSum() throws Exception {
3 assertEquals(17, new Summer().sum(15, 2));
4 }
5 }
3/ Complete class Summer:1 public class Summer {
2 public Integer sum(Integer a, Integer b){
3 return a+b;
4 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Basic (5): Expectations
Expectations: Success Failure Error
Asserts: assertEquals assertSame assertTrue / assertFalse assertNull / assertNotNull fail
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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)
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 1: correction
public class 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; }
public int[] flip(int[] source) { final int[] answer = new int[source.length]; for (int i = 0; i < source.length; i++) { answer[i] = source[source.length - i - 1]; } return answer; }}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 1: correction (JUnit 3)
public class Exercise1UnitTestJUnit3 extends TestCase{private Exercise1 exercise1;
protected void setUp() { exercise1 = new Exercise1(); }
protected void tearDown() { exercise1 = null; }
public void testMaxOfTwo() throws Exception { Assert.assertEquals(5, exercise1.maxOfTwo(4, 5).intValue()); }
public void testMaxOfArray() { Assert.assertEquals(15, exercise1.maxOfArray(new int[]{5, 6, 3, 15, 4, 9,
14}).intValue()); }
public void testFlip() { final int[] source = new int[]{1, 2, 5, 4, 6}; final int[] expectedAnswer = new int[]{6, 4, 5, 2, 1}; final int[] 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]); } }
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 1: correction (JUnit 4)
public class Exercise1UnitTest {
private Exercise1 exercise1;
@org.junit.Before
public void methodBefore() {
exercise1 = new Exercise1();
}
@org.junit.After
public void methodAfter() {
exercise1 = null;
}
@Test
public void maxOfTwo() throws Exception {
Assert.assertEquals(5, exercise1.maxOfTwo(4, 5).intValue());
}
@Test
public void maxOfArray() {
Assert.assertEquals(15, exercise1.maxOfArray(new int[]{5, 6, 3, 15, 4, 9, 14}).intValue());
}
@Test
public void flip() {
final int[] source = new int[]{1, 2, 5, 4, 6};
final int[] expectedAnswer = new int[]{6, 4, 5, 2, 1};
final int[] 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]);
}
}
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 2: Correction (1)
package lalou.jonathan.tdd;
/** * User: Jonathan Lalou * Date: 9/22/11 * Time: 1:53 PM * $Id$ */public class Exercise2 { public Double absoluteValue(Double x) { if (x > 0) return x; else return -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); }
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 2: Correction (2)
public class Exercise2UnitTest {private Exercise2 exercise2;
@Before public void setUp(){ exercise2 = new Exercise2(); }
@Test public void testAbsoluteValue_positive() throws Exception { Assert.assertEquals(3.2, exercise2.absoluteValue(3.2)); }
// the second test IS needed @Test public void testAbsoluteValue_negative() throws Exception { Assert.assertEquals(4.5, exercise2.absoluteValue(-4.5)); }
@Test public void testF() throws Exception { final double expected = 6.9999; Assert.assertEquals(expected, exercise2.f(expected)); }// …}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 2: Correction (3)
@Test public void testG() throws Exception { final double 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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 2: Correction (4)
@Test
public void testG() throws Exception {
final double expected = 6.9999999;
final Double power = 99.99987654;
final Double epsilon = 0.1;
Assert.assertEquals(
expected,
exercise2.g(expected, power),
epsilon);
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Best Practices (2)
Test every possible switch Beware of:
Double, Float, etc. add an epsilon
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 3
A complex number: (x, y) / (ρ,θ) Write the method that gives the square of the module Write unit test
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 3: Correction (1)
1 public class Complex {
2 private final 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 return x*x + y*y;
13 }
14 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 3: Correction (2)
1 public void 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 public void 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 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 3: Correction (3)
1 public void testGetSquaredModule_right() {
2 final Double expected = 5.0, epsilon = 0.01;
3 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 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Best Practices (3)
Setup the minimum of needed fields
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Methods within a method (2)
1 public class 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 return 4 * summed;
14 }
15 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Methods within a method (3)
1 @Test
2 public void testSum() throws Exception {
3 assertEquals(15, methodWithinMethod.sum(1, 2, 3, 4, 5).intValue());
4 }
5
6 @Test
7 public void testSumAndQuadruple_bad() throws Exception {
8 assertEquals(60, methodWithinMethod.sumAndQuadruple(1, 2, 3, 4, 5).intValue());
9 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Methods within a method (4)
1 @Test 2 public void 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 return summed; 12 } 13 };
14 assertEquals(0, sb.toString().length());15 // before calling method16 assertEquals("", sb.toString());17 assertEquals(-4, methodWithinMethod.sumAndQuadruple(params).intValue()); 18 assertEquals(OK, sb.toString());
19 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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 ;-) )
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 5 : correction (1)
1 public class Exercise5 {
2 public void 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 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 5 : correction (2)
public void testInverse() {final List<String> witness = new ArrayList<String>(2);final Exercise5 exercise5 = new Exercise5() {public void 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 runassertFalse(witness.isEmpty());actualAnswer = exercise5.inverse(param);assertNotNull(actualAnswer);assertEquals(expectedAnswer, actualAnswer, 0.000001);// check after runassertTrue(witness.isEmpty());
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Advanced
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks
Use case / interest: Non-deterministic results Precalculate state Fastness Not yet implemented actual class
Mocks ~ AOP
Mocks Time consuming!
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks / Stubs / Fakes
Mock Stub
Provide minimal implementation
Fake Emulate a service, eg: client/server
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks: frameworks in Java
Mockit Mockito EasyMock JMock PowerMock …
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks: EasyMock (old): setup
1 private TreeNodeDetailVarDao treeNodeDetailVarDao;
3 private MockControl<TreeNodeDetailVarDao> treeNodeDetailVarDaoMockControl;
10 @Before()
11 public void setUp() throws Exception {
12 treeNodeDetailVarDaoMockControl = MockControl.createStrictControl(TreeNodeDetailVarDao.class);
13 treeNodeDetailVarDao = treeNodeDetailVarDaoMockControl.getMock(); 14 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks: EasyMock (old): test
treeNodeDetailVarDao.getTreeNodeDetailVarByTreeNodeId(treeNodeId);
treeNodeDetailVarDaoMockControl.setReturnValue(12345);
treeNodeDetailVarDaoMockControl.replay();
genericDtoService.getVarDistributionTOByTreeNodeId(treeNodeId, numberOfBars);
treeNodeDetailVarDaoMockControl.verify();
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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);
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
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
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (1) Entities
public class People {
public Integer id;
public String name;
}
public class Portfolio {
public Integer id;
public Integer peopleId; // foreign key on People
public List<Integer> instrumentIds;
}
public class Instrument {
public Integer id;
public Double price;
public String name;
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (2) Interfaces
public interface PortfolioDAO {
Portfolio findByPeopleId(Integer peopleId) throws IllegalStateException;
// should throw "PortfolioNotFoundException" IRL
}
public interface PeopleDAO {
List<People> findAll();
void helloWorld();
}
public interface InstrumentDAO {
Instrument findById(Integer pk) throws IllegalStateException;
// should throw "InstrumentNotFoundException" IRL
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (3) Code
public class Exercise4 { public PeopleDAO peopleDAO; public InstrumentDAO instrumentDAO; public PortfolioDAO portfolioDAO;
public Exercise4(InstrumentDAO instrumentDAO, PeopleDAO peopleDAO, PortfolioDAO portfolioDAO) {
this.instrumentDAO = instrumentDAO; this.peopleDAO = peopleDAO; this.portfolioDAO = portfolioDAO; }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (4) Methods
1 public Double getPortfolioValue(Integer peopleId){ 2 final Portfolio portfolio; 3 Double answer = 0.0; 4 portfolio = portfolioDAO.findByPeopleId(peopleId); 5 for (Integer instrumentId : portfolio.instrumentIds) { 6 final Instrument instrument; 7 instrument = instrumentDAO.findById(instrumentId); 8 answer += instrument.price; 9 } 10 return answer; 11 } 12 13 public Double getAllPortfolioValue(){ 14 final List<People> allPeople; 15 Double answer = 0.0; 16 17 allPeople = peopleDAO.findAll(); 18 for (People people : allPeople) { 19 answer += getPortfolioValue(people.id); 20 } 21 peopleDAO.helloWorld(); 22 23 return answer; 24 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (5) Unit Tests Setup
public class Exercise4UnitTest extends TestCase { final double epsilon = 0.0001;
private PeopleDAO peopleDAO; private InstrumentDAO instrumentDAO; private PortfolioDAO portfolioDAO;
private MockControl<PeopleDAO> peopleDAOControl; private MockControl<InstrumentDAO> instrumentDAOControl; private MockControl<PortfolioDAO> portfolioDAOControl;
private Exercise4 exercise4;
@Before public void setUp() throws Exception { peopleDAOControl = MockControl.createStrictControl(PeopleDAO.class); instrumentDAOControl = MockControl.createStrictControl(InstrumentDAO.class); portfolioDAOControl = MockControl.createStrictControl(PortfolioDAO.class);
peopleDAO = peopleDAOControl.getMock(); instrumentDAO = instrumentDAOControl.getMock(); portfolioDAO = portfolioDAOControl.getMock();
exercise4 = new Exercise4(instrumentDAO, peopleDAO, portfolioDAO); }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (6) Mocks Setup
private void mockReplay() {
peopleDAOControl.replay();
instrumentDAOControl.replay();
portfolioDAOControl.replay();
}
private void mockVerify() {
peopleDAOControl.verify();
instrumentDAOControl.verify();
portfolioDAOControl.verify();
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (7) Unit test#1
@Test public void testGetPortfolioValue() throws Exception { final Integer peopleId = 5610; final Portfolio portfolio = new Portfolio(); final Instrument instrument1 = new Instrument(); final Instrument instrument2 = new Instrument(); final int instrumentId1 = 147; final int instrumentId2 = 369; final List<Integer> instrumentIds = Arrays.asList(instrumentId1, instrumentId2); final Double expectedAnswer = 57.9; final Double actualAnswer;
instrument1.id = instrumentId1; instrument2.id = instrumentId2; instrument1.price = 12.3; instrument2.price = 45.6;
portfolio.instrumentIds = instrumentIds;
portfolioDAO.findByPeopleId(peopleId); portfolioDAOControl.setReturnValue(portfolio);
instrumentDAO.findById(instrumentId1); instrumentDAOControl.setReturnValue(instrument1);
instrumentDAO.findById(instrumentId2); instrumentDAOControl.setReturnValue(instrument2);
mockReplay(); actualAnswer = exercise4.getPortfolioValue(peopleId); mockVerify();
assertEquals(expectedAnswer, actualAnswer, epsilon); }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 4: correction (8) Unit test#2
@Test public void testGetAllPortfolioValue() throws Exception { final Integer id1 = 654, id2 = 987; final People someone = new People(); final People someoneElse = new People(); final List<People> allPeople; final Double expectedAnswer = 2.02; final Double actualAnswer;
someone.id = id1; someoneElse.id = id2;
allPeople = Arrays.asList(someone, someoneElse);
exercise4 = new Exercise4(instrumentDAO, peopleDAO, portfolioDAO) { public Double getPortfolioValue(Integer peopleId) { assertTrue((id1 == peopleId) || (id2 == peopleId)); // not a relevant test!! return 1.01; } };
peopleDAO.findAll(); peopleDAOControl.setReturnValue(allPeople);
peopleDAO.helloWorld(); peopleDAOControl.setVoidCallable();
mockReplay(); actualAnswer = exercise4.getAllPortfolioValue(); mockVerify(); assertEquals(expectedAnswer, actualAnswer, epsilon); }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Mocks: advanced
Controls: Nice controls Controls Strict controls
Matchers Number of calls Ignore fields
(eg: timestamps for / from a DB)
Return value and/or throw Exception!
Mock interfaces and even concrete classes
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Private Methods
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Private methods
2 strategies: (almost) All methods are at least protected Use Java reflexivity APIs, eg:
1 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
2 final Field privateThreadsField;
3 privateThreadsField = ThreadGroup.class.getDeclaredField( “threads”);
5 privateThreadsField.setAccessible(true);
6 threads = (Thread[]) privateThreadsField.get(threadGroup);
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 6: Private Methods
Test this class:
1 public class Exercise6 {
2
3 private Integer increment(Integer i){
4 return i++;
5 }
6
7 private Integer decrement(Integer i){
8 return i--;
9 }
10 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 6: Correction (1)
1 public class Exercise6UnitTest {
2 private Exercise6 exercise6;
3 private Method methodIncrement, methodDecrement;
4
5 @Before
6 public void setUp() throws Exception {
7 exercise6 = new Exercise6();
8
9 methodIncrement = Exercise6.class.getDeclaredMethod("increment", Integer.class);
10 methodIncrement.setAccessible(true);
11
12 methodDecrement = Exercise6.class.getDeclaredMethod("decrement", Integer.class);
13 methodDecrement.setAccessible(true);
14 } …15 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercice 6: Correction (2)
1 @Test
2 public void testIncrement() throws InvocationTargetException, IllegalAccessException {
3 final Integer param = 9;
4 final Integer expected = 10;
5 final Integer actual = (Integer) methodIncrement.invoke(exercise6, param);
6
7 assertEquals(expected, actual);
8 }
9
10 @Test
11 public void testDecrement() throws InvocationTargetException, IllegalAccessException {
12 final Integer param = 9;
13 final Integer expected = 8;
14 final Integer actual = (Integer) methodDecrement.invoke(exercise6, param);
15
16 assertEquals(expected, actual);
17 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
java.lang.Exceptions
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
java.lang.Exceptions (1)
Why to test exception? Exceptions are a kind of returned values Testing “KO cases” as well as “OK cases”
JUnit 3 Try/catch
JUnit4 @Test(expected=“…Exception”)
You can’t use both in the same JUnit Test Case!
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exceptions (2)
1 public void testArrayIndexOutOfBoundsException() {
2 final Integer[] array = new Integer[]{1, 2, 3};
3 try {
4 final Integer foo = array[18];
5 fail();
6 } catch (ArrayIndexOutOfBoundsException aioobe) {
7 // test OK
8 }
9 }
11 @Test(expected = ArrayIndexOutOfBoundsException.class)
12 public void testArrayIndexOutOfBoundsException_junit4() {
13 final Integer[] array = new Integer[]{1, 2, 3};
14 final Integer foo = array[18];
15 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 7: Exceptions
Write a parser of integers and its unit test
1 public Integer parseString(String szInt)
throws ParseException {
2 return Integer.parseInt(szInt);
3 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 7: Correction
1 private Exercise7 exercise7;
3 @Before
4 public void before(){
5 exercise7 = new Exercise7();
6 }
7
8 @Test
9 public void testParseString_OK() throws ParseException {
10 final String szInt = "123456";
11 final Integer expected = 123456;
12 Assert.assertEquals(expected, exercise7.parseString(szInt));
13 }
14
15 // @Test(expected = Exception.class)
16 @Test(expected = NumberFormatException.class)
17 public void testParseString_KO() throws ParseException {
18 final String szInt = "thisIsNotAnInteger";
19 exercise7.parseString(szInt);}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Loggers
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Loggers (1)
Eg:
1 public class ClassWithLogger {
2 private static final Logger LOGGER = Logger.getLogger(ClassWithLogger.class);
3
4 public void printMessage(Integer foo){
5 LOGGER.warn("this is the message#" + foo);
6 }
7 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Loggers (2)
Idea: play on appenders Consider a List of Strings as an appender Discard other appenders
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Loggers (3)
Eg:1 public class TestAimedAppender extends ArrayList<String> implements Appender { 2 private final Class clazz; 3 4 public TestAimedAppender(Class clazz) { 5 super(); 6 this.clazz = clazz; 7 }
public void close() {} 24
25 @Override 26 public void doAppend(LoggingEvent event) { 27 add(event.getRenderedMessage()); 28 } 29 30 @Override 31 public String getName() { 32 return "TestAppender for " + clazz.getSimpleName(); 33 } public boolean requiresLayout() { 58 return false; 59 } ...
60 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 8: Loggers
Test the following method, discriminating on log level:1 public class Exercise8 {
2 private static final Logger LOGGER = Logger.getLogger(Exercise8.class);
3
4 public void printAnotherMessage(Integer foo) {
5 if (0 == foo % 2) {
6 LOGGER.info("I am an even number: " + foo);
7 } else {
8 LOGGER.warn("I am an odd number: " + foo);
9 }
10 if (0 == foo % 3) {
11 LOGGER.error(foo + " is dividable by 3");
12 }
13 }
14 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 8: correction
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
new Date()
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercice 9:
Unit test this method that raises an exception if the Date given as parameter is anterior to current day
(mind the day, don’t mind hour/minutes/seconds)
public void checkDate(Date startValueDate) throws IllegalArgumentException {
if (startValueDate.before(new Date())) {
throw new IllegalArgumentException();
}
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 9: correction (1)
public class Exercise9 {
public void checkDate(Date startValueDate) throws IllegalArgumentException{if (startValueDate.before(today())) {
throw new IllegalArgumentException();}
}
protected Date today() {return new Date();
}}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 9: correction (2)
public class Exercise9UnitTest extends TestCase {
private Exercise9 exercise9;private SimpleDateFormat dateFormat = new
SimpleDateFormat("yyyyMMdd");private Date today;private Date yesterday;private Date tomorrow;
protected void setUp() throws Exception {today = dateFormat.parse("20120124");yesterday = dateFormat.parse("20120123");tomorrow = dateFormat.parse("20120125");exercise9 = new Exercise9() {
@Overrideprotected Date today() {
return today;}
};}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Exercise 9: correction (3)
@Testpublic void testCheckDate_OK() {
try {exercise9.checkDate(tomorrow);
} catch (IllegalArgumentException e) {fail();
}}
@Testpublic void testCheckDate_KO() {
try {exercise9.checkDate(yesterday);fail();
} catch (IllegalArgumentException e) {// test OK}
}
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Other Tests
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Integration Tests
Test part or all of a complex chain Test in real life situations, not theory behaviors
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Runtime Tests
Concepts Examples
DB with Hibernate EJB
(loggers admited in runtime tests)
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Runtime Test: JDBC query onto Oracle (1)
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns=“…”>
7 <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
8 <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
9 <property name="url" value="jdbc:oracle:thin:@host:1234:SCHEMA"/>
10 <property name="username" value=“myUserName"/>
11 <property name="password" value=“myPassword"/>
12 <property name="initialSize" value="2"/>
13 <property name="minIdle" value="2"/>
14 </bean>
15
16 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
17 <property name="dataSource" ref="dataSource"/>
18 </bean> 20 </beans>
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Runtime Test: JDBC query onto Oracle (2)
public class FloatArrayResultRuntimeTest extends TestCase { 23 private static final Logger LOGGER = Logger.getLogger(FloatArrayResultRuntimeTest.class); private static final String TABLE = "RL_POSITIONRESULTSTRESS"; 30 private static final String PK_FIELD = "POSITIONRESULTSTRESSID"; private static final String BLOB_FIELD =
"stress"; private static final int[] PK_VALUES = {3242, 3247, 3285, 3284}; 33 34 private ApplicationContext applicationContext; 35 private JdbcTemplate jdbcTemplate; 36 37 @Before 38 public void setUp() throws Exception { 39 applicationContext = new ClassPathXmlApplicationContext( 40 "com/bnpp/pb/risklayer/model/domain/FloatArrayResultRuntimeTest-applicationContext.xml"); 41 assertNotNull(applicationContext); 42 jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); 43 assertNotNull(jdbcTemplate); 44 } 50 @Test 51 public void testGetArray() throws Exception { 52 for (int pk_value : PK_VALUES) { 53 final Blob stressBlob; 54 final byte[] stressBytes; 55 final double[] doubles; 57 stressBlob = (Blob) jdbcTemplate.queryForObject("select " + BLOB_FIELD + " from " + TABLE + " where " +
PK_FIELD + " = " + pk_value, Blob.class); 58 assertNotNull(stressBlob); 59 stressBytes = stressBlob.getBytes(1, (int) stressBlob.length()); 60 doubles = NumericArrayHelper.unzipFloatArrayAsDoubleArray(stressBytes); 61 62 LOGGER.info("Blob size: " + doubles.length); 63 LOGGER.info(ToStringBuilder.reflectionToString(doubles)); 64 } 65 }
66 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Runtime Test: call a remote EJB (1): Spring
1 <bean id="loaderSessionRemote"
2 class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
3 <property name="jndiName" value="ejb.backend.generic-data-loader-session-bean"/>
4 <property name="businessInterface" value="com.bnpparibas.primeweb.backend.services.GenericDataLoaderSession"/>
5 <property name="jndiEnvironment">
6 <props>
7 <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
8 <prop key="java.naming.provider.url">t3://localhost:1234</prop>
9 <prop key="java.naming.security.principal">myLogin</prop>
10 <prop key="java.naming.security.credentials">myPassword</prop>
11 <prop key="weblogic.jndi.enableDefaultUser">true</prop>
12 </props>
13 </property>
14 </bean>
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Runtime Test: call a remote EJB (2): Java
1 @Override
2 protected void setUp() throws Exception {
3 BeanFactory factory = new ClassPathXmlApplicationContext(
"applicationContext.xml",
this.getClass());
4 loaderSession = (GenericDataLoaderSession) factory.getBean("loaderSessionRemote");
5 super.setUp();
6 }
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Stress Tests
Simulate behavior face to massive stimulation Validate scalability Identify bottle necks
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Limitations and vulnerabilities
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Limitations
Perfect for: service, DAO, technical, etc. Less suitable for
GUI Swing Web (Struts, GWT, etc.) Multi threads
Expensive Initial cost Maintenance cost
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Conclusion
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Who we are
Questions
www.sungard.com/consultingProprietary and Confidential. Not to be distributed or reproduced without permission
Links and contacts
Google! www.junit.org/ Oracle, Sun, Java sites Forums, mailing lists Trainer:
Twitter: @John_the_Cowboy Google+ : +Jonathan.Lalou Jonathan.Lalou@sungard.com http://jonathan.lalou.free.fr
Recommended