ArticleS. MichaelFeathers.
RefactoringAgainstTheRedBar [add child]

Refactoring Against the Red Bar


At the XP2005 conference last week, I met Elizabeth Keogh. She described a neat way of making sure that you don't break your tests as you refactor them. Over beers, we convinced her that she should write it up. It's a neat technique. Very handy. Thanks Elizabeth!

http://www.livejournal.com/users/sirenian/21808.html


!commentForm


 Wed, 20 Jul 2005 05:02:26, J, Huh?
I have to admit, I really don't get what Elizabeth is trying to achieve here. She takes this test:

public void testThatWhenSaleIsMadeReceiptIsPrintedWithAppropriateAmount() {

LoginScreen[?] loginScreen = new LoginScreen[?]();
loginScreen.enterLogin("Liz");
loginScreen.enterPassword("Pa55word");
loginScreen.clickOk();
SaleScreen[?] saleScreen = new SaleScreen[?]();
...
assertEquals(10.00, receiptPrintOut.getPrice());

}

And then, due to the behaviour of Receipt::print() changing (which we can't see being called in the above code), she refactors this to:

public void testThatWhenSaleIsMadeReceiptIsPrintedWithAppropriateAmount() {

new LoginFixture[?]().login();
ReceiptPrintOut[?] receiptPrintOut = new SaleFixture[?]().buyItem(9123456);
assertEquals(10.00, receiptPrintOut.getPrice());

}

Huh? The assert is the same, but I don't understand the bits above it - it doesn't seem like we've been given enough code to understand it. What am I missing?
 Wed, 20 Jul 2005 17:22:47, Joe, test name
An aside, wouldn't "testPrintReceiptOnSale" be a much better name for this test?
 Sun, 24 Jul 2005 14:01:33, David Chelimsky, enough code to understand
Elizabeth starts by saying she's refactoring the tests. I *think* the refactoring she is doing is to move the LoginScreen[?] bits to their own fixture and the SaleScreen[?] bits to theirs. This is one of the problems of refactoring examples in general. Refactoring is a bunch of really small steps, but most examples show the starting and end points, making it challenging to get the small-step factor.

I could imagine the following scenario based on Elizabeth's example:

We start off with this test, which is passing.

public void testThatWhenSaleIsMadeReceiptIsPrintedWithAppropriateAmount()
{
LoginScreen loginScreen = new LoginScreen();
loginScreen.enterLogin("Liz");
loginScreen.enterPassword("Pa55word");
loginScreen.clickOk();
SaleScreen saleScreen = new SaleScreen();
saleScreen.buyItem(9123456);
assertEquals(10.00, receiptPrintOut.getPrice());
}


Other tests are going to need to log in to the system under test, so we decided to move the first four lines:

LoginScreen loginScreen = new LoginScreen();
loginScreen.enterLogin("Liz");
loginScreen.enterPassword("Pa55word");
loginScreen.clickOk();

to a LoginFixture[?]:
public class LoginFixture()
{
public void login()
{
LoginScreen loginScreen = new LoginScreen();
loginScreen.enterLogin("Liz");
loginScreen.enterPassword("Pa55word");
loginScreen.clickOk();
}
}


Before taking that step, however, we change the Receipt#print method to throw an exception and run the tests, noting the red bar, and noting that the exception thrown shows us the method name being called - so we know exactly where it's failing and that our test continues to excercise the code we want exercised.
public class Receipt {

public void print() {
throw new Exception("call to Receipt#print()");
//actual, passing code follows
}
}


We then make the move, re-run the test and notice that we still get the same red bar. Then we comment the exception in print(), uncomment the real code and run the test again, this time seeing the green bar. The green bar is meaningful now because we just saw a red bar and we know that the code we changed was the reason.

At this point, we'd go through the same process, moving these two lines:

SaleScreen saleScreen = new SaleScreen();
saleScreen.buyItem(9123456);

to a SaleFixture[?]:

public class SaleFixture()
{
public ReceiptPrintOut buyItem(string itemId)
{
SaleScreen saleScreen = new SaleScreen();
return saleScreen.buyItem(9123456);
}
}

using the same series of steps:

run the test
note a green bar
break the code (w/ a useful message)
run the test
note the red bar (w/ the useful message)
refactor the test
run the test
note the red bar (w/ the useful message)
un-break the code
run the test
note the green bar

That all make sense?
 Sun, 24 Jul 2005 23:24:13, J, enough code to understand
Thanks David, that makes much more sense.
 Sun, 30 Apr 2006 05:09:02, ,