Mockobjects.Pdf • Steve Freeman
Total Page:16
File Type:pdf, Size:1020Kb
Software Testing Software Testing • What are Mock Objects • Case Study • Mock Objects pattern • Why use Mock Objects? Błaej Pietrzak [email protected] • Mock Objects Limitations • Mock Objects Frameworks Software Testing Software Testing „A Mock Object is a substitute • Mock Objects is a development technique that: – Lets you unit test classes that you didn’t think you could implementation to emulate or instrument – Helps you write better code whilst doing so other domain code. It should be simpler • Mock Objects is based on two key concepts: – Replace everything except the code under test with mock than the real code, not duplicate its implementations that emulate the rest of the environment implementation, and allow you to – Put the test assertions inside those mock implementations so that you can validate the interactions between the objects set up private state to aid in testing.” and your test case Endo-Testing: Unit Testing with Mock Objects • Mock Objects that become too complex to manage Tim Mackinnon, Steve Freeman, Philip Craig suggest that their domain clients are candidates for refactoring, and we avoid chaining Mock Objects. Software Testing Software Testing Testing a component that directs robots through a warehouse. Testing a component that directs robots through a warehouse. public class TestRobot { public class TestRobot { ... ... public void setUp() { Did thepu rbolbicotv omidovset Uinp (t)hi{s test? robot = new Robot(); Don’t knowro.b..o.t = new Robot(); } } public void testGotoSamePlace() { public void testGotoSamePlace() { final Position POSITION = new Position(1, 1); final Position POSITION = new Position(1, 1); robot.setCurrentPosition(POSITION); robot.setCurrentPosition(POSITION); robot.goTo(POSITION); robot.goTo(POSITION); assertEquals("Should be same", POSITION, assertEquals("Should be same", POSITION, robot.getPosition()); robot.getPosition()); } } } } 1 Software Testing Software Testing One option is to store and retrieve the route it took after each Next test is to move one point on the grid. goto public void testMoveOnePoint() { public void testGotoSamePlace() { final Position POSITION = new Position(1, 1); final Position DESTINATION = new Position(1, 0); robot.setCurrentPosition(POSITION); robot.setCurrentPosition(new Position(0, 0)); robot.goTo(POSITION); robot.goTo(DESTINATION); assertEquals("Should be empty", 0, assertEquals("Should be destination", DESTINATION, robot.getRecentMoveRequests().size()); robot.getPosition()); assertEquals("Should be same", POSITION, List moves = robot.getRecentMoveRequests(); robot.getPosition()); assertEquals("Should be one move", 1, moves.size()); } assertEquals("Should be same move", new MoveRequest(1, MoveRequest.SOUTH), moves.get(1)); } Software Testing Software Testing Tests become more complex... • Problems with this approach to testing: public void testMoveALongWay() { – If one of these tests fail, one will have to step final Position DESTINATION = new Position(34, 71); through the code to find the problem robot.setCurrentPosition(new Position(0, 0)); because the assertions have been made robot.goTo(DESTINATION); after the call has finished assertEquals("Should be destination", DESTINATION, robot.getPosition()); – Functionality had to be added to the getRecentMovesRequests assertEquals("Should be same moves", production class ( ) makeExpectedLongWayMoves(), robot.getRecentMoves()); } Software Testing Software Testing testGotoSamePlace() { Is there a better way? Can I find a style that will public void final Position POSITION = new Position(0, 0); give me better error reporting and put robot.setCurrentPosition(POSITION); the behaviour in the right place? Let’s refactor... Motor mockMotor = new Motor() { public interface Motor { public void move(MoveRequest request) { void move(MoveRequest request); fail(„Should be no moves"); } } public class OneSpeedMotor implements Motor { }; public void move(MoveRequest request) { robot.setMotor(mockMotor); turnBy(request.getTurn()); advance(request.getDistance()); robot.goTo(POSITION); } ... assertEquals("Should be same", POSITION, robot.getPosition()); } } 2 Software Testing Software Testing public class TestRobot { ... ... public void testMoveOnePoint() { final Position DESTINATION static final Position ORIGIN = new Position(1, 0); = new Position(0, 0); mockMotor.addExpectedRequest( public void setUp() { new MoveRequest(1, MoveRequest.SOUTH)); mockMotor = new MockMotor(); robot = new Robot(mockMotor); robot.goTo(DESTINATION); robot.setCurrentPosition(ORIGIN); assertEquals("Should be destination", DESTINATION, robot.getPosition()); } mockMotor.verify(); ... } Software Testing Software Testing ... public void testMoveAlongWay() { final Position DESTINATION 1. Create instances of Mock Objects = new Position(34, 71); 2. Set state in the Mock Objects mockMotor.addExpectedRequests( 3. Set expectations in the Mock Objects makeExpectedLongWayMoveRequests()); (addExpected... methods) robot.goTo(DESTINATION); 4. Invoke domain code with Mock Objects assertEquals("Should be destination", as parameters DESTINATION, robot.getPosition()); 5. Verify consistency in the Mock Objects mockMotor.verify(); } (verify method) } Software Testing Software Testing • Adapts to any kind of unit test(Servlet, JDBC etc.) • Localising unit tests – cont. • Localising unit tests – No stone unturned – Some unit tests need to test conditions that are very difficult to reproduce. – Deferring Infrastructure Choices „Not commit to infrastructure before you have it” public void testCloseStream() { eXtreme Programming mock.setExpectedCloseCalls(1); e.g. The real object does not yet exist myApp.setStream(mock); byte[] data = myApp.getData(); – Coping with scale // get data and close The real object is difficult to set up. The setup of mock.verify(); complex state is localised to one Mock Object instead } of scattered throughout many unit tests. – The real object is or has a user interface 3 Software Testing Software Testing • Better tests • Better tests – Effects of coding style – Failures fail fast – the real object is slow – Developing with Mock Objects reduces the Domain objects often fail some time after an error occurs, need to expose the structure of domain which is one reason that debugging can be so difficult. code. – Refactored assertions – A test knows more about the behavior and • Usually assertions about the domain code are refactored into shared methods, but the developer has to remember less about the structure of tested code. to apply them to new tests. – Unit testing in the presence of singletons • These assertions are built into Mock Objects and so are applied by default whenever the object is used. • The singletons might not have set up the state • New assertions are added once in the Mock Object methods or query methods the results afterwards where they will automatically apply to all previously • Developing with Mock Objects encourages a existing tests. coding style where objects are passed into the code that needs them. This makes substitution possible and reduces the risk of unexpected side- effects. Software Testing Software Testing • Better tests – Effects of coding style • Better tests – cont. – The code developed with MO tends to – Interface discovery conform to The Law of Demeter • When the tests all run, we can extract the following: • The unit tests push towards to writing domain code public interface PersonHandler { that refers only to local objects as parameters, void name(String name); without an explicit policy to do so. void age(int age); – Preserves encapsulation void telephone(String telephone); – Reduces global dependencies void writeTo(PrintWriter writer); – Clarify interactions between classes } •This approach ensures that the interface will be the minimum that the domain code needs, •following the Extreme Programming principle of not adding features beyond our current understanding. Software Testing Software Testing • Mock Object might contain errors e.g. returning • In some cases it can be hard to create values in degrees rather than radians. Mock Objects to represent types in a complex external library. • Libraries must define their APIs in terms of – The most difficult aspect is usually the discovery of interfaces rather than classes so that clients of values and structures for parameters that are passed the library can use such techniques. into the domain code – Vector class in version 1 of Java had many final – In an event-based system, the object that represents methods but no interface, making it impossible to an event might be the root of a graph of objects, all of substitute. which need mocking up for the domain code to work – This process can be costly and sometimes must be weighed against the benefit of having the unit tests 4 Software Testing Software Testing • Force the developer to refactor his/her code • Mock Libraries Note that if you need to implement tests for existing code it can easily become a nightmare ... • Static Java Mock Generators • Using MO, it is not sure the classes will run (tags in javadoc, command line params correctly in the chosen environment. etc.) • Using MO is not simple. It needs some discipline /** and some experience. * • In some cases MO mandates creating API that * Desription ... are not normally needed. * @mock • Also the code may become more complex */ (even if more flexible) because of the need for public class Person testing and not because of business requirements Software Testing Software Testing Interface to be mocked • Creating mocks on the fly