EECS 394 Software Project Management Chris Riesbeck Testing: BDD and Mock Objects Thursday, May 12, 2011
Behavior Driven Development � Dan North evolved BDD from unit style TDD � http://dannorth.net/introducing-bdd/ � Test method names better as "should" sentences � testShouldFailForMissingSurname � Really about defining desired behaviors � self-testing is a secondary aspect � Behaviors derive ultimately from user stories: � As a [user] I want [feature] so that [benefit] � User story acceptance tests (scenarios): � Given some context, When some event, then some outcomes should be true. 2 Thursday, May 12, 2011
In unit testing framework public class WindowControlBehavior { @Test public void shouldCloseWindows() { // Given WindowControl control = new WindowControl("My AFrame"); AFrame frame = new AFrame(); // When control.closeWindow(); // Then assert(!frame.isShowing()); } } 3 Thursday, May 12, 2011
Tools for BDD � North created JBehave (Java) then RBehave (Ruby) � RSpec (BDD for Ruby) incorporated RBehave � http://www.infoq.com/news/2007/10/RSpec- incorporates-RBehave � Then Cucumber replaced RBehave � http://upstre.am/blog/2008/08/cucumber-next- generation-rspec-story-runner/ � https://github.com/aslakhellesoy/cucumber/wiki/ � note the return of the "5 whys" halfway down � Main goal: make user acceptance tests readable, less obscured by code details 4 Thursday, May 12, 2011
Cucumber � A text-based story runner � written in Ruby for RSpec � now supports Java, .Net, Flex and webapps � Write acceptance tests in plain English � Regular expression matching links text to code � http://cukes.info/ � http://railscasts.com/episodes/155-beginning-with- cucumber (15 minutes) � http://teachmetocode.com/screencasts/introduction-to- outside-in-development-with-cucumber/ (25 minutes) 5 Thursday, May 12, 2011
Fitnesse � Tests as decision tables in web pages � preceded Cucumber � tests that client can read � all-in-one server with wiki-based editor � originally for Java, now many languages � http://fitnesse.org/ 6 Thursday, May 12, 2011
easyb � Story verification framework for Java using the Groovy scripting language � http://www.easyb.org/ 7 Thursday, May 12, 2011
Levels of testing � Unit tests � test individual modules (methods, classes) � should be numerous, fast, detailed, automated � System integration tests � test collections of communicating modules � should include all major communication paths � typically fewer and slower than unit tests � integration failures should generate new unit tests � User acceptance tests � integration tests specified and run by client � define essential functionality for entire system 8 Thursday, May 12, 2011
Discussions about acceptance tests � http://blog.8thlight.com/articles/2011/4/26/10- ways-to-do-acceptance-testing-wrong � http://jamesshore.com/Blog/The-Problems-With- Acceptance-Testing.html � http://jamesshore.com/Blog/Alternatives-to- Acceptance-Testing.html 9 Thursday, May 12, 2011
Unit Test Problem � Unit tests � test individual modules (methods, classes) � should be numerous, fast, detailed, automated � should not cross module boundaries � should test the unit, not other classes � How can you unit test code � that calls other code � that may be very slow, e.g., database connections � that may not exist yet 10 Thursday, May 12, 2011
Unit testing we want to test Order public class OrderStateTester extends TestCase { private static String TALISKER = "Talisker"; private Warehouse warehouse = new WarehouseImpl(); but we have to create a protected void setUp() throws Exception { Warehouse object warehouse.add(TALISKER, 50); } public void testOrderIsFilledIfEnoughInWarehouse() { Order order = new Order(TALISKER, 50); modify it order.fill(warehouse); assertTrue(order.isFilled()); assertEquals(0, warehouse.getInventory(TALISKER)); } and test it public void testOrderNotFilledIfNotEnoughInWarehouse() { Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled()); assertEquals(50, warehouse.getInventory(TALISKER)); } } http://martinfowler.com/articles/mocksArentStubs.html 11 Thursday, May 12, 2011
Solution: Mock objects � A mock object is � a stub that imitates an object needed by the code being tested � a "tape recorder" that can verify that the mock object was used as intended � Must have an interface for the object to be mocked � Implementing mock objects by hand can be tedious for classes with many methods � Mock libraries provide tools for making mocks in just a few steps 12 Thursday, May 12, 2011
jMock 1: using Mock class public class OrderTester extends TestCase { private Warehouse warehouse = new WarehouseImpl(); ... public void testOrderIsFilledIfEnoughInWarehouse() { Order order = new Order(TALISKER, 50); order.fill(warehouse); assertTrue(order.isFilled()); assertEquals(0, warehouse.getInventory(TALISKER)); } public class OrderTester extends MockObjectTestCase { ... create a mock Warehouse public void testOrderIsFilledIfEnoughInWarehouse() { Order order = new Order(TALISKER, 50); Mock warehouseMock = new Mock(Warehouse.class); ... pass the mock to Order order.fill((Warehouse) warehouseMock.proxy()); assertTrue(order.isFilled()); warehouseMock.verify(); verify the mock's } expectations http://martinfowler.com/articles/mocksArentStubs.html 13 Thursday, May 12, 2011
jMock 1: using mock() method public class OrderTester extends TestCase { private Warehouse warehouse = new WarehouseImpl(); ... public void testOrderNotFilledIfNotEnoughInWarehouse() { Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled()); assertEquals(50, warehouse.getInventory(TALISKER)); } defines mock() method public class OrderTester extends MockObjectTestCase { ... call mock() to make public void testOrderNotFilledIfNotEnoughInWarehouse() { Order order = new Order(TALISKER, 51); mocked object Mock warehouse = mock(Warehouse.class); ... order.fill((Warehouse) warehouse.proxy()); assertFalse(order.isFilled()); mocked() objects are } verified automatically when test finishes http://martinfowler.com/articles/mocksArentStubs.html 14 Thursday, May 12, 2011
jMock 1: setting expectations public void testOrderIsFilledIfEnoughInWarehouse() { expectations define the Order order = new Order(TALISKER, 50); expected usage of mock Mock warehouseMock = new Mock(Warehouse.class); object in a specific test warehouseMock.expects(once()).method("hasInventory") .with(eq(TALISKER),eq(50)) .will(returnValue(true)); hasInventory(TALISKER, 50) warehouseMock.expects(once()).method("remove") should be called once, and .with(eq(TALISKER), eq(50)) will return true .after("hasInventory"); order.fill((Warehouse) warehouseMock.proxy()); remove(TALISKER, 50) warehouseMock.verify(); should be called once, after assertTrue(order.isFilled()); hasInventory() call } http://martinfowler.com/articles/mocksArentStubs.html 15 Thursday, May 12, 2011
EasyMock 1: setting expectations public class OrderEasyTester extends TestCase { normal JUnit TestCase ... private MockControl warehouseControl; private Warehouse warehouseMock; public void setUp() { warehouseControl = MockControl.createControl(Warehouse.class); warehouseMock = (Warehouse) warehouseControl.getMock(); } public void testOrderIsFilledIfEnoughInWarehouse() { Order order = new Order(TALISKER, 50); record and replay approach warehouseMock.hasInventory(TALISKER, 50); to setting expectations warehouseControl.setReturnValue(true); warehouseMock.remove(TALISKER, 50); warehouseControl.replay(); compiled method calls order.fill(warehouseMock); warehouseControl.verify(); assertTrue(order.isFilled()); } http://martinfowler.com/articles/mocksArentStubs.html 16 Thursday, May 12, 2011
jMock 2: generic API public class OrderTester extends MockObjectTestCase { ... with Java generics, public void testOrderIsFilledIfEnoughInWarehouse() { no Mock class, no final Order order = new Order(TALISKER, 50); typecasting final Warehouse warehouseMock = mock(Warehouse.class); checking(new Expectations() {{ Java Double-Brace final Sequence ordering = sequence("ordering"); initializer block oneOf (warehouseMock).hasInventory(TALISKER, 50); inSequence(ordering); oneOf (wareHouseMock).remove(TALISKER, 50); expectations stored inSequence(ordering); in separate }} Expectations object order.fill(warehouseMock); assertTrue(order.isFilled()); } sequences are optional and separate objects EasyMock 3.0 also has a generic API 17 Thursday, May 12, 2011
Mock libraries � Ruby - list of options � http://www.ruby-toolbox.com/categories/mocking.html � PHP � SimpleTest includes a mocking API: � http://www.lastcraft.com/mock_objects_documentation.php � Mockery, usable with PHPUnit � http://blog.astrumfutura.com/2010/05/mockery-from-mock- objects-to-test-spies/ � Python � Mocker -- uses record/replay approach � http://labix.org/mocker � Fudge - modeled on jMock � http://farmdev.com/projects/fudge/ 18 Thursday, May 12, 2011
Recommend
More recommend