SlideShare a Scribd company logo
1 of 21
Download to read offline
Sustainable
                   Test-Driven
                  Development
                   Steve Freeman and Nat Pryce




                                   www.growing-object-oriented-software.com 2011




                           Out Now




Friday, 9 September 2011
Why Sustainability
                                                                          Matters (a)
        public class ExchangeRateUploaderTest extends EasyMockTestCase {
           private Logger logger;
           private CurrencyManager mockCurrencyManager;
           private ExchangeRateManager mockExchangeRateManager;
           private PriceManagerFactory mockPriceManagerFactory;
           private PriceManager mockPriceManager;
           private GodObject mockGod;
           private DatabaseFacade mockPersistenceManager;
           private DatabaseFacade mockFrameworkPersistenceManager;
           private CyclicProcessManager mockCyclicProcessManager;
           private SystemVariableManager mockSystemVariableManager;
           private ScreenManager mockScreenManager;
           private Registry registry;
           private User adminUser;
           private Server server;

           private ExchangeRateUploader newExchangeRateUploader(CyclicProcessThread thread) {
               return new ExchangeRateUploader(thread) {
                    @Override protected void initializeAction() throws FrameworkException {
                        // Does nothing to prevent excessive mocking
                     }
                     @Override    public Logger getLogger() { return logger; }
                     @Override    protected void setLogMDC() { }
                     @Override    protected User getUser() { return adminUser; }
                     @Override    protected CurrencyManager newCurrencyManager() { return mockCurrencyManager; }
                     @Override    protected PriceManagerFactory newPriceManagerFactory() { return mockPriceManagerFactory; }
                     @Override    protected CyclicProcessManager newCyclicProcessManager() { return mockCyclicProcessManager; }
                     @Override    protected DatabaseFacade newPersistenceManager() { return mockPersistenceManager; }
                     @Override    protected Registry newTaskPerformanceRegistry() { return registry; }
                     @Override    public PriceDataManager getPriceDataManager() { return null; }
                     @Override    protected ExchangeRateManager newExchangeRateManager() { return mockExchangeRateManager; }
               };

           }




                                                                                                      www.growing-object-oriented-software.com 2011




                                                                      Why Sustainability
           public void testInternalAction() throws FrameworkException {
               mockGod = addMock(GodObject.class);
               expect(mockGod.getPriceDataManager()).andStubReturn(null);
               mockPersistenceManager = addMock(DatabaseFacade.class);
               registry = addMock(Registry.class);
                                                                            Matters (b)
               adminUser = new TestUser("Admin", "", "", new TestCompany("company"), "");
               mockCyclicProcessManager();

               registry.finalizeThisThread(isA(String.class), isA(String.class));
               Date now = DateUtils.trimMinutesAndSecondsFromDate(new Date());

               mockSystemVariableManager();
               mockLogger();
               mockContextPersistenceManager();
               mockPriceManager();

               CyclicProcessThread thread = mockUserStateAndGetCyclicProcessThread();

               String primeName = "prime";
               String aName = "a";
               String otherName = "other";

               Currency primeCurrency = new TestCurrency(primeName, true);
               Currency aCurrency = new TestCurrency(aName, true);
               Currency otherCurrency = new TestCurrency(otherName, false);

               FXCurrencyPair aCurrencyPair = new FXCurrencyPair(primeCurrency, aCurrency);
               FXCurrencyPair otherCurrencyPair = new FXCurrencyPair(otherCurrency, primeCurrency);

               setupCurrencyManager(primeCurrency, aCurrency, otherCurrency);
               mockExchangeRateManager = addMock(ExchangeRateManager.class);

               mockGetFXRatesAtDatesForCurrencies(now, aCurrencyPair, otherCurrencyPair);

               FrameworkNumber aCurrencyValue = new FrameworkNumber("5");
               FrameworkNumber otherCurrencyValue = new FrameworkNumber("2");
               ExchangeRate aCurrencyRate = new ExchangeRate(primeCurrency, aCurrency, aCurrencyValue, now);
               ExchangeRate otherCurrencyRate = new ExchangeRate(otherCurrency, primeCurrency, otherCurrencyValue, now);
               expect(mockCurrencyManager.getParentToFractionalCurrencyMapForFractionalCurrency()).andStubReturn(newMap());

               expect(
                   mockExchangeRateManager.saveExchangeRate(null, new FrameworkString(primeName), new FrameworkString(aName), aCurrencyValue,
                       new FrameworkDate(now))).andReturn(null);
               expect(
                   mockExchangeRateManager.saveExchangeRate(null, new FrameworkString(otherName), new FrameworkString(primeName),
                       otherCurrencyValue, new FrameworkDate(now))).andReturn(null);

               Map<String, ExchangeRate> out = new HashMap<String, ExchangeRate>();
               out.put("primea", aCurrencyRate);
               out.put("otherprime", otherCurrencyRate);
               expect(mockPriceManager.getLatestExchangeRates(newList(aCurrencyPair, otherCurrencyPair))).andReturn(out);

               mockPMFactoryCleanup();

               replayMocks();

               ExchangeRateUploader uploader = newExchangeRateUploader(thread);
               uploader.initialise();
               uploader.run();
           }
                                                                                                      www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Why Sustainability
         private void mockPMFactoryCleanup() {
                                                                    Matters (c)
                 PersistenceFactory mockPersistenceFactory = addMock(PersistenceFactory.class);
                 mockPersistenceFactory.purgeAllStateForThisThread();
                 expect(mockGod.getPersistenceFactory()).andReturn(mockPersistenceFactory).anyTimes();
                 expect(mockPersistenceFactory.getExceptionsInRequest()).andReturn(Collections.<Throwable>emptyList()).times(1);
         }

         private void mockCyclicProcessManager() throws CyclicProcessException {
             mockCyclicProcessManager = addMock(CyclicProcessManager.class);
                 expect(mockGod.getCyclicProcessManager()).andStubReturn(mockCyclicProcessManager);
                 mockCyclicProcessManager.updateServerCyclicProcessCurrentRunStatus(isA(String.class),
                     isA(LISTENER_STATUS.class), isA(String.class), isA(Double.class), isA(Date.class));
                 expectLastCall().anyTimes();
                 server = addMock(Server.class);
                 expect(mockCyclicProcessManager.getThisServer()).andStubReturn(server);
         }

         private void setupCurrencyManager(Currency primeCurrency, Currency aCurrency, Currency otherCurrency) {
             mockCurrencyManager = addMock(CurrencyManager.class);
             List<Currency> allCurrencies = new ArrayList<Currency>();
                 allCurrencies.add(aCurrency);
                 allCurrencies.add(primeCurrency);
                 allCurrencies.add(otherCurrency);
                 expect(mockCurrencyManager.getPrimeCurrency()).andReturn(primeCurrency).anyTimes();
                 expect(mockCurrencyManager.getAllParentCurrencies()).andReturn(allCurrencies).times(2);
         }

         private void mockGetFXRatesAtDatesForCurrencies(Date now, FXCurrencyPair aCurrencyPair,
             FXCurrencyPair otherCurrencyPair) throws CurrencyException
         {
                 FrameworkNumber originalACurrencyRate = new FrameworkNumber("1.23");
                 Map<FXCurrencyPair, Collection<Date>> currencyPairAndDatesMap = new HashMap<FXCurrencyPair, Collection<Date>>();
                 currencyPairAndDatesMap.put(aCurrencyPair, Arrays.asList(now));
                 currencyPairAndDatesMap.put(otherCurrencyPair, Arrays.asList(now));
                 FXCurrencyPairRates outputObj = addMock(FXCurrencyPairRates.class);
                 expect(outputObj.rateMapSize()).andReturn(5).anyTimes();
                 expect(outputObj.getActualPriceDateForCurrencyPair(aCurrencyPair, now)).andReturn(null).once();
                 expect(outputObj.getRateFromFxRateMap(now, aCurrencyPair)).andReturn(originalACurrencyRate).once();
                 expect(outputObj.getActualPriceDateForCurrencyPair(otherCurrencyPair, now)).andReturn(null).once();
                 expect(outputObj.getRateFromFxRateMap(now, otherCurrencyPair)).andReturn(originalACurrencyRate);
                 expect(mockExchangeRateManager.getFXRatesAtDatesForCurrencies(currencyPairAndDatesMap)).andReturn(outputObj);
         }


                                                                                 www.growing-object-oriented-software.com 2011




                                                              Why Sustainability
                     Role mockAdminRole = addMock(Role.class);
                                                                    Matters (d)
                 private CyclicProcessThread mockUserStateAndGetCyclicProcessThread() {

                     CyclicProcessThread thread = addMock(CyclicProcessThread.class);
                     expect(thread.getAdminRole()).andReturn(mockAdminRole).anyTimes();
                     expect(thread.getAdminUser()).andReturn(adminUser).anyTimes();
                     thread.interrupt();
                     expectLastCall();
                     mockScreenManager = addMock(ScreenManager.class);
                     expect(mockGod.getScreenManager()).andReturn(mockScreenManager).anyTimes();
                     mockScreenManager.setThreadSignedOnState(new SignedOnState(adminUser, mockAdminRole, false));
                     expectLastCall().anyTimes();
                     expect(thread.getGod()).andReturn(mockGod).anyTimes();
                     expect(thread.getShutdownInProgress()).andReturn(false).anyTimes();
                     return thread;
                 }

                 private void mockContextPersistenceManager() {
                     mockFrameworkPersistenceManager = addMock(DatabaseFacade.class);
                     expect(mockGod.getDatabaseFacade()).andReturn(mockFrameworkPersistenceManager).anyTimes();
                     mockFrameworkPersistenceManager.beginNewSession();
                     expectLastCall().anyTimes();
                 }

                 private void mockPriceManager() throws PriceException {
                     mockPriceManagerFactory = addMock(PriceManagerFactory.class);
                     mockPriceManager = addMock(PriceManager.class);
                     expect(mockPriceManagerFactory.newPriceManager(mockFrameworkPersistenceManager,
                                                                     mockSystemVariableManager, null))
                         .andReturn(mockPriceManager).once();
                 }

                 private void mockSystemVariableManager() {
                     mockSystemVariableManager = addMock(SystemVariableManager.class);
                     expect(mockGod.getSystemVariableManager()).andReturn(mockSystemVariableManager).anyTimes();
                     expect(mockSystemVariableManager.getSystemVariable(CYCLIC_PROCESS_LISTENER_HEART_BEAT_TOLERANCE, "30000"))
                         .andReturn("30000").anyTimes();
                 }

                 private void mockLogger() {
                     logger = addMock(Logger.class);
                     logger.info(isA(String.class)); expectLastCall().atLeastOnce();
                     logger.debug(isA(String.class)); expectLastCall().atLeastOnce();
                 }
             }


                                                                                 www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Test Readability
            To design is to communicate clearly by
            whatever means you can control or
            master.
                                        —Milton Glaser




Friday, 9 September 2011
Test Names
                 Describe Features
          public class TargetObjectTest {
            @Test public void isReady() {



                                                           ✘
            @Test public void choose() {
            @Test public void choose1() {


       public class TargetObject {
         public void isReady() {
         public void choose(Picker picker) {



                               www.growing-object-oriented-software.com 2011




                    Test Names
                 Describe Features

 public class ListTests {
   @Test public void
   holdsItemsInTheOrderTheyWereAdded() {
   @Test public void
   canHoldMultipleReferencesToTheSameItem() {
   @Test public void
   throwsAnExceptionWhenRemovingAnItemItDoesntHold() {




                               www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Canonical Test Structure

public class StringTemplateTest {
 @Test public void expandsMacrosSurroundedWithBraces() {
    StringTemplate template = new StringTemplate("{a}{b}"); Setup
    HashMap<String,Object> macros = new HashMap<String,Object>();
    macros.put("a", "A"); macros.put("b", "B");

        String expanded = template.expand(macros);                   Execute

        assertThat(expanded, equalTo("AB"));                         Assert
                                                                     Teardown
    }
}




                                          www.growing-object-oriented-software.com 2011




           Streamline the Test Code


assertThat(instruments,
           hasItem(instrumentWithPrice(greaterThan(81))));




                                          www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Narrow Assertions and
                Expectations


 oneOf(failureReporter).cannotTranslateMessage(
                          with(SNIPER_ID), with(badMessage),
                          with(any(RuntimeException.class)));




                                      www.growing-object-oriented-software.com 2011




         Self-Describing Variables


         final static Chat UNUSED_CHAT = null;


           final static int INVALID_ID = 666;




                                      www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
@RunWith(NestedJUnit4.class)
 public class RemovingFullRowsTest extends Assert {
   private static final RotatableGrid PIECE =
     new RotatablePiece(
       "" +
       ".X.n" +
       ".X.n" +
       ".X.n");

   private final RowRemovalListener listener =
                             mock(RowRemovalListener.class);
   private Board board;

   private void dropAndFallToBottom(RotatableGrid piece) {
    board.drop(piece);
     while (board.hasFalling()) {
      board.tick();
     }
   }
   [...]

                           http://www.cs.helsinki.fi/u/luontola/tdd-2009/harjoitukset




public class When_many_rows_become_full_at_the_same_time {
  @Before
  public void dropPiece() {
     board = new Board("" +
                       "........n" +
                       "........n" +
                       "AAAA.AAAn" +
                       "BBBB..BBn" +
                       "CCCC.CCCn");
     board.addRowRemovalListener(listener);
     dropAndFallToBottom(PIECE);
   }

   @Test
   public void all_of_those_rows_are_removed() {
     String s = board.toString();
     assertFalse("Should not contain 'A':n" + s, s.contains("A"));
     assertFalse("Should not contain 'C':n" + s, s.contains("C"));
   }

   @Test
   public void the_row_removal_listener_is_notified_about_the_removed_rows() {
     verify(listener).onRowsRemoved(2);
   }
}




Friday, 9 September 2011
Constructing
         Complex Test Data
             Many attempts to communicate are
             nullified by saying too much.
                                                  —Robert Greenleaf




                  The Problem With
                  Object Structures
            Order order = new Order(
              new Customer("Sherlock Holmes",
                new Address("221b Baker Street",
                            "London",
                            new PostCode("NW1", "3RX"))));
            order.addLine(new OrderLine("Deerstalker Hat", 1));
            order.addLine(new OrderLine("Tweed Cape", 1));




 Order order1 = ExampleOrders.newDeerstalkerAndCapeAndSwordstickOrder();
 Order order2 = ExampleOrders.newDeerstalkerAndBootsOrder();




                                           www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Test Data Builder:
                     Add Indirection
             public class OrderBuilder {
               private Customer customer = new CustomerBuilder().build();
               private List<OrderLine> lines = new ArrayList<OrderLine>();
               private BigDecimal discountRate = BigDecimal.ZERO;

                 public OrderBuilder withCustomer(Customer customer) {
                   this.customer = customer;
                   return this;
                 }
                 public OrderBuilder withOrderLines(OrderLines lines) {
                   this.lines = lines;
                   return this;
                 }
                 public OrderBuilder withDiscount(BigDecimal discountRate) {
                   this.discountRate = discountRate;
                   return this;
                 }
                 public Order build() {
                   Order order = new Order(customer);
                   for (OrderLine line : lines) order.addLine(line);
                   order.setDiscountRate(discountRate);
                   return order;
                 }
             }
                                                   www.growing-object-oriented-software.com 2011




             Only Need To Include
               Relevant Values


  new OrderBuilder()
   .fromCustomer(
       new CustomerBuilder()
        .withAddress(new AddressBuilder().withNoPostcode().build())
        .build())
   .build();




                                                   www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Named Methods Make
              Mistakes Obvious


        new AddressBuilder()
         .withStreet("221b Baker Street")
         .withStreet2("London")
         .withPostCode("NW1 6XE")
         .build();




                                          www.growing-object-oriented-software.com 2011




               Use Builders to
            Create Similar Objects
            OrderBuilder hatAndCape = new OrderBuilder()
              .withLine("Deerstalker Hat", 1)
              .withLine("Tweed Cape", 1);




   Order orderWithDiscount    = hatAndCape
                                 .but().withDiscount(0.10).build();
   Order orderWithGiftVoucher = hatAndCape
                                 .but().withGiftVoucher("abc").build();




                                          www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Compacting Construction

     Order order = anOrder()
                    .from(aCustomer()
                           .with(anAddress().withNoPostcode()))
                    .build();



     Address aLongerAddress = anAddress()
                               .withStreet("221b Baker Street")
                               .withCity("London")
                               .with(postCode("NW1", "3RX"))
                               .build();




                                          www.growing-object-oriented-software.com 2011




               Refactor To Builders
 @Test public void reportsTotalSalesOfOrderedProducts() {
   sendAndProcess(anOrder()
                    .withLine("Deerstalker Hat", 1)
                    .withLine("Tweed Cape", 1));
   sendAndProcess(anOrder().withLine("Deerstalker Hat", 1));

    TotalSalesReport report = gui.openSalesReport();
    report.checkDisplayedTotalSalesFor("Deerstalker Hat", is(equalTo(2)));
    report.checkDisplayedTotalSalesFor("Tweed Cape", is(equalTo(1)));

 void sendAndProcess(OrderBuilder orderDetails) {
   Order order = orderDetails
                  .withDefaultCustomersReference(nextCustomerReference())
                  .build();
   requestSender.send(order);
   progressMonitor.waitForCompletion(order);
 }



                                          www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
What, Not How
      @Test public void reportsTotalSalesOfOrderedProducts() {
        havingReceived(anOrder()
                        .withLine("Deerstalker Hat", 1)
                        .withLine("Tweed Cape", 1));
        havingReceived(anOrder().withLine("Deerstalker Hat", 1));

          TotalSalesReport report = gui.openSalesReport();
          report.displaysTotalSalesFor("Deerstalker Hat", equalTo(2));
          report.displaysTotalSalesFor("Tweed Cape", equalTo(1));
      }




                                            www.growing-object-oriented-software.com 2011




              Test Diagnostics

              Mistakes are the portals of discovery.

                                                   —James Joyce




Friday, 9 September 2011
Explain Yourself

           assertEquals(16301, customer.getBalance());

           ComparisonFailure: expected:<[16301]> but was:<[16103]>




      assertEquals("balance", 16301, customer.getBalance());

      ComparisonFailure: balance expected:<[16301]> but was:<[16103]>




                                                    www.growing-object-oriented-software.com 2011




                    Describe Yourself
           ComparisonFailure: expected:<[a customer account id]>
                               but was:<[id not set]>

                                    java.lang.AssertionError: payment date
                                    Expected: <Thu Jan 01 01:00:01 GMT 1970>
                                         got: <Thu Jan 01 01:00:02 GMT 1970>


   Date startDate = namedDate(1000, "startDate");
   Date endDate = namedDate(2000, "endDate");

   Date namedDate(long timeValue, final String name) {
     return new Date(timeValue) {
      public String toString() { return name; }
     };
   }
                                    java.lang.AssertionError: payment date
                                    Expected: <startDate>
                                         got: <endDate>


                                                    www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Tracer Objects
    @RunWith(JMock.class) public class CustomerTest {
      final LineItem item1 = context.mock(LineItem.class, "item1");
      final LineItem item2 = context.mock(LineItem.class, "item2");
      final Billing billing = context.mock(Billing.class);

        @Test public void requestsInvoiceForPurchasedItems() {
          context.checking(new Expectations() {{
            oneOf(billing).add(item1);
            oneOf(billing).add(item2);
          }});
          customer.purchase(item1, item2);
          customer.requestInvoice(billing);
        }
    }
              not all expectations were satisfied
              expectations:
                expected once, already invoked 1 time: billing.add(<item1>)
                ! expected once, never invoked: billing.add(<item2>>)
              what happened before this:
                billing.add(<item1>)



                                                www.growing-object-oriented-software.com 2011




Friday, 9 September 2011
Test Flexibility
             Living plants are flexible and tender;
             the dead are brittle and dry.
             [...]
             The rigid and stiff will be broken.
             The soft and yielding will overcome.
                                    —Lao Tzu (c.604—531 B.C.)




               Specify Precisely
              What Should Happen
                 and No More




                                      www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Interlude




                   Information,
                Not Representation
        public interface CustomerBase {
          // Returns null if no customer found
          Customer findCustomerWithEmailAddress(String emailAddress);

      allowing(customerBase).findCustomerWithEmailAddress(theAddress);
                                              will(returnValue(null));


         public static final Customer NO_CUSTOMER_FOUND = null;


    public interface CustomerBase {
      Maybe<Customer> findCustomerWithEmailAddress(String emailAddress);
    }




                                          www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Precise Assertions
            assertThat(“strike price”,
                       92, equalTo(instrument.getStrikePrice()));



              assertThat(“transaction id”,
                         instrument.getTransactionId(),
                         largerThan(PREVIOUS_TRANSACTION_ID));



             assertThat(failureMessage,
                        allOf(containsString("strikePrice=92"),
                              containsString("id=FGD.430"),
                              containsString("is expired")));




                                           www.growing-object-oriented-software.com 2011




              Precise Expectations

   oneOf(auction).addAuctionEventListener(with(sniperForItem(itemId)));




 oneOf(auditTrail).recordFailure(with(
                                   allOf(containsString("strikePrice=92"),
                                         containsString("id=FGD.430"),
                                         containsString("is expired"))));




                                           www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Allow Queries
                 Expect Commands


       ignoring(auditTrail);
       allowing(catalog).getPriceForItem(item); will(returnValue(74));

       exactly(2).of(order).addItem(item, 74);




                                           www.growing-object-oriented-software.com 2011




                Only Enforce Order
                 When It Matters

@Test public void announcesMatchForOneAuction() {
  final AuctionSearcher auctionSearch =
                new AuctionSearcher(searchListener, asList(STUB_AUCTION1));

    context.checking(new Expectations() {{
      Sequence events = context.sequence("events");
      oneOf(searchListener).searchMatched(STUB_AUCTION1); inSequence(events);
      oneOf(searchListener).searchFinished();             inSequence(events);
     }});

    auctionSearch.searchFor(KEYWORDS);
}




                                           www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
“Guinea Pig” Objects
     public class XmlMarshallerTest {
       public static class MarshalledObject {
         private String privateField = "private";
         public transient String transientField = "transient";
         public final String publicFinalField = "public final";
         // constructors, accessors for private field, etc.
       }

         @Test public void marshallsAndUnmarshallsSerialisableFields() {
           XMLMarshaller marshaller = new XmlMarshaller();
           MarshalledObject original = new MarshalledObject();
           String xml = marshaller.marshall(original);
           MarshalledObject unmarshalled = marshaller.unmarshall(xml);
           assertThat(unmarshalled,
                      hasSameSerialisableFieldsAs(original));
         }
     }



                                            www.growing-object-oriented-software.com 2011




                 Tests Are Code Too


          • Expressiveness over convenience
          • Refactor and abstract
          • Focus on what matters
          • If it’s hard to test, that’s a clue




                                            www.growing-object-oriented-software.com 2011

Friday, 9 September 2011
Other sources




                               www.growing-object-oriented-software.com 2011




             And, of course…




Friday, 9 September 2011

More Related Content

What's hot

Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
Tatsuya Maki
 
생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트
기룡 남
 
Advanced Hibernate V2
Advanced Hibernate V2Advanced Hibernate V2
Advanced Hibernate V2
Haitham Raik
 
Vaadin7 modern-web-apps-in-java
Vaadin7 modern-web-apps-in-javaVaadin7 modern-web-apps-in-java
Vaadin7 modern-web-apps-in-java
Joonas Lehtinen
 

What's hot (20)

Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
 
Vaadin today and tomorrow
Vaadin today and tomorrowVaadin today and tomorrow
Vaadin today and tomorrow
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
 
생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트
 
The Ring programming language version 1.5.2 book - Part 67 of 181
The Ring programming language version 1.5.2 book - Part 67 of 181The Ring programming language version 1.5.2 book - Part 67 of 181
The Ring programming language version 1.5.2 book - Part 67 of 181
 
COScheduler In Depth
COScheduler In DepthCOScheduler In Depth
COScheduler In Depth
 
The Ring programming language version 1.8 book - Part 105 of 202
The Ring programming language version 1.8 book - Part 105 of 202The Ring programming language version 1.8 book - Part 105 of 202
The Ring programming language version 1.8 book - Part 105 of 202
 
Advanced Hibernate V2
Advanced Hibernate V2Advanced Hibernate V2
Advanced Hibernate V2
 
Rxjs vienna
Rxjs viennaRxjs vienna
Rxjs vienna
 
Data in Motion: Streaming Static Data Efficiently 2
Data in Motion: Streaming Static Data Efficiently 2Data in Motion: Streaming Static Data Efficiently 2
Data in Motion: Streaming Static Data Efficiently 2
 
Android development
Android developmentAndroid development
Android development
 
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
Bang-Bang, you have been hacked - Yonatan Levin, KolGeneBang-Bang, you have been hacked - Yonatan Levin, KolGene
Bang-Bang, you have been hacked - Yonatan Levin, KolGene
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7
 
Vaadin7 modern-web-apps-in-java
Vaadin7 modern-web-apps-in-javaVaadin7 modern-web-apps-in-java
Vaadin7 modern-web-apps-in-java
 
Testing microservices: Tools and Frameworks
Testing microservices: Tools and FrameworksTesting microservices: Tools and Frameworks
Testing microservices: Tools and Frameworks
 
React lecture
React lectureReact lecture
React lecture
 
GKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroid
 
The Ring programming language version 1.5.4 book - Part 70 of 185
The Ring programming language version 1.5.4 book - Part 70 of 185The Ring programming language version 1.5.4 book - Part 70 of 185
The Ring programming language version 1.5.4 book - Part 70 of 185
 

Viewers also liked

Viewers also liked (9)

Surfing the Agile Wave
Surfing the Agile WaveSurfing the Agile Wave
Surfing the Agile Wave
 
Smart Metrics
Smart Metrics  Smart Metrics
Smart Metrics
 
Research instruments case study
Research instruments case studyResearch instruments case study
Research instruments case study
 
Sullivan cuff case study
Sullivan cuff case studySullivan cuff case study
Sullivan cuff case study
 
The problem solvers problem
The problem solvers problemThe problem solvers problem
The problem solvers problem
 
Slow and dirty with callouts
Slow and dirty with calloutsSlow and dirty with callouts
Slow and dirty with callouts
 
System Error
System ErrorSystem Error
System Error
 
Tool up your lamp stack
Tool up your lamp stackTool up your lamp stack
Tool up your lamp stack
 
Value stream mapping
Value stream mapping  Value stream mapping
Value stream mapping
 

Similar to Sustaining Test-Driven Development

Tdd thats-not-what-we-meant
Tdd thats-not-what-we-meantTdd thats-not-what-we-meant
Tdd thats-not-what-we-meant
AHAConference
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
Spring mvc my Faviourite Slide
Spring mvc my Faviourite SlideSpring mvc my Faviourite Slide
Spring mvc my Faviourite Slide
Daniel Adenew
 
Unit test candidate solutions
Unit test candidate solutionsUnit test candidate solutions
Unit test candidate solutions
benewu
 

Similar to Sustaining Test-Driven Development (20)

Tdd thats-not-what-we-meant
Tdd thats-not-what-we-meantTdd thats-not-what-we-meant
Tdd thats-not-what-we-meant
 
Clean coding-practices
Clean coding-practicesClean coding-practices
Clean coding-practices
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotation
 
Unit testing with mock libs
Unit testing with mock libsUnit testing with mock libs
Unit testing with mock libs
 
Jersey Guice AOP
Jersey Guice AOPJersey Guice AOP
Jersey Guice AOP
 
Spring and Cloud Foundry; a Marriage Made in Heaven
Spring and Cloud Foundry; a Marriage Made in HeavenSpring and Cloud Foundry; a Marriage Made in Heaven
Spring and Cloud Foundry; a Marriage Made in Heaven
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
 
Spring mvc my Faviourite Slide
Spring mvc my Faviourite SlideSpring mvc my Faviourite Slide
Spring mvc my Faviourite Slide
 
Advance Java Programs skeleton
Advance Java Programs skeletonAdvance Java Programs skeleton
Advance Java Programs skeleton
 
Unit test candidate solutions
Unit test candidate solutionsUnit test candidate solutions
Unit test candidate solutions
 
Construire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradleConstruire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradle
 
Vaadin7
Vaadin7Vaadin7
Vaadin7
 
A GWT Application with MVP Pattern Deploying to CloudFoundry using Spring Roo
A GWT Application with MVP Pattern Deploying to CloudFoundry using  Spring Roo A GWT Application with MVP Pattern Deploying to CloudFoundry using  Spring Roo
A GWT Application with MVP Pattern Deploying to CloudFoundry using Spring Roo
 
droidQuery: The Android port of jQuery
droidQuery: The Android port of jQuerydroidQuery: The Android port of jQuery
droidQuery: The Android port of jQuery
 
Test-driven Development with AEM
Test-driven Development with AEMTest-driven Development with AEM
Test-driven Development with AEM
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
Rhino Mocks
Rhino MocksRhino Mocks
Rhino Mocks
 
Google App Engine Developer - Day3
Google App Engine Developer - Day3Google App Engine Developer - Day3
Google App Engine Developer - Day3
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
 
Rx workshop
Rx workshopRx workshop
Rx workshop
 

More from AgileOnTheBeach

Research instruments case study
Research instruments case studyResearch instruments case study
Research instruments case study
AgileOnTheBeach
 
Ignition team - creating agile companies
Ignition team - creating agile companiesIgnition team - creating agile companies
Ignition team - creating agile companies
AgileOnTheBeach
 
First build the right thing
First build the right thingFirst build the right thing
First build the right thing
AgileOnTheBeach
 
Behaviour Driven Development - Beyond given when then
Behaviour Driven Development - Beyond given when thenBehaviour Driven Development - Beyond given when then
Behaviour Driven Development - Beyond given when then
AgileOnTheBeach
 
Oxford Innovation - case study
Oxford Innovation - case studyOxford Innovation - case study
Oxford Innovation - case study
AgileOnTheBeach
 
Feedback Loops in Agile Development
Feedback Loops in Agile DevelopmentFeedback Loops in Agile Development
Feedback Loops in Agile Development
AgileOnTheBeach
 
A question of craftsmanship
A question of craftsmanshipA question of craftsmanship
A question of craftsmanship
AgileOnTheBeach
 
The problem solvers problem
The problem solvers problemThe problem solvers problem
The problem solvers problem
AgileOnTheBeach
 

More from AgileOnTheBeach (14)

Research instruments case study
Research instruments case studyResearch instruments case study
Research instruments case study
 
Objective agility
Objective agilityObjective agility
Objective agility
 
Lean and lego
Lean and lego Lean and lego
Lean and lego
 
Ignition team - creating agile companies
Ignition team - creating agile companiesIgnition team - creating agile companies
Ignition team - creating agile companies
 
First build the right thing
First build the right thingFirst build the right thing
First build the right thing
 
Embedded storycrafting
Embedded storycraftingEmbedded storycrafting
Embedded storycrafting
 
Beware sharp tools
Beware sharp toolsBeware sharp tools
Beware sharp tools
 
Lean startup
Lean startupLean startup
Lean startup
 
Behaviour Driven Development - Beyond given when then
Behaviour Driven Development - Beyond given when thenBehaviour Driven Development - Beyond given when then
Behaviour Driven Development - Beyond given when then
 
Agile in Practice
Agile in PracticeAgile in Practice
Agile in Practice
 
Oxford Innovation - case study
Oxford Innovation - case studyOxford Innovation - case study
Oxford Innovation - case study
 
Feedback Loops in Agile Development
Feedback Loops in Agile DevelopmentFeedback Loops in Agile Development
Feedback Loops in Agile Development
 
A question of craftsmanship
A question of craftsmanshipA question of craftsmanship
A question of craftsmanship
 
The problem solvers problem
The problem solvers problemThe problem solvers problem
The problem solvers problem
 

Recently uploaded

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Enterprise Knowledge
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 

Recently uploaded (20)

Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 

Sustaining Test-Driven Development

  • 1. Sustainable Test-Driven Development Steve Freeman and Nat Pryce www.growing-object-oriented-software.com 2011 Out Now Friday, 9 September 2011
  • 2. Why Sustainability Matters (a) public class ExchangeRateUploaderTest extends EasyMockTestCase { private Logger logger; private CurrencyManager mockCurrencyManager; private ExchangeRateManager mockExchangeRateManager; private PriceManagerFactory mockPriceManagerFactory; private PriceManager mockPriceManager; private GodObject mockGod; private DatabaseFacade mockPersistenceManager; private DatabaseFacade mockFrameworkPersistenceManager; private CyclicProcessManager mockCyclicProcessManager; private SystemVariableManager mockSystemVariableManager; private ScreenManager mockScreenManager; private Registry registry; private User adminUser; private Server server; private ExchangeRateUploader newExchangeRateUploader(CyclicProcessThread thread) { return new ExchangeRateUploader(thread) { @Override protected void initializeAction() throws FrameworkException { // Does nothing to prevent excessive mocking } @Override public Logger getLogger() { return logger; } @Override protected void setLogMDC() { } @Override protected User getUser() { return adminUser; } @Override protected CurrencyManager newCurrencyManager() { return mockCurrencyManager; } @Override protected PriceManagerFactory newPriceManagerFactory() { return mockPriceManagerFactory; } @Override protected CyclicProcessManager newCyclicProcessManager() { return mockCyclicProcessManager; } @Override protected DatabaseFacade newPersistenceManager() { return mockPersistenceManager; } @Override protected Registry newTaskPerformanceRegistry() { return registry; } @Override public PriceDataManager getPriceDataManager() { return null; } @Override protected ExchangeRateManager newExchangeRateManager() { return mockExchangeRateManager; } }; } www.growing-object-oriented-software.com 2011 Why Sustainability public void testInternalAction() throws FrameworkException { mockGod = addMock(GodObject.class); expect(mockGod.getPriceDataManager()).andStubReturn(null); mockPersistenceManager = addMock(DatabaseFacade.class); registry = addMock(Registry.class); Matters (b) adminUser = new TestUser("Admin", "", "", new TestCompany("company"), ""); mockCyclicProcessManager(); registry.finalizeThisThread(isA(String.class), isA(String.class)); Date now = DateUtils.trimMinutesAndSecondsFromDate(new Date()); mockSystemVariableManager(); mockLogger(); mockContextPersistenceManager(); mockPriceManager(); CyclicProcessThread thread = mockUserStateAndGetCyclicProcessThread(); String primeName = "prime"; String aName = "a"; String otherName = "other"; Currency primeCurrency = new TestCurrency(primeName, true); Currency aCurrency = new TestCurrency(aName, true); Currency otherCurrency = new TestCurrency(otherName, false); FXCurrencyPair aCurrencyPair = new FXCurrencyPair(primeCurrency, aCurrency); FXCurrencyPair otherCurrencyPair = new FXCurrencyPair(otherCurrency, primeCurrency); setupCurrencyManager(primeCurrency, aCurrency, otherCurrency); mockExchangeRateManager = addMock(ExchangeRateManager.class); mockGetFXRatesAtDatesForCurrencies(now, aCurrencyPair, otherCurrencyPair); FrameworkNumber aCurrencyValue = new FrameworkNumber("5"); FrameworkNumber otherCurrencyValue = new FrameworkNumber("2"); ExchangeRate aCurrencyRate = new ExchangeRate(primeCurrency, aCurrency, aCurrencyValue, now); ExchangeRate otherCurrencyRate = new ExchangeRate(otherCurrency, primeCurrency, otherCurrencyValue, now); expect(mockCurrencyManager.getParentToFractionalCurrencyMapForFractionalCurrency()).andStubReturn(newMap()); expect( mockExchangeRateManager.saveExchangeRate(null, new FrameworkString(primeName), new FrameworkString(aName), aCurrencyValue, new FrameworkDate(now))).andReturn(null); expect( mockExchangeRateManager.saveExchangeRate(null, new FrameworkString(otherName), new FrameworkString(primeName), otherCurrencyValue, new FrameworkDate(now))).andReturn(null); Map<String, ExchangeRate> out = new HashMap<String, ExchangeRate>(); out.put("primea", aCurrencyRate); out.put("otherprime", otherCurrencyRate); expect(mockPriceManager.getLatestExchangeRates(newList(aCurrencyPair, otherCurrencyPair))).andReturn(out); mockPMFactoryCleanup(); replayMocks(); ExchangeRateUploader uploader = newExchangeRateUploader(thread); uploader.initialise(); uploader.run(); } www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 3. Why Sustainability private void mockPMFactoryCleanup() { Matters (c) PersistenceFactory mockPersistenceFactory = addMock(PersistenceFactory.class); mockPersistenceFactory.purgeAllStateForThisThread(); expect(mockGod.getPersistenceFactory()).andReturn(mockPersistenceFactory).anyTimes(); expect(mockPersistenceFactory.getExceptionsInRequest()).andReturn(Collections.<Throwable>emptyList()).times(1); } private void mockCyclicProcessManager() throws CyclicProcessException { mockCyclicProcessManager = addMock(CyclicProcessManager.class); expect(mockGod.getCyclicProcessManager()).andStubReturn(mockCyclicProcessManager); mockCyclicProcessManager.updateServerCyclicProcessCurrentRunStatus(isA(String.class), isA(LISTENER_STATUS.class), isA(String.class), isA(Double.class), isA(Date.class)); expectLastCall().anyTimes(); server = addMock(Server.class); expect(mockCyclicProcessManager.getThisServer()).andStubReturn(server); } private void setupCurrencyManager(Currency primeCurrency, Currency aCurrency, Currency otherCurrency) { mockCurrencyManager = addMock(CurrencyManager.class); List<Currency> allCurrencies = new ArrayList<Currency>(); allCurrencies.add(aCurrency); allCurrencies.add(primeCurrency); allCurrencies.add(otherCurrency); expect(mockCurrencyManager.getPrimeCurrency()).andReturn(primeCurrency).anyTimes(); expect(mockCurrencyManager.getAllParentCurrencies()).andReturn(allCurrencies).times(2); } private void mockGetFXRatesAtDatesForCurrencies(Date now, FXCurrencyPair aCurrencyPair, FXCurrencyPair otherCurrencyPair) throws CurrencyException { FrameworkNumber originalACurrencyRate = new FrameworkNumber("1.23"); Map<FXCurrencyPair, Collection<Date>> currencyPairAndDatesMap = new HashMap<FXCurrencyPair, Collection<Date>>(); currencyPairAndDatesMap.put(aCurrencyPair, Arrays.asList(now)); currencyPairAndDatesMap.put(otherCurrencyPair, Arrays.asList(now)); FXCurrencyPairRates outputObj = addMock(FXCurrencyPairRates.class); expect(outputObj.rateMapSize()).andReturn(5).anyTimes(); expect(outputObj.getActualPriceDateForCurrencyPair(aCurrencyPair, now)).andReturn(null).once(); expect(outputObj.getRateFromFxRateMap(now, aCurrencyPair)).andReturn(originalACurrencyRate).once(); expect(outputObj.getActualPriceDateForCurrencyPair(otherCurrencyPair, now)).andReturn(null).once(); expect(outputObj.getRateFromFxRateMap(now, otherCurrencyPair)).andReturn(originalACurrencyRate); expect(mockExchangeRateManager.getFXRatesAtDatesForCurrencies(currencyPairAndDatesMap)).andReturn(outputObj); } www.growing-object-oriented-software.com 2011 Why Sustainability Role mockAdminRole = addMock(Role.class); Matters (d) private CyclicProcessThread mockUserStateAndGetCyclicProcessThread() { CyclicProcessThread thread = addMock(CyclicProcessThread.class); expect(thread.getAdminRole()).andReturn(mockAdminRole).anyTimes(); expect(thread.getAdminUser()).andReturn(adminUser).anyTimes(); thread.interrupt(); expectLastCall(); mockScreenManager = addMock(ScreenManager.class); expect(mockGod.getScreenManager()).andReturn(mockScreenManager).anyTimes(); mockScreenManager.setThreadSignedOnState(new SignedOnState(adminUser, mockAdminRole, false)); expectLastCall().anyTimes(); expect(thread.getGod()).andReturn(mockGod).anyTimes(); expect(thread.getShutdownInProgress()).andReturn(false).anyTimes(); return thread; } private void mockContextPersistenceManager() { mockFrameworkPersistenceManager = addMock(DatabaseFacade.class); expect(mockGod.getDatabaseFacade()).andReturn(mockFrameworkPersistenceManager).anyTimes(); mockFrameworkPersistenceManager.beginNewSession(); expectLastCall().anyTimes(); } private void mockPriceManager() throws PriceException { mockPriceManagerFactory = addMock(PriceManagerFactory.class); mockPriceManager = addMock(PriceManager.class); expect(mockPriceManagerFactory.newPriceManager(mockFrameworkPersistenceManager, mockSystemVariableManager, null)) .andReturn(mockPriceManager).once(); } private void mockSystemVariableManager() { mockSystemVariableManager = addMock(SystemVariableManager.class); expect(mockGod.getSystemVariableManager()).andReturn(mockSystemVariableManager).anyTimes(); expect(mockSystemVariableManager.getSystemVariable(CYCLIC_PROCESS_LISTENER_HEART_BEAT_TOLERANCE, "30000")) .andReturn("30000").anyTimes(); } private void mockLogger() { logger = addMock(Logger.class); logger.info(isA(String.class)); expectLastCall().atLeastOnce(); logger.debug(isA(String.class)); expectLastCall().atLeastOnce(); } } www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 4. Test Readability To design is to communicate clearly by whatever means you can control or master. —Milton Glaser Friday, 9 September 2011
  • 5. Test Names Describe Features public class TargetObjectTest { @Test public void isReady() { ✘ @Test public void choose() { @Test public void choose1() { public class TargetObject { public void isReady() { public void choose(Picker picker) { www.growing-object-oriented-software.com 2011 Test Names Describe Features public class ListTests { @Test public void holdsItemsInTheOrderTheyWereAdded() { @Test public void canHoldMultipleReferencesToTheSameItem() { @Test public void throwsAnExceptionWhenRemovingAnItemItDoesntHold() { www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 6. Canonical Test Structure public class StringTemplateTest { @Test public void expandsMacrosSurroundedWithBraces() { StringTemplate template = new StringTemplate("{a}{b}"); Setup HashMap<String,Object> macros = new HashMap<String,Object>(); macros.put("a", "A"); macros.put("b", "B"); String expanded = template.expand(macros); Execute assertThat(expanded, equalTo("AB")); Assert Teardown } } www.growing-object-oriented-software.com 2011 Streamline the Test Code assertThat(instruments, hasItem(instrumentWithPrice(greaterThan(81)))); www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 7. Narrow Assertions and Expectations oneOf(failureReporter).cannotTranslateMessage( with(SNIPER_ID), with(badMessage), with(any(RuntimeException.class))); www.growing-object-oriented-software.com 2011 Self-Describing Variables final static Chat UNUSED_CHAT = null; final static int INVALID_ID = 666; www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 8. @RunWith(NestedJUnit4.class) public class RemovingFullRowsTest extends Assert { private static final RotatableGrid PIECE = new RotatablePiece( "" + ".X.n" + ".X.n" + ".X.n");   private final RowRemovalListener listener = mock(RowRemovalListener.class);   private Board board;   private void dropAndFallToBottom(RotatableGrid piece) {    board.drop(piece);     while (board.hasFalling()) {      board.tick();     }   } [...] http://www.cs.helsinki.fi/u/luontola/tdd-2009/harjoitukset public class When_many_rows_become_full_at_the_same_time { @Before public void dropPiece() { board = new Board("" + "........n" + "........n" + "AAAA.AAAn" + "BBBB..BBn" + "CCCC.CCCn"); board.addRowRemovalListener(listener); dropAndFallToBottom(PIECE); } @Test public void all_of_those_rows_are_removed() { String s = board.toString(); assertFalse("Should not contain 'A':n" + s, s.contains("A")); assertFalse("Should not contain 'C':n" + s, s.contains("C")); }    @Test public void the_row_removal_listener_is_notified_about_the_removed_rows() { verify(listener).onRowsRemoved(2);    } } Friday, 9 September 2011
  • 9. Constructing Complex Test Data Many attempts to communicate are nullified by saying too much. —Robert Greenleaf The Problem With Object Structures Order order = new Order( new Customer("Sherlock Holmes", new Address("221b Baker Street", "London", new PostCode("NW1", "3RX")))); order.addLine(new OrderLine("Deerstalker Hat", 1)); order.addLine(new OrderLine("Tweed Cape", 1)); Order order1 = ExampleOrders.newDeerstalkerAndCapeAndSwordstickOrder(); Order order2 = ExampleOrders.newDeerstalkerAndBootsOrder(); www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 10. Test Data Builder: Add Indirection public class OrderBuilder { private Customer customer = new CustomerBuilder().build(); private List<OrderLine> lines = new ArrayList<OrderLine>(); private BigDecimal discountRate = BigDecimal.ZERO; public OrderBuilder withCustomer(Customer customer) { this.customer = customer; return this; } public OrderBuilder withOrderLines(OrderLines lines) { this.lines = lines; return this; } public OrderBuilder withDiscount(BigDecimal discountRate) { this.discountRate = discountRate; return this; } public Order build() { Order order = new Order(customer); for (OrderLine line : lines) order.addLine(line); order.setDiscountRate(discountRate); return order; } } www.growing-object-oriented-software.com 2011 Only Need To Include Relevant Values new OrderBuilder() .fromCustomer( new CustomerBuilder() .withAddress(new AddressBuilder().withNoPostcode().build()) .build()) .build(); www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 11. Named Methods Make Mistakes Obvious new AddressBuilder() .withStreet("221b Baker Street") .withStreet2("London") .withPostCode("NW1 6XE") .build(); www.growing-object-oriented-software.com 2011 Use Builders to Create Similar Objects OrderBuilder hatAndCape = new OrderBuilder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1); Order orderWithDiscount = hatAndCape .but().withDiscount(0.10).build(); Order orderWithGiftVoucher = hatAndCape .but().withGiftVoucher("abc").build(); www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 12. Compacting Construction Order order = anOrder() .from(aCustomer() .with(anAddress().withNoPostcode())) .build(); Address aLongerAddress = anAddress() .withStreet("221b Baker Street") .withCity("London") .with(postCode("NW1", "3RX")) .build(); www.growing-object-oriented-software.com 2011 Refactor To Builders @Test public void reportsTotalSalesOfOrderedProducts() { sendAndProcess(anOrder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1)); sendAndProcess(anOrder().withLine("Deerstalker Hat", 1)); TotalSalesReport report = gui.openSalesReport(); report.checkDisplayedTotalSalesFor("Deerstalker Hat", is(equalTo(2))); report.checkDisplayedTotalSalesFor("Tweed Cape", is(equalTo(1))); void sendAndProcess(OrderBuilder orderDetails) { Order order = orderDetails .withDefaultCustomersReference(nextCustomerReference()) .build(); requestSender.send(order); progressMonitor.waitForCompletion(order); } www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 13. What, Not How @Test public void reportsTotalSalesOfOrderedProducts() { havingReceived(anOrder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1)); havingReceived(anOrder().withLine("Deerstalker Hat", 1)); TotalSalesReport report = gui.openSalesReport(); report.displaysTotalSalesFor("Deerstalker Hat", equalTo(2)); report.displaysTotalSalesFor("Tweed Cape", equalTo(1)); } www.growing-object-oriented-software.com 2011 Test Diagnostics Mistakes are the portals of discovery. —James Joyce Friday, 9 September 2011
  • 14. Explain Yourself assertEquals(16301, customer.getBalance()); ComparisonFailure: expected:<[16301]> but was:<[16103]> assertEquals("balance", 16301, customer.getBalance()); ComparisonFailure: balance expected:<[16301]> but was:<[16103]> www.growing-object-oriented-software.com 2011 Describe Yourself ComparisonFailure: expected:<[a customer account id]> but was:<[id not set]> java.lang.AssertionError: payment date Expected: <Thu Jan 01 01:00:01 GMT 1970> got: <Thu Jan 01 01:00:02 GMT 1970> Date startDate = namedDate(1000, "startDate"); Date endDate = namedDate(2000, "endDate"); Date namedDate(long timeValue, final String name) { return new Date(timeValue) { public String toString() { return name; } }; } java.lang.AssertionError: payment date Expected: <startDate> got: <endDate> www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 15. Tracer Objects @RunWith(JMock.class) public class CustomerTest { final LineItem item1 = context.mock(LineItem.class, "item1"); final LineItem item2 = context.mock(LineItem.class, "item2"); final Billing billing = context.mock(Billing.class); @Test public void requestsInvoiceForPurchasedItems() { context.checking(new Expectations() {{ oneOf(billing).add(item1); oneOf(billing).add(item2); }}); customer.purchase(item1, item2); customer.requestInvoice(billing); } } not all expectations were satisfied expectations: expected once, already invoked 1 time: billing.add(<item1>) ! expected once, never invoked: billing.add(<item2>>) what happened before this: billing.add(<item1>) www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 16. Test Flexibility Living plants are flexible and tender; the dead are brittle and dry. [...] The rigid and stiff will be broken. The soft and yielding will overcome. —Lao Tzu (c.604—531 B.C.) Specify Precisely What Should Happen and No More www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 17. Interlude Information, Not Representation public interface CustomerBase { // Returns null if no customer found Customer findCustomerWithEmailAddress(String emailAddress); allowing(customerBase).findCustomerWithEmailAddress(theAddress); will(returnValue(null)); public static final Customer NO_CUSTOMER_FOUND = null; public interface CustomerBase { Maybe<Customer> findCustomerWithEmailAddress(String emailAddress); } www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 18. Precise Assertions assertThat(“strike price”, 92, equalTo(instrument.getStrikePrice())); assertThat(“transaction id”, instrument.getTransactionId(), largerThan(PREVIOUS_TRANSACTION_ID)); assertThat(failureMessage, allOf(containsString("strikePrice=92"), containsString("id=FGD.430"), containsString("is expired"))); www.growing-object-oriented-software.com 2011 Precise Expectations oneOf(auction).addAuctionEventListener(with(sniperForItem(itemId))); oneOf(auditTrail).recordFailure(with( allOf(containsString("strikePrice=92"), containsString("id=FGD.430"), containsString("is expired")))); www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 19. Allow Queries Expect Commands ignoring(auditTrail); allowing(catalog).getPriceForItem(item); will(returnValue(74)); exactly(2).of(order).addItem(item, 74); www.growing-object-oriented-software.com 2011 Only Enforce Order When It Matters @Test public void announcesMatchForOneAuction() { final AuctionSearcher auctionSearch = new AuctionSearcher(searchListener, asList(STUB_AUCTION1)); context.checking(new Expectations() {{ Sequence events = context.sequence("events"); oneOf(searchListener).searchMatched(STUB_AUCTION1); inSequence(events); oneOf(searchListener).searchFinished(); inSequence(events); }}); auctionSearch.searchFor(KEYWORDS); } www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 20. “Guinea Pig” Objects public class XmlMarshallerTest { public static class MarshalledObject { private String privateField = "private"; public transient String transientField = "transient"; public final String publicFinalField = "public final"; // constructors, accessors for private field, etc. } @Test public void marshallsAndUnmarshallsSerialisableFields() { XMLMarshaller marshaller = new XmlMarshaller(); MarshalledObject original = new MarshalledObject(); String xml = marshaller.marshall(original); MarshalledObject unmarshalled = marshaller.unmarshall(xml); assertThat(unmarshalled, hasSameSerialisableFieldsAs(original)); } } www.growing-object-oriented-software.com 2011 Tests Are Code Too • Expressiveness over convenience • Refactor and abstract • Focus on what matters • If it’s hard to test, that’s a clue www.growing-object-oriented-software.com 2011 Friday, 9 September 2011
  • 21. Other sources www.growing-object-oriented-software.com 2011 And, of course… Friday, 9 September 2011