Friday, February 10, 2012

Automatic Property Testing

Suppose you have a class that looks like this:

public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}

private string _Phone;

public string Phone
{
get
{
return _Phone;
}
set
{
if (_Phone != value)
{
_Name = value;
OnPropertyChanged("Phone");
}
}
}

private string _Name;

public string Name
{
get
{
return _Name;
}
set
{
if (_Name != value)
{
_Name = value;
}
}
}
}


It’s a simple person class with Name and Phone properties. Because it implements INotifyPropertyChanged, the setter for every property should raise the PropertyChanged event. However, there are two bugs in this class:




  • The setter for Phone sets _Name rather than _Phone.


  • The developer forgot to raise PropertyChanged in the setter for Name.



We want to write some unit tests for this class. We need at least two tests for every property: one ensuring the value stored to the property is the one read from it and one ensuring that storing a value raises PropertyChanged. Here’s an example of the tests for Phone:



private string changedProperty;

public void PropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
changedProperty = e.PropertyName;
}

[TestMethod()]
public void Phone_ValueTest()
{
Person target = new Person();
string expected = "555-555-5555";
target.Phone = expected;
string actual = target.Phone;
Assert.AreEqual(expected, actual);
}

[TestMethod()]
public void Phone_RaisesPropertyChanged()
{
Person target = new Person();
target.PropertyChanged += new
PropertyChangedEventHandler(PropertyChanged);
target.Phone = "555-555-5555";
string actual = changedProperty;
string expected = "Phone";
Assert.AreEqual(expected, actual);
}


As you can guess, it’s quickly going to get tedious writing these tests, especially if you have a lot of properties. Let’s automate this.



ClassTester is a project on CodePlex (http://classtester.codeplex.com) that automatically tests every property to ensure the value written is the value read and, if the class implements INotifyPropertyChanged, writing to the property raises PropertyChanged. Using ClassTester couldn’t be easier: replace all of the property test methods with this one simple method:



[TestMethod()]
public void AutomaticPropertyChangedTest()
{
PropertyTester tester = new PropertyTester(new Person());
tester.TestProperties();
}


Nice and (almost) generic: we can use this test forevery class; the only change we have to make is the name of the class to test.



When run, this test fails with the error “The get value of the 'Phone' property on the type 'AutomaticPropertyTesting.Person' did not equal the set value”. That points out the first bug, so we fix that by changing “_Name” to “_Phone” in the setter for Phone. (If this seems far-fetched, I actually ran into that today; the hazards of copy and paste coding!) Running the test again still gives a failure: “The property 'Name' on the type 'AutomaticPropertyTesting.Person' did not throw a PropertyChangedEvent”. Adding OnPropertyChanged(“Phone”) to the setter for Name doesn’t fix the problem; only the correct OnPropertyChanged(“Name”) allows the test to pass.



Let’s make the class a little more complex by adding an ID property that’s automatically set to a new Guid value when the class is instantiated. Note that the setter is private so only this class can change the value, such as when a person is loaded from a database, and that it doesn’t raise PropertyChanged because of this.



private Guid _id = Guid.NewGuid();

public
Guid ID
{
get
{
return _id;
}

private set
{
_id = value;
}
}

Now the test fails again: “The property 'ID' on the type 'AutomaticPropertyTesting.Person' did not throw a PropertyChangedEvent”. Fortunately, ClassTester has a mechanism to ignore certain properties: you can add this to the test method:

tester.IgnoredProperties.Add("ID");


Of course, you’d have to do that for every property you don’t want automatically tested (not only for this reason, but some others described on the Codeplex site, such as when a property’s type is an interface or a class with a non-default constructor). That can get a little tedious too. So, I created a couple of classes to further automate this.



The first class is a new attribute:



public class DontAutomaticallyTestAttribute : Attribute {}


The second class is a façade for PropertyTester. What it brings to the table is automatically adding properties marked with the DontAutomaticallyTest attribute to the IgnoredProperties list.



public class PropertyTest
{
private object _toBeTested;

public PropertyTest(object toBeTested)
{
_toBeTested = toBeTested;
}

public void TestProperties()
{
PropertyTester tester = new PropertyTester(_toBeTested);
PropertyInfo[] props = _toBeTested.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
if (DontTest(prop))
{
tester.IgnoredProperties.Add(prop.Name);
}
}
tester.TestProperties();
}

private bool DontTest(PropertyInfo property)
{
return property.GetCustomAttributes(
typeof(DontAutomaticallyTestAttribute), false).Length > 0;
}
}


Now you change the line of code in AutomaticPropertyChangedTest to use the new class:



PropertyTest tester = new PropertyTest(new Person());



and add the DontAutomaticallyTest attribute to those properties you don’t want automatically tested (you will write manual tests, won’t you?):



[DontAutomaticallyTest]
public Guid ID


Of course, you also have to add a reference to the project holding these two classes to both the project for your classes and your test project. However, for a couple of minutes work, you can now save tons of time creating tests for all the properties in your classes.

Sunday, February 05, 2012

Updating the VFPX ReportBuilder.APP

There are a couple of bugs in the VFP 9 Service Pack 2 version of ReportBuilder.APP, which provides the Report Designer dialogs and event handlers. I discussed the first bug, which causes the Printer Environment setting for a report to be turned on when you click the font button in the Field or Label Properties dialogs, in an earlier blog post. The other is a small one: using code like:

do (_reportbuilder) with 3, "TablePath.DBF"

to specify a custom registry table causes DELETED to be set off. The fix for this is simple: save the current setting of DELETED in FRXBuilder.PRG before setting it off and restore the setting near the end.

Several people suggested I actually implement these fixes in the copy of ReportBuilder.APP (and the corresponding source code) available on VFPX, so I did so today.