SlideShare una empresa de Scribd logo
1 de 21
Descargar para leer sin conexión
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

Más contenido relacionado

La actualidad más candente

Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xTatsuya Maki
 
Vaadin today and tomorrow
Vaadin today and tomorrowVaadin today and tomorrow
Vaadin today and tomorrowJoonas Lehtinen
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...탑크리에듀(구로디지털단지역3번출구 2분거리)
 
생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트기룡 남
 
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 181Mahmoud Samir Fayed
 
COScheduler In Depth
COScheduler In DepthCOScheduler In Depth
COScheduler In DepthWO Community
 
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 202Mahmoud Samir Fayed
 
Advanced Hibernate V2
Advanced Hibernate V2Advanced Hibernate V2
Advanced Hibernate V2Haitham Raik
 
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 2Martin Zapletal
 
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, KolGeneDroidConTLV
 
React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7Dongho Cho
 
Vaadin7 modern-web-apps-in-java
Vaadin7 modern-web-apps-in-javaVaadin7 modern-web-apps-in-java
Vaadin7 modern-web-apps-in-javaJoonas Lehtinen
 
Testing microservices: Tools and Frameworks
Testing microservices: Tools and FrameworksTesting microservices: Tools and Frameworks
Testing microservices: Tools and FrameworksPiotr Mińkowski
 
GKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGDG Korea
 
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 185Mahmoud Samir Fayed
 

La actualidad más candente (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
 

Destacado

Destacado (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 a 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-meantAHAConference
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotationjavatwo2011
 
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 HeavenJoshua Long
 
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 TestsTomek Kaczanowski
 
Spring mvc my Faviourite Slide
Spring mvc my Faviourite SlideSpring mvc my Faviourite Slide
Spring mvc my Faviourite SlideDaniel Adenew
 
Advance Java Programs skeleton
Advance Java Programs skeletonAdvance Java Programs skeleton
Advance Java Programs skeletonIram Ramrajkar
 
Unit test candidate solutions
Unit test candidate solutionsUnit test candidate solutions
Unit test candidate solutionsbenewu
 
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 gradleThierry Wasylczenko
 
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 Ali Parmaksiz
 
droidQuery: The Android port of jQuery
droidQuery: The Android port of jQuerydroidQuery: The Android port of jQuery
droidQuery: The Android port of jQueryPhDBrown
 
Test-driven Development with AEM
Test-driven Development with AEMTest-driven Development with AEM
Test-driven Development with AEMJan Wloka
 
Google App Engine Developer - Day3
Google App Engine Developer - Day3Google App Engine Developer - Day3
Google App Engine Developer - Day3Simon Su
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good TestsTomek Kaczanowski
 

Similar a 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
 

Más de AgileOnTheBeach

Research instruments case study
Research instruments case studyResearch instruments case study
Research instruments case studyAgileOnTheBeach
 
Ignition team - creating agile companies
Ignition team - creating agile companiesIgnition team - creating agile companies
Ignition team - creating agile companiesAgileOnTheBeach
 
First build the right thing
First build the right thingFirst build the right thing
First build the right thingAgileOnTheBeach
 
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 thenAgileOnTheBeach
 
Oxford Innovation - case study
Oxford Innovation - case studyOxford Innovation - case study
Oxford Innovation - case studyAgileOnTheBeach
 
Feedback Loops in Agile Development
Feedback Loops in Agile DevelopmentFeedback Loops in Agile Development
Feedback Loops in Agile DevelopmentAgileOnTheBeach
 
A question of craftsmanship
A question of craftsmanshipA question of craftsmanship
A question of craftsmanshipAgileOnTheBeach
 
The problem solvers problem
The problem solvers problemThe problem solvers problem
The problem solvers problemAgileOnTheBeach
 

Más de 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
 

Último

Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...
Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...
Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...Will Schroeder
 
Empowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership BlueprintEmpowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership BlueprintMahmoud Rabie
 
Designing A Time bound resource download URL
Designing A Time bound resource download URLDesigning A Time bound resource download URL
Designing A Time bound resource download URLRuncy Oommen
 
9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding Team9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding TeamAdam Moalla
 
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPAAnypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPAshyamraj55
 
Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™Adtran
 
VoIP Service and Marketing using Odoo and Asterisk PBX
VoIP Service and Marketing using Odoo and Asterisk PBXVoIP Service and Marketing using Odoo and Asterisk PBX
VoIP Service and Marketing using Odoo and Asterisk PBXTarek Kalaji
 
UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8DianaGray10
 
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...Aggregage
 
Salesforce Miami User Group Event - 1st Quarter 2024
Salesforce Miami User Group Event - 1st Quarter 2024Salesforce Miami User Group Event - 1st Quarter 2024
Salesforce Miami User Group Event - 1st Quarter 2024SkyPlanner
 
Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024D Cloud Solutions
 
Cybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptxCybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptxGDSC PJATK
 
How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?IES VE
 
Bird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystemBird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystemAsko Soukka
 
Comparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and IstioComparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and IstioChristian Posta
 
AI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity WebinarAI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity WebinarPrecisely
 
Secure your environment with UiPath and CyberArk technologies - Session 1
Secure your environment with UiPath and CyberArk technologies - Session 1Secure your environment with UiPath and CyberArk technologies - Session 1
Secure your environment with UiPath and CyberArk technologies - Session 1DianaGray10
 
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDEADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDELiveplex
 
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfUiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfDianaGray10
 

Último (20)

Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...
Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...
Apres-Cyber - The Data Dilemma: Bridging Offensive Operations and Machine Lea...
 
201610817 - edge part1
201610817 - edge part1201610817 - edge part1
201610817 - edge part1
 
Empowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership BlueprintEmpowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership Blueprint
 
Designing A Time bound resource download URL
Designing A Time bound resource download URLDesigning A Time bound resource download URL
Designing A Time bound resource download URL
 
9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding Team9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding Team
 
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPAAnypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPA
 
Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™
 
VoIP Service and Marketing using Odoo and Asterisk PBX
VoIP Service and Marketing using Odoo and Asterisk PBXVoIP Service and Marketing using Odoo and Asterisk PBX
VoIP Service and Marketing using Odoo and Asterisk PBX
 
UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8
 
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
 
Salesforce Miami User Group Event - 1st Quarter 2024
Salesforce Miami User Group Event - 1st Quarter 2024Salesforce Miami User Group Event - 1st Quarter 2024
Salesforce Miami User Group Event - 1st Quarter 2024
 
Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024
 
Cybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptxCybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptx
 
How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?
 
Bird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystemBird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystem
 
Comparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and IstioComparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and Istio
 
AI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity WebinarAI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity Webinar
 
Secure your environment with UiPath and CyberArk technologies - Session 1
Secure your environment with UiPath and CyberArk technologies - Session 1Secure your environment with UiPath and CyberArk technologies - Session 1
Secure your environment with UiPath and CyberArk technologies - Session 1
 
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDEADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
 
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfUiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
 

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