Chapter 24: File Save and Load


If this program is to be of any use to anyone , it needs to be able to save and load files. That shouldn t be too hard, except that this morning I haven t any idea about how we can do it. Let s find out what we know and what we can do.

First the Functionality

As requested by the customer, it s time to implement File menu items, Save and Load, for the XML Notepad. I m tempted to start with the menu, but I know that if I do it may be hard to focus my attention on the tests that I should write for the functional part of the save and load. I know that once I get programming, it s hard to turn back to tests, and so my best bet is to start with tests.

A long time ago, I wrote a few file-oriented tests, just to learn how files work. I ll take a look at them now. Here s one that looks interesting:

 [Test] public void SaveDialog() { 
string hello;
string filename = GetFileName(false);
using (StreamWriter writer = File.CreateText(filename)) {
writer.Write("hello");
}
using (StreamReader reader = File.OpenText(filename)) {
hello = reader.ReadLine();
}
AssertEquals("hello", hello);
}
private string GetFileName(bool showDialog) {
if (! showDialog ) return "hello.txt";
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "txt files (*.txt)*.txtAll files (*.*)*.*" ;
dialog.FilterIndex = 2 ;
dialog.RestoreDirectory = true ;
if(dialog.ShowDialog() != DialogResult.OK)
Assert("dialog failed", false);
return dialog.FileName;
}

I remember writing this test. The GetFileName method accepts a Boolean so that when we run the tests, we don t get the dialog popping up all the time. I ll change it and run it with the dialog just to make sure it still works...and sure enough it pops up a standard Save As dialog. That will come in handy real soon. And the code for writing the file should be good for saving as well.

Examining the tests for TextModel, I find this small test:

 [Test] public void WriteStream() { 
model.SetLines (new String[] { "<P></P>"});
StringWriter w = new StringWriter();
model.Save(w);
AssertEquals("<P></P>\r\n", w.ToString());
}

That was written a long time ago to test saving as well. The corresponding code in the model Save() method is

 public void Save(TextWriter w) { 
foreach (string line in lines )
w.WriteLine(line);
}

That s good news. We know how to bring up a Save As dialog, and we know how to save the text.

Lesson  

Let s reflect a moment on what s going on here. I haven t worked for a while on the program, and it has been a very long time since I did any file I/O. You re probably way ahead of me on file I/O, but the principle here applies in any case. If we develop the habit of recording all the little experiments we do as tests, we can refer back to them later. Even simple ones like the Save() method here can give us a start later when we need it. If an experiment is worth doing, it s probably worth recording its result, and the best way I know is to record it as a test. When we need to refresh our memory on how something works or on something we learned in the past and have now forgotten, we can find the tests that will get us going again. That s what happened here, and I m ready to start moving forward.

Let s write another, somewhat stronger, test of saving and test load at the same time.

 [Test] public void WriteAndReadFile() { 
}

The name says what I have in mind. I want to write a file and read it back in to be sure everything is the same. I m not sure how to proceed ”it has been over a week since I have worked on the program ”so I m going to program by intention :

 [Test] public void WriteAndReadFile() { 
ClearModel();
VerifyClear();
GiveModelContents();
VerifyContents();
SaveModelToFile();
ClearModel();
VerifyClear();
LoadModelFromFile();
VerifyContents();
}

This code, expressing intention in a kind of top-down style, amounts to a plan: Clear the model, make sure it is clear, give it some contents, and make sure they are there. Save the model, clear it, be sure it is clear, and then load it and be sure we got the contents back. I find that things usually go well for me when I express my intention in the code this way, although, as you ve seen, I don t always remember to do it. Sometimes we just know what code we want to write, and my habit ”frankly, it s not the best habit to have ”is to write it. Then I usually have the time and inclination to make it look more expressive. A far better habit would be to start with expressiveness , even when I know what the detailed code should look like. I ll try to remember that. Here, however, I don t know what the detailed code will look like but I m about to write it to find out. Here goes with the easy ones, ClearModel() and VerifyClear(). I found this test, which looks a lot like ClearModel:

 [Test] public void TestNoLines() { 
model.SetLines(new String[0]);
AssertEquals(0, model.Lines.Count);
}

That suggests an implementation for ClearModel(), and while I m at it I ll use it in this test. The easy way to do that is the refactoring Extract Method. I ll use the refactoring menu, to give me this:

 [Test] public void TestNoLines() { 
ClearModel();
AssertEquals(0, model.Lines.Count);
}
private void ClearModel() {
model.SetLines(new String[0]);
}

That s pretty good. What about VerifyClear()? Well, that other line in TestNoLines looks like just the thing. We ll extract that also and then run the tests. I m taking a chance here in not running the tests after the first change, but it seems like a pretty simple refactoring, and since I used the tool to do it, I m confident. Let s see what happens with this next Extract Method:

 [Test] public void TestNoLines() { 
ClearModel();
VerifyClear();
}
private void VerifyClear() {
AssertEquals(0, model.Lines.Count);
}

That looks good. I want to test now, but I know that the new test shown here won t compile with those missing methods . I ll comment them out and then test:

 [Test] public void WriteAndReadFile() { 
ClearModel();
VerifyClear();
// GiveModelContents();
// VerifyContents();
// SaveModelToFile();
ClearModel();
VerifyClear();
// LoadModelFromFile();
// VerifyContents();
}

No surprises , everything works. Now let s do GiveModelContents and VerifyContents. I bet I can find a test to extract those ideas from. Sure enough, here s something good:

 [Test] public void TestNoProcessing() { 
model.SetLines(new String[3] { "hi", "there", "chet"});
AssertEquals(3, model.Lines.Count);
}

My plan will be to extract this code into the two methods I need and to beef up the second one to have more asserts in it. Two extracts later I have this:

 [Test] public void TestNoProcessing() { 
GiveModelContents();
VerifyContents();
}
private void VerifyContents() {
AssertEquals(3, model.Lines.Count);
}
private void GiveModelContents() {
model.SetLines(new String[3] { "hi", "there", "chet"});
}

I ll uncomment some of the new test lines and then run the tests. Then I ll enhance the VerifyContents():

 [Test] public void WriteAndReadFile() { 
ClearModel();
VerifyClear();
GiveModelContents();
VerifyContents();
// SaveModelToFile();
ClearModel();
VerifyClear();
// LoadModelFromFile();
// VerifyContents();
}

Again, the tests run. Let s improve VerifyContents just a bit:

 private void VerifyContents() { 
AssertEquals(3, model.Lines.Count);
AssertEquals("hi", model.Lines[0]);
AssertEquals("there", model.Lines[1]);
AssertEquals("chet", model.Lines[2]);
}

That s not much different, and it was certainly easy to do, but it will give more confidence when the save and load take place. So far this has all been easy, but now I ll have to do a little work. SaveModelToFile() is next, and we can steal that from the two tests at the beginning of this chapter. It goes like this:

 public void SaveModelToFile() { 
using (StreamWriter writer = File.CreateText("savedmodel.xml")) {
model.Save(writer);
}
}

I expect this to work, so I ll comment out the SaveModelToFile() line in the test to see what happens. The test runs. I can t resist looking at the file to see what it looks like, and it looks good. It isn t legal XML, of course, but I m not concerned about that in these tests. Now let s see if we can read the file back in, something like this:

 public void LoadModelFromFile() { 
String line;
ArrayList lines = new ArrayList();
using (StreamReader reader = File.OpenText("savedmodel.xml")) {
while((line = reader.ReadLine()) != null) {
lines.Add(line);
}
}
model.Lines = lines;
}

That compiles, and the tests run (with the new test fully uncommented, of course). The model is correctly saving itself to the file, but the load is taking place in our code, not in the model. Let s put a Load method in the TextModel, analogous to the Save, and modify the test to use it. In TestTextModel:

 public void LoadModelFromFile() { 
using (StreamReader reader = File.OpenText("savedmodel.xml")) {
model.Load(reader);
}
}

And in TextModel itself:

 public void Load(TextReader r) { 
String line;
lines = new ArrayList();
while((line = r.ReadLine()) != null) {
lines.Add(line);
}
}

It was actually a bit easier in TextModel: I just cleared the lines ArrayList and added directly to it. We now have Save and Load working in TextModel, and we can go ahead and create the menu items to exercise them. We know we should start with a test ”maybe even an acceptance test ”but honestly I m not sure how to do that. This is a good stopping point. Let s summarize and take a break.

Lesson  

So far this has gone very smoothly, especially since I haven t looked at any of this code for so long. I attribute that success to these things:

First, we began by reviewing old tests and the code they referenced. That refreshed our minds on how to do file I/O in general, and it reminded us what the TextModel already knew about saving. Frankly, I had forgotten that TextModel knew anything at all about I/O, and I suspect that you didn t know either.

Second, we began with a test and coded it by intention: clear and verify the model, give it contents and verify, save it, clear and verify, load and verify. We got a nice simple test, yet it makes us absolutely sure that the save and load are working.

Third, we moved in small steps. This enabled us to make essentially no mistakes. The only mistakes made were typos like missing parentheses that weren t even worth mentioning in the text. They took a moment to correct and didn t slow us down a bit.

Review tests, express intention, small steps. It s working so far.

Lesson  

I believe this is the first place where I have used the using statement in this book. As I mentioned, I had done some work with files in another context and brought the code over. It was in that other context that I learned about using and I really like it. The reason is that it avoids one of my most common errors: forgetting to close the file.

I guess I have a general problem in remembering things that have to be done at the end, and I ve developed habits to help me. The using statement helps me close files and do other disposal. When I type an open curly brace , I habitually type the closing one right away, and then go back and edit in the middle so that I won t forget it later. And in Smalltalk, which doesn t complain if you forget a return statement from a function, I developed the habit of using a method template that included a return statement to remind me.

If you have some mistakes that you make frequently, see if you can think of simple habits to form that will protect you from yourself.

Lesson  

You may be wondering why I couldn t figure out how to do a customer test. Surely it s just a matter of putting *save and *load commands into the interpreter? As you ll see below, that s what finally happened. But I couldn t see that here. Perhaps I was tired . I m glad I stopped for a break ”I might have really messed something up!




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