Bad Test, Good Test

       Seb Rose
   Claysnow Limited

      @sebrose
Take an index card

What makes a unit test good or bad?

- Write 3 ‘good’ things on one side

- Write 3 ‘bad’ things on the other side
What is a unit test?
Understandable
@Test
public void scale() {
    Date now = new Date();

    Calendar calBegin = Calendar.getInstance();
    calBegin.setTime(now);
    calBegin.add(Calendar.HOUR, -4);
    Date begin = calBegin.getTime();

    Period p = new Period(4);
    long delta = p.getBegin().getTime() -
                               begin.getTime();
    Assert.assertTrue(
               p.getEnd().compareTo(now) >= 0);
    logger.trace(delta);
    Assert.assertTrue(delta < 10 && delta > -10);
    Assert.assertEquals(
       new Integer(4), new Integer(p.getScale()));
}
[Test]
	   public void asterisk_should_format_to_em()
	   {
        // Context
        Formatter f = new MarkdownFormatter();
        String expected = "This is <em>em</em> text";

        // Action
        String actual = f.Format("This is *em* text");

        // Outcome
        Assert.AreEqual(expected, actual);
	   }
Maintainable
@Test
	   public void smoker_requires_manual_referral()
	   {
        Customer customer = new Customer(“Joe”, “Smith”,
          “12/12/1980”, “Accountant”, “$300,000”, “Yes”,
          “No”);

        Referral referral = underwriting.process(customer);

        Assert.assertEquals(Referral.Manual, referral);
	   }
@Test
	   public void non_smoker_does_not_require_referral()
	   {
        Customer customer = new Customer(“Joe”, “Smith”,
          “12/12/1980”, “Accountant”, “$300,000”, “No”,
          “No”);

        Referral referral = underwriting.process(customer);

        Assert.assertEquals(Referral.None, referral);
	   }
public interface CustomerBuilder {
  public CustomerBuilder standardSingleMale();
  public CustomerBuilder standardSingleFemale();

    public CustomerBuilder smoker();
    public CustomerBuilder hasOccupation(String occupation);

    public Customer build();
}
@Test
	   public void non_smoker_does_not_require_referral()
	   {
        Customer customer = customerBuilder
          .standardSingleMale()
          .build();

        Referral referral = underwriting.process(customer);

        Assert.assertEquals(Referral.None, referral);
	   }
@Test
	   public void smoker_requires_manual_referral()
	   {
        Customer customer = customerBuilder
          .standardSingleFemale()
          .smoker()
          .build();

        Referral referral = underwriting.process(customer);

        Assert.assertEquals(Referral.Manual, referral);
	   }
Repeatable
@Test
public void testRolling() {
    Yahtzee y = Yahtzee.roll();
    assertEquals(y, y);
    Yahtzee y1 = new Yahtzee(1,2,3,4,5);
    Yahtzee y2 = new Yahtzee(1,2,3,4,5);
    assertTrue(y1.equals(y2));
    assertEquals(y1,y2);
    assertFalse(Yahtzee.roll()==Yahtzee.roll());
}
Necessary
[Test]
#region TestCase...
public void TestMapFromRomanNormalToRomanOnes(
                string roman, int times)
{
  var actual = _engine.ToRomanOnes(roman);
  var expected = _engine.Repeat('I', times);

    Assert.That(actual, Is.EqualTo(expected));
}
Granular
@Test
public void testRolling() {
    Yahtzee y = Yahtzee.roll();
    assertEquals(y, y);
    Yahtzee y1 = new Yahtzee(1,2,3,4,5);
    Yahtzee y2 = new Yahtzee(1,2,3,4,5);
    assertTrue(y1.equals(y2));
    assertEquals(y1,y2);
    assertFalse(Yahtzee.roll()==Yahtzee.roll());
}
Fast
Properties of unit testing

    Understandable
     Maintainable
      Repeatable
      Necessary
       Granular
         Fast
Look back at your index card

For each of your ‘good’ things, which of
the properties does it exhibit?

For each of your ‘bad’ things, which of
the properties does it contravene?
Have I missed any?
None of this is a substitute
 for writing testable code.
class PriceCalculator {
  public Price getPrice() {
    if (DateTime.Compare(DateTime.Now,PRICE_RISE_DATE)<0){
       return LOW_PRICE;
    } else {
       return HIGH_PRICE;
    }
  }
}
[Test]
	   public void prices_should_increase_on_2013_03_31()
	   {
      PriceCalculator calc = new PriceCalculator();

        // How can we set the date?
        // ...
	   }
class PriceCalculator {
  public PriceCalculator( SystemClock clock ) {
    clock_ = clock;
  }

    public Price getPrice() {
      if (DateTime.Compare(clock.Now, PRICE_RISE_DATE)<0){
        return LOW_PRICE;
      } else {
        return HIGH_PRICE;
      }
    }
}
[Test]
	   public void prices_should_increase_on_2013_03_31()
	   {
      SystemClock clock = new SystemClock(2013, 03, 31);
      PriceCalculator calc = new PriceCalculator(clock);

        // Now we control the date?
        // ....
	   }
How can we improve?
Properties of unit testing

    Understandable
     Maintainable
      Repeatable
      Necessary
       Granular
         Fast
Unit tests are your executable
           specification



Treat them with as much respect as
       your production code
The Feather’s definition of legacy
code:




      Code that has no tests
Seb Rose
Twitter: 
@sebrose
Blog: 

 www.claysnow.co.uk
E-mail:

 seb@claysnow.co.uk

Bad test, good test

  • 1.
    Bad Test, GoodTest Seb Rose Claysnow Limited @sebrose
  • 2.
    Take an indexcard What makes a unit test good or bad? - Write 3 ‘good’ things on one side - Write 3 ‘bad’ things on the other side
  • 3.
    What is aunit test?
  • 4.
  • 5.
    @Test public void scale(){ Date now = new Date(); Calendar calBegin = Calendar.getInstance(); calBegin.setTime(now); calBegin.add(Calendar.HOUR, -4); Date begin = calBegin.getTime(); Period p = new Period(4); long delta = p.getBegin().getTime() - begin.getTime(); Assert.assertTrue( p.getEnd().compareTo(now) >= 0); logger.trace(delta); Assert.assertTrue(delta < 10 && delta > -10); Assert.assertEquals( new Integer(4), new Integer(p.getScale())); }
  • 6.
    [Test] public void asterisk_should_format_to_em() { // Context Formatter f = new MarkdownFormatter(); String expected = "This is <em>em</em> text"; // Action String actual = f.Format("This is *em* text"); // Outcome Assert.AreEqual(expected, actual); }
  • 7.
  • 8.
    @Test public void smoker_requires_manual_referral() { Customer customer = new Customer(“Joe”, “Smith”, “12/12/1980”, “Accountant”, “$300,000”, “Yes”, “No”); Referral referral = underwriting.process(customer); Assert.assertEquals(Referral.Manual, referral); }
  • 9.
    @Test public void non_smoker_does_not_require_referral() { Customer customer = new Customer(“Joe”, “Smith”, “12/12/1980”, “Accountant”, “$300,000”, “No”, “No”); Referral referral = underwriting.process(customer); Assert.assertEquals(Referral.None, referral); }
  • 10.
    public interface CustomerBuilder{ public CustomerBuilder standardSingleMale(); public CustomerBuilder standardSingleFemale(); public CustomerBuilder smoker(); public CustomerBuilder hasOccupation(String occupation); public Customer build(); }
  • 11.
    @Test public void non_smoker_does_not_require_referral() { Customer customer = customerBuilder .standardSingleMale() .build(); Referral referral = underwriting.process(customer); Assert.assertEquals(Referral.None, referral); }
  • 12.
    @Test public void smoker_requires_manual_referral() { Customer customer = customerBuilder .standardSingleFemale() .smoker() .build(); Referral referral = underwriting.process(customer); Assert.assertEquals(Referral.Manual, referral); }
  • 13.
  • 14.
    @Test public void testRolling(){ Yahtzee y = Yahtzee.roll(); assertEquals(y, y); Yahtzee y1 = new Yahtzee(1,2,3,4,5); Yahtzee y2 = new Yahtzee(1,2,3,4,5); assertTrue(y1.equals(y2)); assertEquals(y1,y2); assertFalse(Yahtzee.roll()==Yahtzee.roll()); }
  • 15.
  • 16.
    [Test] #region TestCase... public voidTestMapFromRomanNormalToRomanOnes( string roman, int times) { var actual = _engine.ToRomanOnes(roman); var expected = _engine.Repeat('I', times); Assert.That(actual, Is.EqualTo(expected)); }
  • 17.
  • 18.
    @Test public void testRolling(){ Yahtzee y = Yahtzee.roll(); assertEquals(y, y); Yahtzee y1 = new Yahtzee(1,2,3,4,5); Yahtzee y2 = new Yahtzee(1,2,3,4,5); assertTrue(y1.equals(y2)); assertEquals(y1,y2); assertFalse(Yahtzee.roll()==Yahtzee.roll()); }
  • 19.
  • 20.
    Properties of unittesting Understandable Maintainable Repeatable Necessary Granular Fast
  • 21.
    Look back atyour index card For each of your ‘good’ things, which of the properties does it exhibit? For each of your ‘bad’ things, which of the properties does it contravene?
  • 22.
  • 23.
    None of thisis a substitute for writing testable code.
  • 24.
    class PriceCalculator { public Price getPrice() { if (DateTime.Compare(DateTime.Now,PRICE_RISE_DATE)<0){ return LOW_PRICE; } else { return HIGH_PRICE; } } }
  • 25.
    [Test] public void prices_should_increase_on_2013_03_31() { PriceCalculator calc = new PriceCalculator(); // How can we set the date? // ... }
  • 26.
    class PriceCalculator { public PriceCalculator( SystemClock clock ) { clock_ = clock; } public Price getPrice() { if (DateTime.Compare(clock.Now, PRICE_RISE_DATE)<0){ return LOW_PRICE; } else { return HIGH_PRICE; } } }
  • 27.
    [Test] public void prices_should_increase_on_2013_03_31() { SystemClock clock = new SystemClock(2013, 03, 31); PriceCalculator calc = new PriceCalculator(clock); // Now we control the date? // .... }
  • 29.
    How can weimprove?
  • 31.
    Properties of unittesting Understandable Maintainable Repeatable Necessary Granular Fast
  • 32.
    Unit tests areyour executable specification Treat them with as much respect as your production code
  • 33.
    The Feather’s definitionof legacy code: Code that has no tests
  • 34.
    Seb Rose Twitter: @sebrose Blog: www.claysnow.co.uk E-mail: seb@claysnow.co.uk

Editor's Notes

  • #4 Untestable code. Test first/TDD
  • #5 Naming Locality of reference Symbolic constants Distinguish Context/Action/Outcome Documentation - writing for other developers NOT the compiler
  • #6 Symbolic constant
  • #7 Naming Locality Context/Action/Outcome
  • #8 Abstractions White box (TDD) but not mirroring implementation SOLID - DRY
  • #9 Data
  • #10 Data
  • #11 Builders/Data DRY Can build graphs
  • #12 Builders
  • #13 Builders
  • #15 No unreliable resources file system/network No dates/random numbers
  • #16 Triangulation
  • #17 What’s this for?
  • #18 Single behaviour CHeck back to naming Executable Documentation
  • #19 No unreliable resources file system/network No dates/random numbers
  • #21 Osherove - maintainable, trustworthy, readable Bache - coverage, readability, robustness, speed
  • #24 TDD. Decoupling.