September 2006 - Posts

I was testing the application and came across a bug; whenever you complete a reoccurring item, it uses a faulty algorithm.

 public void Complete()
{
    MetadataAttribute dueDateAttribute = this.Attributes["DueDate"];

    if (dueDateAttribute != null && dueDateAttribute.Value != null)
    {
        int unit = 0;
        if (this.Attributes.Contains("ReoccurrenceUnit"))
            unit = this.Attributes["ReoccurrenceUnit"].GetValue<int>();

        if (unit > 0)
            dueDateAttribute.Value = dueDateAttribute.GetValue<DateTime>().AddDays(Convert.ToInt32(unit));
        else
            this.Attributes.Remove(dueDateAttribute);
    }

    this.OnTaskCompleted(EventArgs.Empty);
}

There are some bugs with this; first off, I got rid of ReoccurrenceUnit, changing it to ReoccurrenceAmount instead!  I don't know how this got missed.  But that is a problem with using a collection as your property values; these are bugs that are harder to find.  This kind of problem couldn't have been caught by the test either because it ensure you enter in the correct key value, which I didn't.  I will place more provisions on that later; however, it will be working now.

The other problem is that it makes no use of the IsReoccurring value, which should be the true test.  Instead, it looks for the measurement value.  The refactored method now looks like this:

public void Complete()
{
    //Get the due date attribute
    MetadataAttribute dueDateAttribute = this.Attributes["DueDate"];

    //If this is a reoccurring task
    if (this.GetAttributeValue<bool>("IsReoccurring"))
    {
        //Get the amount of reoccurrence from the attributes
        int amount = this.GetAttributeValue<int>("ReoccurrenceAmount");

        //If the amount is a positive number
        if (amount > 0)
            //Add the amount to the date
            dueDateAttribute.Value = dueDateAttribute.GetValue<DateTime>().AddDays(Convert.ToDouble(amount));
        else
            //No amount provided; throw an error
            throw new ArgumentException("The reoccurrence amount is not a positive amount", "ReoccurrenceAmount attribute");
    }
    else
        //The task is completed, so remove the date attribute from the
        //collection; this means that it is completed
        this.Attributes.Remove(dueDateAttribute);

    this.OnTaskCompleted(EventArgs.Empty);
}

A quick test verifies that this works. 

In looking at the task details can be edited story, I noticed that a lot of the features it has is the same as the new task screen; so instead of writing a separate screen, I'm going to refactor the NewTask screen to include editing capabilities.  The first step is to provide a means to distinguish between adding and editing.  I do this by storing a reference to the task.

private Task CurrentTask
{
    get { return _currentTask; }
    set { _currentTask = value; }
}

Only when editing does a current task exist.  But how does it get populated?  The current task gets assigned from the EditTask method.  I use a method because a task only needs to be notified about editing once, instead of having that potential through the setter of the property:

public void EditTask(Task task)
{
    this.CurrentTask = task;
    //Set the property values to the controls on the screen
    this.TaskName = task.Name;
    this.Category = task.Category;
    if (task.Attributes.Contains("IsReoccurring"))
        this.IsReoccuring = task.GetAttributeValue<bool>("IsReoccurring");
    if (task.Attributes.Contains("ReoccurrenceAmount"))
        this.ReoccurrenceAmount = task.GetAttributeValue<int>("ReoccurrenceAmount");
    if (task.Attributes.Contains("ReoccurrenceMeasurement"))
        this.ReoccurrenceMeasurement = task.GetAttributeValue<string>("ReoccurrenceMeasurement");
    if (task.Attributes.Contains("DueDate"))
        this.DueDate = task.GetAttributeValue<DateTime>("DueDate");
    if (task.Attributes.Contains("Description"))
        this.Description = task.GetAttributeValue<string>("Description");
    this.gbAction.Enabled = true;
}

In the OK button click, I know have to determine whether the task is being edited or added as such below:

Task task = null;
if (this.CurrentTask == null)
    task = new Task(this.TaskName);
else
    task = this.CurrentTask;

In addition, I have to move over the Edit Task form code, so the following code is the new mix of add/edit functionality.  Note that gbAction is a group box holding the complete/defer options in the edit form.

//If editing a task, which means this feature is enabled
if (this.gbAction.Enabled)
{
    if (this.rbComplete.Checked)
        this.CurrentTask.Complete();
    else if (this.rbDefer.Checked)
    {
        int amount = 0;
        //If not a valid integer measurement, display an error message
        if (!int.TryParse(this.txtDeferAmount.Text, out amount))
        {
            this.ShowErrorNotification("Please provide an amount to defer the task by", this.txtDeferAmount);
            return;
        }
        else
            //Defer the task by a specified number of days
            this.CurrentTask.Defer(TimeSpan.FromDays(Convert.ToDouble(amount)));
    }
}
else
{
    //Add the task to the collection
    MGR.Tasks.Add(task);
    //Assign the new task as the current task; future changes will be updates
    this.CurrentTask = task;
    this.gbAction.Enabled = true;
}

This button now drives the completion and deferral through the object model.  In addition, it also adds the task to the collection.  A picture of the screen is attached.

The story for tasks can be deleted was a snap.  All I had to do is work with the ListView, to delete the item upon keypress.  This only works with a selected item.

private void lvTasks_KeyUp(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Delete)
    {
        //Get the selected task
        Task task = this.SelectedTask;
        //If not null remove the task
        if (task != null)
            MGR.Tasks.Remove(task);
    }
}

Real simple to do; SelectedTask is a property that gets the selected item represented in the list view.  If not null, it is removed from the collection, which fires the TaskRemoved event, which removes the item from the list.  This is the event handler for TaskRemoved:

void Tasks_TaskRemoved(object sender, DataEventArgs<Task> e)
{
    ListViewItem item = this.FindByTag(e.Data);
    //If the item exists, remove it from the list
    if (item != null)
        this.lvTasks.Items.Remove(item);
}

FindByTag is simply an iterative method, searching the tag of all the list items in the list looking for the item.

Posted by bmains | with no comments

I finished the file open method as such:

private void msiFileOpen_Click(object sender, EventArgs e)
{
    string file = this.GetFileFromDialog(this.ofdDialog);

    if (!string.IsNullOrEmpty(file))
    {
        StreamReader reader = new StreamReader(file);
        string xml = reader.ReadToEnd();

        if (!string.IsNullOrEmpty(xml))
        {
            MetadataSerialization<Task> serializer = new MetadataSerialization<Task>();
            //Clear the collection of any existing items
            MGR.Tasks.Clear();
            //Copy the tasks from the collection returned from serialization
            MGR.Tasks.CopyFrom(serializer.LoadFromXml(xml));
            //Save the path to the file that was opened
            _openedFile = file;
        }
        else
            MessageBox.Show("The file you selected is not a valid task file");
    }
}

I have a GetFileFromDialog which gets the path of the file from the parameter dialog box.  Then, if a file has been provided, I read the file, and make sure we actually have data; when cancelling the dialog box, a value will not be provided, so we must account for that.  When we do, I deserialize it, and pass the tasks to the root Manager object (the object model object).  This loads the tasks into the editor.  _openedFile will be discussed later.

Save File operations looks like this:

private void msiFileSave_Click(object sender, EventArgs e)
{
    string file;

    //Get the path of a file from one of the sources
    if (!string.IsNullOrEmpty(_openedFile))
        file = _openedFile;
    else
        file = this.GetFileFromDialog(this.sfdDialog);

    //if the file has been selected
    if (!string.IsNullOrEmpty(file))
    {
        MetadataSerialization<Task> serializer = new MetadataSerialization<Task>();
        //Save the tasks to a specific file
        serializer.SaveToXmlFile(MGR.Tasks, file);
    }
}

The _openedFile variable stores the path to the file that was opened; if a new task list, then it will be null, otherwise containing the field.  So, from this, if it exists, I get the path to the file and store it in a variable; otherwise, I get the file from the dialog box.  If no file exists, then don't proceed, otherwise, the collection is saved to XML.

I run it in the application, and what do you know, it works great.  That was really easy to implement, besides some minor problems with serialization.  These features have been completed as of now. 

Posted by bmains | with no comments

I tested this method to be sure that it would work:

[Test()]
public void TestLoadingAndSavingFile()
{
    TaskCollection collection = new TaskCollection();
    Task task = new Task("Wash Car", "Auto");
    task.Attributes.Add(new MetadataAttribute("IsReoccurring", false));
    collection.Add(task);

    task = new Task("Wax Car", "Auto");
    task.Attributes.Add(new MetadataAttribute("IsReoccurring", true));
    task.Attributes.Add(new MetadataAttribute("ReoccurrenceUnit", 180));
    task.Attributes.Add(new MetadataAttribute("ReoccurrenceMeasurement", "days"));
    collection.Add(task);

    Assert.AreEqual(2, collection.Count);

    //Serialize the file
    serialization.SaveToXmlFile(collection, PATH);

    XmlDocument document = new XmlDocument();
    document.Load(PATH);

    Assert.IsNotNull(document.DocumentElement);
    Assert.AreEqual(2, document.DocumentElement.ChildNodes.Count);

    MetadataElementCollection<Task> tasksCollection = serialization.LoadFromXmlFile(PATH);
    Assert.IsNotNull(tasksCollection);
    Assert.AreEqual(2, tasksCollection.Count);

    TaskCollection tasksOfficialCollection = new TaskCollection();
    tasksOfficialCollection.CopyFrom(tasksCollection);

    Assert.AreEqual(2, tasksOfficialCollection.Count);
}

There are a few things to note; I create a collection of tasks, then save it to a file.  I want to make sure that file is valid, and so I save it to a file, and check it out with an XML document object.  Next, I load it, to ensure I can get it back, and check it.  The last step is to ensure that I can get it back to a TaskCollection, and not of the base class type.

This was a problem, as without copying the items to the right collection, I wasn't able to convert the reference.  So I had to create a CopyFrom method to copy the items in the collection to the new collection.  It looks like this:

public void CopyFrom(MetadataElementCollection<Task> collection)
{
    foreach (Task item in collection)
        this.Add(item);
}

This worked pretty well in the test.

I just realized, the MetadataSerialization works with a single task!  I want multiple tasks; so I have to think of how I will do this.  Essentially, what I envision is adding another class; MetadataSerialization is somewhat big in itself, and I'll create a root class above it.  The first step will be to find out if it is being used.  In VC#, I right-click over the class name and click Find All References.

So I have to change the interface to:

public MetadataElementCollection<T> LoadFromXml(string xml)
public MetadataElementCollection<T> LoadFromXmlFile(string path)
private MetadataElementCollection<T> LoadXml(XmlDocument doc)  //Now loads the collection; previous method changed to LoadXmlElement
private T LoadXmlElement(XmlElement element)  //Now loads a single element
public string SaveToXml(MetadataElementCollection<T> collection)
public void SaveToXmlFile(MetadataElementCollection<T> collection, string path)

SaveToXml was an easy change; I changed the root element and added an extra loop to loop through the collection.  LoadXml previously is now LoadXmlElement to load a single element.  I changed the parameter to deal with a single element.  LoadXml  now looks like this:

private MetadataElementCollection<T> LoadXml(XmlDocument doc)
{
    MetadataElementCollection<T> collection = new MetadataElementCollection<T>();

    foreach (XmlElement element in doc.ChildNodes)
    {
        T item = this.LoadXmlElement(element);
        if (item != null)
            collection.Add(item);
    }

    return collection;
}

You may wonder how I derive my collections; all collections that use MetadataElementCollection inherit from MetadataElementCollection<MetadataElement>, and so I can use this as a common denominating factor.  Overall this was easy to accomplish.  At least I was able to refactor the interface before anything made use of it.  The only thing left to do is to change the tests, and test this new class out.

 

I've decided to work on the opening of a file.  I've created an OpenFileDialog (and a SaveFileDialog for later) to open a .tl file, which is the extension I'll use to set it up.  My dialog box looks something like this:

this.ofdDialog.DefaultExt = "tl";
this.ofdDialog.Filter = "Task Lists(*.tl)|*.tl";
this.ofdDialog.Title = "Open Task List";
this.ofdDialog.InitialDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);

From previous experience of an application using multiple file dialogs, I created two methods:

private string GetFileFromDialog(FileDialog dialog)
{
    if (dialog.ShowDialog() == DialogResult.OK)
    {
        this.SaveDirectory(dialog, dialog.FileName);
        return dialog.FileName;
    }
    return string.Empty;
}

private void SaveDirectory(FileDialog dialog, string filePath)
{
    FileInfo info = new FileInfo(filePath);
    dialog.InitialDirectory = info.DirectoryName;
}

When opening a file and navigating to a folder, I wanted to save the directory navigated to, at least for the application session.  So I created the SaveDirectory method to save the directory of a file to the dialog box, overwriting the MyDocuments default option.  I use the FileInfo as a convenient way to get the directory path.

GetFileFromDialog makes the action of retrieving the file name unique and avoids duplicating code for opening and saving files, which is a good refactoring to do.  I knew of this ahead of time from past experience with applications, and so I have this method return the selected file name, or empty if nothing.

For the opening of the file, I have a menu.  This menu is defined as this:

private void msiFileOpen_Click(object sender, EventArgs e)
{
    string file = this.GetFileFromDialog(this.ofdDialog);
    StreamReader reader = new StreamReader(file);
    string xml = reader.ReadToEnd();

    if (!string.IsNullOrEmpty(xml))
    {
       
    }
    else
        MessageBox.Show("The file you selected is not a valid task file");
}

I get the file, open it and get the XML.  I want to ensure the file actually does have XML, and so I test it.  In the first part of the if statement, I am going to use my MetadataSerialization object I created.  Unfortunately, I found a problem.
 

 

Posted by bmains | with no comments

To make it easier, I created this following method:

public void AddAttribute(string name, object value)
{
    this.Attributes.Add(new MetadataAttribute(name, value));
}

This made it easier to add methods, but it added overhead.  I changed the MetadataAttribute class to add type; however, that would mean an additional change to MetadataElement.  So, rather than introducing this for no good reason, I remove it to reduce overall complexity.  That is a principal of Steve McConnell's Code Complete:  if you don't use it, get rid of it.  Added complexity means added maintenance.

I found a bug with the MetadataAttribute; it seems no matter what, the value is coming back as a string.  This is because I return the type from the value.  I notice this problem with the loading/saving portion, and as such, I want to do this instead:

public Type Type
{
    get
    {
        if (_type != null)
            return _type;
        else if (_type == null && _value != null)
            return _value.GetType();
        return null;
    }
    set
    {
        _type = value;
    }
}

By making it writable, I can manage that.  This is the proposed fix to the bug; however, I have to go back through and make sure it is being added whenever I'm creating attributes.  Furthermore, I also have to figure out how to convert the object value based on a System.Type, which is no easy task from what I've read.

First comes first, the test I create to ensure that this is actually a problem is this:

[Test()]
public void TestTypeCorrectness()
{
    MetadataElement test = new MetadataElement("Test");
    test.Attributes.Add(new MetadataAttribute("IsBool", false));

    object value = test.Attributes["IsBool"].Value;
    Assert.IsTrue(value is bool);
    Assert.AreEqual(false, value);
}

However, this test worked out true; why is it then I am having problems?  I know part of the problem was with how I retrieve the value from the XML (Loading process), so I changed how I handle the value though this:

object value = null;
//Try to load the value by converting it to the proper type;
//default is to get the inner text as a string
try { value = Convert.ChangeType(node.InnerText, type); }
catch { value = node.InnerText; }

I use Convert.ChangeType to convert an object to a specific type; however, the problem with this is that the object has to implement IConvertible.  I don't know of another approach to handle this, and so I will look for a better solution later.  But previously, I knew that the only value loaded would be a string.  I need to get it to the right type.  I created this test:

[Test()]
public void TestTypeCorrectnessAfterLoading()
{
    Task task = serialization.LoadFromXmlFile(PATH);
    MetadataAttribute isReoccurringAttribute = task.Attributes["IsReoccurring"];
    MetadataAttribute measureAttribute = task.Attributes["ReoccurrenceMeasurement"];
    MetadataAttribute unitAttribute = task.Attributes["ReoccurrenceUnit"];

    Assert.IsTrue(isReoccurringAttribute.Value is bool);
    Assert.AreEqual(false, isReoccurringAttribute.Value);
    Assert.IsTrue(measureAttribute.Value is int);
    Assert.AreEqual(5, measureAttribute.Value);
    Assert.IsTrue(unitAttribute.Value is string);
    Assert.AreEqual(unitAttribute.Value, "miles");
}

This works too; I'm not sure if this problem crept in somewhere else, but I will proceed until I can find otherwise. 

Conceptually, I thought about the serialization process, and how I used the XmlSerializer object to serialize my objects into XML previously.  However, this wouldn't work because of how the attributes work in the collection; at least, from what I know, it wouldn't work.

So I created a MetadataSerializer, which serializes a MetadataElement derived type by looping through the attributes and saving them/loading them to/from XML.  First, to load the XML, I created two public interfaces to load XML; one to load a string, and one to load a file.

public T LoadFromXml(string xml)
{
    //Create a document and load the XML
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);
    return this.LoadXml(doc);
}

public T LoadFromXmlFile(string path)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(path);
    return this.LoadXml(doc);
}

The LoadXml method actually performs the work, using an XMLDocument object.  It loops through the child nodes of the root, returning an instantiated object of type T (the generic defined in the root class):

private T LoadXml(XmlDocument doc)
{
    //if no document element or children, return null
    if (doc.DocumentElement == null || doc.DocumentElement.ChildNodes.Count == 0)
        return null;

    //Get the name node, to pass to the constructor
    XmlNode nameNode = doc.SelectSingleNode("//Attribute[@Name='Name']");
    //If not provided, throw an error
    if (nameNode == null)
        throw new ArgumentNullException("Name", "Name cannot be null in the attributes list");

    //Create a new instancee of the item using the Activator, passing
    //the name in the parameter constructs
    T item = (T)Activator.CreateInstance(typeof(T), new object[] { nameNode.InnerText });

    foreach (XmlNode node in doc.DocumentElement.ChildNodes)
    {
        //Get the parameters of the node
        string name = node.Attributes["Name"].InnerText;
        Type type = Type.GetType(node.Attributes["Type"].InnerText);
        //Change the value to a specific type
        object value = node.InnerText;

        //Don't duplicate add the name again
        if (name != "Name")
        {
            //Create a new attribute with the parameters
            MetadataAttribute attribute = new MetadataAttribute(name);
            attribute.Value = value;
            //Add the attribute to the item
            item.Attributes.Add(attribute);
        }
    }

    return item;
}

So from this, we have a way to load an object from an XML string/file.  How about saving?  I created the same two equivalent methods to save the object to an XML string:

public string SaveToXml(T item)
{
    XmlDocument doc = new XmlDocument();
    //Create the root node to save the attributes to
    doc.AppendChild(doc.CreateElement("Element"));

    foreach (MetadataAttribute attribute in item.Attributes)
    {
        XmlElement attributeElement = doc.CreateElement("Attribute");
        //Create the name attribute
        XmlAttribute nameAttribute = doc.CreateAttribute("Name");
        nameAttribute.InnerText = attribute.Name;
        //Append the name attribute to the element
        attributeElement.Attributes.Append(nameAttribute);

        //Create the type attribute
        XmlAttribute typeAttribute = doc.CreateAttribute("Type");
        typeAttribute.InnerText = attribute.Type.FullName;
        //Append the type attribute to the element
        attributeElement.Attributes.Append(typeAttribute);

        //Append the text to the element
        attributeElement.InnerText = attribute.Value.ToString();
        //Append the element to the root element
        doc.DocumentElement.AppendChild(attributeElement);
    }

    return doc.OuterXml;
}

public void SaveToXmlFile(T item, string path)
{
    //Get the xml from the item
    string xml = this.SaveToXml(item);
    //Save the XML to the file
    using (StreamWriter writer = new StreamWriter(path, false))
        writer.Write(xml);
}

These two methods save the XML to a string/file by looping through the attributes, adding them to the XML root object, and then returning the outer XML.

I constructed this before testing; remember previously that I said that I believe testing using an actual constructed object instead of spending much more energy using a dummy object to test the same thing.  I used this philosophy, but not without testing it of course.  Here is the test:

[TestFixture()]
public class MetadataSerializationTest
{
    MetadataSerialization<Task> serialization = new MetadataSerialization<Task>();
    readonly string PATH = AppDomain.CurrentDomain.BaseDirectory + "test.xml";

    [Test()]
    public void TestLoadingXml()
    {
        //Create a text XML string
        string xml = @"
        <Element>
            <Attribute Name='Name' Type='System.String'>Test</Attribute>
        </Element>";

        //Deserialize the object
        Task task = serialization.LoadFromXml(xml);
        //Assert is true
        Assert.IsTrue(task.Name == "Test");
        Assert.AreEqual(1, task.Attributes.Count);
    }

    [Test()]
    public void TestSavingXml()
    {
        //Create a new task and add an attribute
        Task task = new Task("Test");
        task.Attributes.Add(new MetadataAttribute("IsReoccurring", false));

        //Get the XML string for the object
        string xml = serialization.SaveToXml(task);
        //Write out the XML
        Console.WriteLine("TestSavingXML:  " + xml);
        //Make sure there is an XML string
        Assert.IsFalse(string.IsNullOrEmpty(xml));
        Assert.IsTrue(xml.Contains("Name=\"Name\""));
        Assert.IsTrue(xml.Contains("Type=\"System.String\""));
        Assert.IsTrue(xml.Contains("Name=\"IsReoccurring\""));
    }

    [Test()]
    public void TestLoadingAndSavingXmlToFile()
    {
        //Create a new task and add an attribute
        Task task = new Task("Test", "Basic");
        task.Attributes.Add(new MetadataAttribute("IsReoccurring", false, typeof(bool)));
        task.Attributes.Add(new MetadataAttribute("ReoccurrenceMeasurement", 5, typeof(int)));
        task.Attributes.Add(new MetadataAttribute("ReoccurrenceUnit", "miles", typeof(string)));

        //Save the object to a file
        serialization.SaveToXmlFile(task, PATH);

        //Make sure the file exists
        Assert.IsTrue(File.Exists(PATH));
        task = null;

        //Load the file into a new task
        Task newTask = serialization.LoadFromXmlFile(PATH);
        Assert.IsNotNull(newTask);
        Assert.AreEqual("Test", newTask.Name);
        Assert.AreEqual("Basic", newTask.Category);
        Assert.IsTrue(newTask.Attributes.Contains("IsReoccurring"));
        Assert.AreEqual(false, newTask.Attributes["IsReoccurring"].Value);
    }

    [Test()]
    public void TestTypeCorrectnessAfterLoading()
    {
        Task task = serialization.LoadFromXmlFile(PATH);
        MetadataAttribute isReoccurringAttribute = task.Attributes["IsReoccurring"];
        MetadataAttribute measureAttribute = task.Attributes["ReoccurrenceMeasurement"];
        MetadataAttribute unitAttribute = task.Attributes["ReoccurrenceUnit"];

        Assert.IsTrue(isReoccurringAttribute.Value is bool);
        Assert.AreEqual(false, isReoccurringAttribute.Value);
        Assert.IsTrue(measureAttribute.Value is int);
        Assert.AreEqual(5, measureAttribute.Value);
        Assert.IsTrue(unitAttribute.Value is string);
        Assert.AreEqual(unitAttribute.Value, "miles");
    }
}

This test worked successfully, and so the object worked.  However, as I looked at the test, I did note that there were some things I could have tested, without first creating the object.  I could have tested that inputting an XML string worked without creating the object first.  I could have tested saving the object to an XML string successfully as well.

This made me wonder about my personal philosophy; isn't there something we can test before writing the object?  I believe so, but in advance, it is hard to make that determination.

I began attempting to work the file save/load process as part of the first iteration.  I started to create a method like this for the task object.

public string SaveToXml()
{
    XmlDocument document = new XmlDocument();
    //Create a root node called task
    XmlElement rootNode = document.CreateElement("Task");
    document.AppendChild(rootNode);

    //If the name exists, store this information
    if (this.Attributes.Contains("Name"))
    {
        //Append the name information as an attribute
        XmlAttribute nameAttribute = document.CreateAttribute("Name");
        nameAttribute.InnerText = this.Attributes["Name"].ToString();
        rootNode.Attributes.Append(nameAttribute);
    }
    //If the creation date exists, store this information
    if (this.Attributes.Contains("CreationDate"))
    {
        //Append the creation date information as an attribute
        XmlAttribute creationDateAttribute = document.CreateAttribute("CreationDate");
        creationDateAttribute.InnerText = this.Attributes["CreationDate"].ToString();
        rootNode.Attributes.Append(creationDateAttribute);
    }
    //If the due date exists, store this information
    if (this.Attributes.Contains("DueDate"))
    {
        //Append the due date information as an attribute
        XmlAttribute dueDateAttribute = document.CreateAttribute("DueDate");
        dueDateAttribute.InnerText = this.Attributes["DueDate"].ToString();
        rootNode.Attributes.Append(dueDateAttribute);
    }
   
    //Create the element for is reoccurring
    XmlElement reoccurringElement = document.CreateElement("IsReoccurring");
    //If a value exists, set the inner text to the value
    if (this.Attributes.Contains("IsReoccurring"))
        reoccurringElement.InnerText = this.Attributes["IsReoccurring"].ToString();
    //By default, use false for reoccurring
    else
        reoccurringElement.InnerText = false;
    rootNode.AppendChild(reoccurringElement);

    //If the reoccurrence element exists, store this information
    if (this.Attributes.Contains("ReoccurrenceMeasurement"))
    {
        //Append the reoccurrence measurement as a child element
        XmlElement reoccurrenceMeasurementElement = document.CreateElement("ReoccurrenceMeasurement");
    }
}

My reasoning was I would loop through the collection, adding the tasks to a string, creating an XML document, and then save it.  One of my thoughts was that I could include this functionality in a method above; however, I don't know of any other feature that would need that yet, and I could refactor that concept later into the application.  However, as I was writing it, I wondered what I was doing, because this method needs heavy refactoring itself.  This was a bad design on my part, and will need to be reevaluated.

You may also notice that I didn't create a major test for converting as task yet.  The reason is because if I'm going to write all of this functionality, I figure I may as well actually write the method or feature, and test it heavily.  The reason is, when you create a test, you write a bunch of code that looks similar, then create another bunch of code for the actual implementation.  So why not write the implementation and then test it heavily to see if it worked?  To me, that is very similar.

Also, I only do this for a limited number of things, like the Object Model API methods; and only where it seems I have the approach, and I would be writing all that code for the test anyway and it seems like I would go way out of my way not to do it right in the object model.  View it however you may; I think that is an acceptable approach.

Posted by bmains | 1 comment(s)

If you've looked at my blog, you've probably seen a lot of testing done; in fact, I've been torn with deciding what to include, and what not to.  At times, it seems like it might be important, but then later I think about it, and say "maybe it wasn't."  I ended up skipping an iteration cycle because I was able to complete the features quicker than I could post about them, so I apologize for that.

But I wanted to talk about why I do post about testing.  The reason you see testing up front is because with XP, there are a lot of things we assume that will work.  As developers, we do make a lot of assumptions; we assume the code we right is bug-free (at least some times), we think that we know about one area of the framework when it turns out we were wrong.  I have a lot of problems with interactions in my app, that I wonder if testing first would have fixed it.

At any rate, testing first with what you don't know, or are unsure of, or even if you think you know, is a good approach, because we often aren't completely sure, and are operating off of our assumptions, knowledge passed on in books, blogs, and articles, or some other source.  That's why I show the importance of testing, and even improving my tests.  Sometimes they blow up, sometimes they don't.  Seeing the progression can be good, or it can be overbearing in a blog, and I just pray that this is in the former.

At any rate, testing first before implementing is a good approach, because it really does improve the quality of your code.  I'm seeing great progress in my own code.  I'm implementing working features better than I did before.  It has change the way that I view approaching developing applications.  Unfortunately, in my career, I didn't have a ton of success with applications, mostly because I was knew, and mostly because I was forced onto a methodology that didn't work in the past for other developers and that didn't also work for me.

I see how iterative development can work, with testing as the key.  It may seem like a lot; there are plenty of times when I'm dreading creating another test, and that may be a time to walk away for a little bit.  But that's the typical life of a developer. At any rate, I wanted to discuss the balance between testing and implementing, and why testing was important, and why I had a lot there.  I hope you found that beneficial.

Posted by bmains | with no comments
Filed under:

Figuring out what to test can be hard to determine.  I created this following test:

[Test()]
public void TestWriteXML()
{
    XmlDocument doc = new XmlDocument();
    doc.LoadXml("<TestRoot><TestElement>Test Item</TestElement></TestRoot>");
    //Save the document to the file path
    doc.Save(this.GetPath());

    Assert.IsTrue(File.Exists(this.GetPath()));

    using (StreamReader reader = new StreamReader(this.GetPath()))
    {
        string line = reader.ReadToEnd();
        Assert.AreEqual("<TestRoot><TestElement>Test Item</TestElement></TestRoot>", line);
    }

    doc = new XmlDocument();
    try
    {
        doc.Load(this.GetPath());
        Assert.IsTrue(doc.DocumentElement != null);
        Assert.IsTrue(doc.DocumentElement.ChildNodes.Count == 1);
    }
    catch
    {
        Assert.Fail("The XML could not be validated against");
    }
}

It failed because the XML was saved in a different format than I thought.  But then I got thinking about it.  Do I really care about the format?  Do I really need to worry about what format the XML is in?  Actually, the properly formatted XML would be better anyway.  I would just like to see that the document element exists and the count is there...  So, as you can see, it can sometimes be difficult whether to test or not to.  However, it does benefit you to see how the implementation actually works.

I always struggle with what to test; should I test with the windows UI components, like the DataGridView and ListView?  Unfortunately, you can't test features like the height/width of the control and GUI stuff, though that is where NUnitForms comes into play.  This is a new testing framework for testing the GUI, as well as implementing a recorder that you can record your actions.  Very cool.

At any rate, we got the IO down; let's implement the IO features.

Posted by bmains | with no comments

For this, we need to determine about the minimum characteristics of file IO capabilities.  Whenever you open a streamreader or streamwriter object, you must ensure the object is disposed, and so using is a nice keyword to use for this.  An example of using is defined below:

using (StreamWriter writer = new StreamWriter(this.GetPath(), false))
{
    writer.Write("Test");
}

The using keyword is useful when the object you are instantiating implements IDisposable.  This is because it will automatically handle disposing of the object for you; this way, we can ensure that the writer is closed upon completion, and the file will not be locked for the next reader/writer access.

From this, I create the following tests:

[TestFixture()]
public class FileIOTest
{
    private string GetPath()
    {
        return AppDomain.CurrentDomain.BaseDirectory + "\\test.tl";
    }

    [Test()]
    public void TestWriteToFile()
    {
        using (StreamWriter writer = new StreamWriter(this.GetPath(), false))
        {
            writer.Write("Test");
        }

        using (StreamReader reader = new StreamReader(this.GetPath()))
        {
            Assert.AreEqual("Test", reader.ReadToEnd());
        }
    }

    [Test()]
    public void TestWriteLineToFile()
    {
        using (StreamWriter writer = new StreamWriter(this.GetPath(), false))
        {
            writer.WriteLine("Test");
        }

        using (StreamReader reader = new StreamReader(this.GetPath()))
        {
            Assert.AreEqual("Test\r\n", reader.ReadToEnd());
        }
    }

    [Test()]
    public void TestWriteLinesToFile()
    {
        using (StreamWriter writer = new StreamWriter(this.GetPath(), false))
        {
            writer.WriteLine("Test");
        }

        using (StreamReader reader = new StreamReader(this.GetPath()))
        {
            Assert.AreEqual("Test\r\n", reader.ReadToEnd());
        }

        using (StreamWriter writer = new StreamWriter(this.GetPath(), true))
        {
            writer.WriteLine("Test");
        }

        using (StreamReader reader = new StreamReader(this.GetPath()))
        {
            Assert.AreEqual("Test\r\nTest\r\n", reader.ReadToEnd());
        }
    }
}

All of these tests worked successfully.  But, I had been thinking about using XML, and so the next test we will have to perform will use the XML objects to access it.

Posted by bmains | with no comments

In looking at the stories, the developers determined that the following risks were associated with the values that the customer assigned to the tasks.  This matrix was created with the previous requirements:

 

  High Value Medium Value Low Value
High Risk Custom Reoccurrence Measurements can be Used when Creating Tasks (3)
Custom Reoccurrence Measurement Values can be Incremented on the Fly (3)
Custom Reoccurrence Measurements can be Managed (3)
Multiple Custom Reoccurrence Measurements can Map to One Value (2)
Tasks can be Viewed in a Calendar (3)
Medium Risk A Progression Value is Tracked for Each Task (3)
Reminders can be Setup to Notify the User of a Task (3)
 
Low Risk Tasks can be Saved (1)
Tasks can be Opened (1)
Tasks can be Deleted by the Delete Key (1)
Task Properties can be Edited (2)

From this, we can see we have a lot more risk than we did previously.  From this, the users determined that the "Tasks can be Viewed in a Calendar" option will be cancelled, at least for now.  We also determined that for the first iteration, we will use the following stories:

  • Tasks can be Saved
  • Tasks can be Opened
  • Tasks can be Deleted by the Delete Key
  • Task Properties can be Edited

This will be the first iteration period that we will progress to.

Also, from the estimates above, it will take about 25 weeks to complete, including the testing time.  I have a feeling this is an inaccurate estimate, but do not adjust it at the present time.  You calculate this value based upon the 1,2,3's above (1,2,3 weeks are the actual values), and from this, you estimate how long it will take.  However, as you work on the project, the project manager edits the timelines constantly to reflect how the team is actually working, slowly making it a more complete picture of the application.   You will see this as we progress with this application.

Posted by bmains | with no comments
Filed under:
More Posts Next page »