Building It In


I was able to pair with Chet for a while on this part of the story. I spent some time bringing him up to date on the code and the Undo experiments. I did this by a combination of talking, showing him the code, and a little bit of sketching on paper and moving cards around to show the relationship between the TextBox and the TextModel.

To start building up more of the real Undo capability, we knew we needed some tests. Testing manually using the GUI is inconvenient, and it s hard to get any useful information out of it. We see what it does, but not why. After a little discussion, we decided to use the CustomerTests.cs way of writing tests, with the little scripts saying what the input and output are. Our reasoning was that those tests go through the GUI, so they can exercise the form and the TextBox and their interaction with the TextModel. Those tests are end to end in nature and seem like just what we need.

Lesson  

Teams are sometimes concerned about whether the programmers should use the customer testing tools for their tests. It s a matter of judgment about using the best tool for the job. One concern is that sometimes customer tests are time-consuming . It s important that the programmer tests execute very quickly, because if they are slow we will use them less often, program more between running the tests, make more mistakes, and slow ourselves down. Sometimes customer tests are a little less clear about what has gone wrong than are the more usual NUnit-style tests, so tests written in the customer style might need more debugging. Our own tests here have a bit of that character, in fact. Remember that a single CustomerTest method reads all the .test files and executes them one after another. The result of this is that when a test fails, we get a little printout, but not much other information, and it would be difficult to set a breakpoint to stop for just that test. Since we don t use the debugger much, it hasn t been a problem. If it became a problem, we would figure out a way to address it. For now, we decided to write some customer-style tests to support our work on Undo.

Chet and I decided that we would implement a couple of new commands: *undo, of course, and also *type, which will take a string and push it into the XML Notepad one character at a time. With that in mind, we wrote the following test, to type some things into a notepad and then undo them back out:

 *enter 
*type abc
*output
<P>abc</P>
*end
*undo
*output
<P>ab</P>
*end
*undo
*output
<P>a</P>
*end
*undo
*output
<P></P>
*end
*undo
*output

*end

Naturally, this test doesn t run. The command *type isn t understood . We ll code that:

 private void InterpretCommands(String commands, String message) { 
StringReader reader = new StringReader(commands);
String line = reader.ReadLine();
CreateModel();
while ( line != null) {
if (line.StartsWith("*alt"))
ExecuteMenu("&"+line[4]);
else if (line.StartsWith("*type"))
TypeCharacters(line.Substring(6));
else if ( line == "*enter")
form.XMLKeyDownHandler((object) this, new KeyEventArgs(Keys.Enter));
else if ( line == "*shiftEnter")
form.XMLKeyDownHandler((object) this,
new KeyEventArgs(Keys.Enter Keys.Shift));
else if (line == "*display")
Console.WriteLine("display\r\n{0}\r\nend", model.TestText);
else if (line == "*output")
CompareOutput(reader, message);
else if (line == "*input")
SetInput(reader);
else if (line == "*loadfile")
LoadFile();
else if (line == "*savefile")
SaveFile();
else
Assert(line + " command not defined", false);
line = reader.ReadLine();
}
}

Now we implement TypeCharacters() and a supporting method that we made up by intention :

 private void TypeCharacters(String s) { 
foreach (char c in s)
model.InsertCharacter(c);
}

Here we implement InsertCharacter:

 public void InsertCharacter(char c) { 
lines[LineContainingCursor()] = FrontOfCursorLine() + c + BackOfCursorLine();
selectionStart++;
}

Working through this, we encountered a few problems that we aren t showing in detail. We forgot the *end at the end of each *output check, we forgot to show the vertical bar for the caret position, and we forgot to increment the selectionStart. Three errors for about 10 lines of code: about our usual. It s Chet s fault. The test runs down to the *undo operation now. We have to implement that:

 private void InterpretCommands(String commands, String message) { 
StringReader reader = new StringReader(commands);
String line = reader.ReadLine();
CreateModel();
while ( line != null) {
if (line.StartsWith("*alt"))
ExecuteMenu("&"+line[4]);
else if (line.StartsWith("*type"))
TypeCharacters(line.Substring(6));
else if (line == "*undo")
model.Restore();
else if ( line == "*enter")
form.XMLKeyDownHandler((object) this, new KeyEventArgs(Keys.Enter));
else if ( line == "*shiftEnter")
form.XMLKeyDownHandler((object) this,
new KeyEventArgs(Keys.Enter Keys.Shift));
else if (line == "*display")
Console.WriteLine("display\r\n{0}\r\nend", model.TestText);
else if (line == "*output")
CompareOutput(reader, message);
else if (line == "*input")
SetInput(reader);
else if (line == "*loadfile")
LoadFile();
else if (line == "*savefile")
SaveFile();
else
Assert(line + " command not defined", false);
line = reader.ReadLine();
}
}

This fails when the model.Restore() is executed, because we haven t done any Snapshot() operations yet. Where should we put the Snapshot()? Clearly we need one in the TextModel s InsertCharacter() method, but we also need one in the other operations, *enter in this case. We ll begin by putting Snapshot() calls wherever we need them, and then we ll figure out how to remove the duplication. Because we have a broken test, we re taking the shortest reasonable path to making it work:

 public void InsertCharacter(char c) { 
Snapshot();
lines[LineContainingCursor()] = FrontOfCursorLine() + c + BackOfCursorLine();
selectionStart++;
}

public void InsertTags(InsertAction action) {
Snapshot();
int cursorLine = LineContainingCursor();
lines.InsertRange(cursorLine+1, action.TagsToInsert);
selectionStart = NewSelectionStart(cursorLine + 1, action.TagsToSkip);
}


public void Enter() {
Snapshot();
if (InListItem())
InsertTags(Tags.ListItem);
else
InsertTags(Tags.Paragraph);
}

These changes cause the new customer test to run, except for that last part with the empty line containing just a vertical bar. We re not too surprised at that because we have never checked for empty output before. Truth is, there should be no lines now, and we don t have a good way to represent that.

Discussing this, we decided that updating the model in the InsertCharacter method is wrong. We have a way in the CustomerTests.cs to send characters through the notepad, and we should use it. We also want to test backspace followed by an undo, Shift+Enter, and other special situations that aren t handled now. If we push characters through the XML Notepad, however, things should work in a more regular fashion.

The code that sends things through the notepad has to create an event and an EventArgs. It looks, for example, like this:

 form.XMLKeyDownHandler((object) this, new KeyEventArgs(Keys.Enter)); 

This is the code to send an Enter through. Notice that we create the KeyEventArgs with whatever comes back from the enumeration Keys.Enter. But our InsertCharacter() code wants to send a variable character. This took some research. It turns out that a static method on Enum, Parse(), will, given a string, return the appropriate value from the Enum. After much trial and error, we came up with this, in CustomerTest:

 private void TypeCharacters(String s) { 
foreach (char c in s) {
form.XMLKeyDownHandler(
(object) this,
new KeyEventArgs( (Keys) Enum.Parse(typeof(Keys), c.ToString(), true)));
}
}

The Boolean true at the end of the Parse() call tells it to ignore case. For now that will be enough, although if we want our *type command to handle uppercase as well as lowercase, we ll have to do something special. We probably won t need that.

With this change made, the undo test stops working. The reason, of course, is that the snapshot is once again not happening, because we had hard- wired it into the InsertCharacter() method of the TextModel and that is no longer being used. Things are getting a bit messy: it s time to clean up just a bit.




Extreme Programming Adventures in C#
Javaв„ў EE 5 Tutorial, The (3rd Edition)
ISBN: 735619492
EAN: 2147483647
Year: 2006
Pages: 291

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net