This is the text from William Wake's posting to the Extreme Programming mailing list proposing this challenge:
Test-first has come up in the extremeprogramming group again.
I thought I'd address this with a challenge: I'll provide a series of tests (a few more every few days), and anybody who wants can have a go at implementing them. I hope for two things out of this:
1. Anybody learning test-first can get a go at small-step incremental programming, without the pressure of inventing tests.
2. I'm curious how similar the designs will be.
I'll post the challenges on two egroups: extremeprogramming (for regular readers there) and xplorations (for low volume). The tests will be Java, but should have an obvious translation to other languages.
PROBLEM DOMAIN For the problem, I'll use a (super-) simple spreadsheet. My plan is to get a few rounds of features before dealing with any user interface.
WHAT TO DO * Implement code to meet the tests, one at a time. (No fair peeking ahead:)
* Refactor after each test.
* Reflect on the process - Are these the tests you'd have written? - Did you create the simplest code you could? - Did you spend much time debugging? - Any surprises in how your system is structured?
* (Optional) After you've done a particular challenge, I'd love to hear from you. Email to William.Wake@a... (William.Wake at acm.org). If you want to make life easy & interesting for me, do this:
Subject: CHALLENGE PART n Body: Brief description of the decisions you made ("I used a Frobble to calculate interest, and I split Foos into Bars and Bazs.") Attachment: A Zip file of your source code, named TFn.zip.
I'd be glad to hear your design decisions & comments even if you don't want to send your code. I won't re-post anything you send me without your permission.
SO Challenge part 1 will be coming soon.
(Look for the message "Test-First Challenge - Introduction" for background.)
Domain: Simple spreadsheet with columns A..Z, AA..AZ, ..., and rows 1..n.
Tests:
public void test1A_EmptyCells() {
Sheet sheet = new Sheet();
AssertEquals(sheet.get("A1"), "");
AssertEquals(sheet.get("ZX347"), "");
}
// Implement each test before going to the next one.
public void test1B_TextCells() {
Sheet sheet = new Sheet();
String theCell = "A21";
sheet.put(theCell, "A string");
AssertEquals(sheet.get(theCell), "A string");
sheet.put(theCell, "A different string");
AssertEquals(sheet.get(theCell), "A different string");
sheet.put(theCell, "");
AssertEquals(sheet.get(theCell), "");
}
// Implement each test before going to the next one.
// You can split this test case if it helps.
public void test1C_NumericCells() {
Sheet sheet = new Sheet();
String theCell = "A21";
sheet.put(theCell, "X99"); // "Obvious" string
AssertEquals(sheet.get(theCell), "X99);
sheet.put(theCell, "14"); // "Obvious" number
AssertEquals(sheet.get(theCell), "14");
sheet.put(theCell, " 99 X"); // Whole string must be numeric
AssertEquals(sheet.get(theCell), " 99 X");
sheet.put(theCell, " 1234 "); // Blanks ignored
AssertEquals(sheet.get(theCell), "1234");
sheet.put(theCell, " "); // Just a blank
AssertEquals(sheet.get(theCell), " ");
}
public void test1D_LiteralValues() { // Used to edit cell
Sheet sheet = new Sheet();
String theCell = "A21";
sheet.put(theCell, "Some string");
AssertEquals(sheet.getLiteral(theCell), "Some string");
sheet.put(theCell, " 1234 ");
AssertEquals(sheet.getLiteral(theCell), " 1234 ");
sheet.put(theCell, "=7"); // Foreshadowing formulas:)
AssertEquals(sheet.getLiteral(theCell), "=7");
}
// We'll talk about "get" and formulas next time
For the introduction and earlier parts, see the archives of either the XPlorations or extremeprogramming egroups.
FEEDBACK ON PART 1 * I flipped out on the test code a little. It should be regular JUnit, i.e., assertEquals("message", expected, actual). (I'm starting this exercise away from a Java environment.)
* Some people pointed out that none of the tests used two different cells. The next test addresses this, though I wish I'd put it in the last batch. For me, this is a place where the code helps tell what to test: realizing that all I need was a single variable would tell me to write a test demonstrating the need for a structure of some sort.
WHAT TO DO * Do part 1 if you haven't yet. * Implement code for each test (one at a time), then refactor to simplify your code. * (Optional) Email me with results and feedback. (William.Wake at acm.org). (What's working well/poorly? Missing tests? etc.) For the introduction and earlier parts, see the archives of either the XPlorations or extremeprogramming egroups.
FEEDBACK ON PART 1 * I flipped out on the test code a little. It should be regular JUnit, i.e., assertEquals("message", expected, actual). (I'm starting this exercise away from a Java environment.)
* Some people pointed out that none of the tests used two different cells. The next test addresses this, though I wish I'd put it in the last batch. For me, this is a place where the code helps tell what to test: realizing that all I need was a single variable would tell me to write a test demonstrating the need for a structure of some sort.
WHAT TO DO * Do part 1 if you haven't yet. * Implement code for each test (one at a time), then refactor to simplify your code. * (Optional) Email me with results and feedback. (William.Wake at acm.org). (What's working well/poorly? Missing tests? etc.)
CHALLENGE, PART 2
public void testManyCells() {
Sheet sheet = new Sheet();
sheet.put("A1", "8");
sheet.put("X27", "10");
sheet.put("ZX901", "4");
assertEquals("A1", "8", sheet.get("A1"));
assertEquals("X27", "10", sheet.get("X27"));
assertEquals("ZX901", "4", sheet.get("ZX901"));
sheet.put("A1", "12");
assertEquals("A1 after", "12", sheet.get("A1"));
assertEquals("X27 same", "10", sheet.get("X27"));
assertEquals("ZX901 same", "4", sheet.get("ZX901"));
}
// Implement code for previous test before moving to next one.
public void testFormulaSpec() {
Sheet sheet = new Sheet();
sheet.put("B1", " =7"); // note leading space
assertEquals("Not a formula", " =7", sheet.get("B1"));
assertEquals("Unchanged", " =7", sheet.getFormula("B1"));
}
// Next - start on parsing expressions
public void testConstantFormula() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7");
assertEquals("Formula", "=7", sheet.getFormula("A1"));
assertEquals("Value", "7", sheet.get("A1"));
}
// More formula tests. You may feel the need to make up
// additional intermediate test cases to drive your code
// better. (For example, you might want to test "2*3"
// before "2*3*4".) That's fine, go ahead and create them.
// Just keep moving one test at a time.
// Order of tests - I'm familiar enough with parsing to think
// it's probably better to do them in this order (highest
// precedence to lowest). For extra credit, you might redo
// this part of the exercise with the tests in a different order
// to see what difference it makes.
public void testParentheses() {
Sheet sheet = new Sheet();
sheet.put("A1", "=(7)");
assertEquals("Parends", "7", sheet.get("A1"));
}
public void testDeepParentheses() {
Sheet sheet = new Sheet();
sheet.put("A1", "=((((10))))");
assertEquals("Parends", "10", sheet.get("A1"));
}
public void testMultiply() {
Sheet sheet = new Sheet();
sheet.put("A1", "=2*3*4");
assertEquals("Times", "24", sheet.get("A1"));
}
public void testAdd() {
Sheet sheet = new Sheet();
sheet.put("A1", "=71+2+3");
assertEquals("Add", "76", sheet.get("A1"));
}
public void testPrecedence() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7+2*3");
assertEquals("Precedence", "13", sheet.get("A1"));
}
public void testFullExpression() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7*(2+3)*((((2+1))))");
assertEquals("Expr", "105", sheet.get("A1"));
}
// Add any test cases you feel are missing based on
// where your code is now.
// Then try your hand at a few test cases: Add "-" and "/"
// with normal precedence.
// Next, error handling.
public void testSimpleFormulaError() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7*");
assertEquals("Error", "#Error", sheet.get("A1"));
}
public void testParenthesisError() {
Sheet sheet = new Sheet();
sheet.put("A1", "(((((7))");
assertEquals("Error", "#Error", sheet.get("A1"));
}
// Add any more error cases you need. Numeric errors (e.g.,
// divide by 0) can return #Error too.
// Take a deep breath and refactor. This was a big jump.
// Next time we'll tackle formulas involving cells.
For the introduction and earlier parts, see the archives of either
the XPlorations or extremeprogramming egroups.
FEEDBACK ON PART 1
* I flipped out on the test code a little. It should be regular
JUnit, i.e., assertEquals("message", expected, actual). (I'm starting
this exercise away from a Java environment.)
* Some people pointed out that none of the tests used two different
cells. The next test addresses this, though I wish I'd put it in the
last batch. For me, this is a place where the code helps tell what to
test: realizing that all I need was a single variable would tell me
to write a test demonstrating the need for a structure of some sort.
WHAT TO DO
* Do part 1 if you haven't yet.
* Implement code for each test (one at a time), then refactor to
simplify your code.
* (Optional) Email me with results and feedback. (William.Wake at
acm.org). (What's working well/poorly? Missing tests? etc.)
CHALLENGE, PART 2
public void testManyCells() {
Sheet sheet = new Sheet();
sheet.put("A1", "8");
sheet.put("X27", "10");
sheet.put("ZX901", "4");
assertEquals("A1", "8", sheet.get("A1"));
assertEquals("X27", "10", sheet.get("X27"));
assertEquals("ZX901", "4", sheet.get("ZX901"));
sheet.put("A1", "12");
assertEquals("A1 after", "12", sheet.get("A1"));
assertEquals("X27 same", "10", sheet.get("X27"));
assertEquals("ZX901 same", "4", sheet.get("ZX901"));
}
// Implement code for previous test before moving to next one.
public void testFormulaSpec() {
Sheet sheet = new Sheet();
sheet.put("B1", " =7"); // note leading space
assertEquals("Not a formula", " =7", sheet.get("B1"));
assertEquals("Unchanged", " =7", sheet.getFormula("B1"));
}
// Next - start on parsing expressions
public void testConstantFormula() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7");
assertEquals("Formula", "=7", sheet.getFormula("A1"));
assertEquals("Value", "7", sheet.get("A1"));
}
// More formula tests. You may feel the need to make up
// additional intermediate test cases to drive your code
// better. (For example, you might want to test "2*3"
// before "2*3*4".) That's fine, go ahead and create them.
// Just keep moving one test at a time.
// Order of tests - I'm familiar enough with parsing to think
// it's probably better to do them in this order (highest
// precedence to lowest). For extra credit, you might redo
// this part of the exercise with the tests in a different order
// to see what difference it makes.
public void testParentheses() {
Sheet sheet = new Sheet();
sheet.put("A1", "=(7)");
assertEquals("Parends", "7", sheet.get("A1"));
}
public void testDeepParentheses() {
Sheet sheet = new Sheet();
sheet.put("A1", "=((((10))))");
assertEquals("Parends", "10", sheet.get("A1"));
}
public void testMultiply() {
Sheet sheet = new Sheet();
sheet.put("A1", "=2*3*4");
assertEquals("Times", "24", sheet.get("A1"));
}
public void testAdd() {
Sheet sheet = new Sheet();
sheet.put("A1", "=71+2+3");
assertEquals("Add", "76", sheet.get("A1"));
}
public void testPrecedence() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7+2*3");
assertEquals("Precedence", "13", sheet.get("A1"));
}
public void testFullExpression() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7*(2+3)*((((2+1))))");
assertEquals("Expr", "105", sheet.get("A1"));
}
// Add any test cases you feel are missing based on
// where your code is now.
// Then try your hand at a few test cases: Add "-" and "/"
// with normal precedence.
// Next, error handling.
public void testSimpleFormulaError() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7*");
assertEquals("Error", "#Error", sheet.get("A1"));
}
public void testParenthesisError() {
Sheet sheet = new Sheet();
sheet.put("A1", "(((((7))");
assertEquals("Error", "#Error", sheet.get("A1"));
}
// Add any more error cases you need. Numeric errors (e.g.,
// divide by 0) can return #Error too.
// Take a deep breath and refactor. This was a big jump.
// Next time we'll tackle formulas involving cells.
CHALLENGE, PART 2
public void testManyCells() {
Sheet sheet = new Sheet();
sheet.put("A1", "8");
sheet.put("X27", "10");
sheet.put("ZX901", "4");
assertEquals("A1", "8", sheet.get("A1"));
assertEquals("X27", "10", sheet.get("X27"));
assertEquals("ZX901", "4", sheet.get("ZX901"));
sheet.put("A1", "12");
assertEquals("A1 after", "12", sheet.get("A1"));
assertEquals("X27 same", "10", sheet.get("X27"));
assertEquals("ZX901 same", "4", sheet.get("ZX901"));
}
// Implement code for previous test before moving to next one.
public void testFormulaSpec() {
Sheet sheet = new Sheet();
sheet.put("B1", " =7"); // note leading space
assertEquals("Not a formula", " =7", sheet.get("B1"));
assertEquals("Unchanged", " =7", sheet.getFormula("B1"));
}
// Next - start on parsing expressions
public void testConstantFormula() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7");
assertEquals("Formula", "=7", sheet.getFormula("A1"));
assertEquals("Value", "7", sheet.get("A1"));
}
// More formula tests. You may feel the need to make up
// additional intermediate test cases to drive your code
// better. (For example, you might want to test "2*3"
// before "2*3*4".) That's fine, go ahead and create them.
// Just keep moving one test at a time.
// Order of tests - I'm familiar enough with parsing to think
// it's probably better to do them in this order (highest
// precedence to lowest). For extra credit, you might redo
// this part of the exercise with the tests in a different order
// to see what difference it makes.
public void testParentheses() {
Sheet sheet = new Sheet();
sheet.put("A1", "=(7)");
assertEquals("Parends", "7", sheet.get("A1"));
}
public void testDeepParentheses() {
Sheet sheet = new Sheet();
sheet.put("A1", "=((((10))))");
assertEquals("Parends", "10", sheet.get("A1"));
}
public void testMultiply() {
Sheet sheet = new Sheet();
sheet.put("A1", "=2*3*4");
assertEquals("Times", "24", sheet.get("A1"));
}
public void testAdd() {
Sheet sheet = new Sheet();
sheet.put("A1", "=71+2+3");
assertEquals("Add", "76", sheet.get("A1"));
}
public void testPrecedence() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7+2*3");
assertEquals("Precedence", "13", sheet.get("A1"));
}
public void testFullExpression() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7*(2+3)*((((2+1))))");
assertEquals("Expr", "105", sheet.get("A1"));
}
// Add any test cases you feel are missing based on
// where your code is now.
// Then try your hand at a few test cases: Add "-" and "/"
// with normal precedence.
// Next, error handling.
public void testSimpleFormulaError() {
Sheet sheet = new Sheet();
sheet.put("A1", "=7*");
assertEquals("Error", "#Error", sheet.get("A1"));
}
public void testParenthesisError() {
Sheet sheet = new Sheet();
sheet.put("A1", "(((((7))");
assertEquals("Error", "#Error", sheet.get("A1"));
}
// Add any more error cases you need. Numeric errors (e.g.,
// divide by 0) can return #Error too.
// Take a deep breath and refactor. This was a big jump.
// Next time we'll tackle formulas involving cells.
Joshua Macy
Last modified: Fri Jan 25 08:28:04 Eastern Standard Time 2002