다음을 통해 공유


Unit Testing Dependency Properties with VSTT, a first stab

I've been playing around with WPF recently, and one of the things I've ended up doing is creating a set of custom controls (rather than User controls), as well as what I will call 'aggregation' classes[1]. Some of these have had a fair chunk of code that has been in need of unit testing. We don't want to end up down the path of testing everything through the UI, which has notoriously difficult with UI code in the past. With WPF it's a lot easier due to the wonderful separation of church and state (Look n feel vs logic).

When I started down this path, I went along my merry way creating [TestMethod]'s, and running them one-at a time. In conjunction with [TestInitialize], they were all passing wonderfully. "Great!" I thought, "lets run them all together and see lots of green....". Uh oh. All but the first failed. Had I really written that bad a custom control? (Being me, that's perfectly possible).

When I saw the error message, I knew what exactly what was going on:
Test method Foo.RendererIsCleared threw exception: System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it..

With a call stack that looks like:
System.Windows.Threading.Dispatcher.VerifyAccess()
System.Windows.DependencyObject.GetValue(DependencyProperty dp)
FooBar.get_Thingy()
FooBar.ThingyChanged()
FooBar.Doohick_PropertyChanged(Object sender, PropertyChangedEventArgs e)
System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)

This is clear -- dependency properties don't support MTA scenarios (duh), which is a bit of a problem when each and every [TestMethod] is executed on a different thread. TestInitialize & first test executed are run on the same thread, and the last thread, and [ClassCleanup] are run on the last thread. This, you can see causes somewhat of a problem. How can we work around this?

My first attempt was 'ah-ha. Dispatcher allows you to invoke methods on the dispatcher thread, and thus I can get them to execute on the same thread they were created on'. Sadly this doesn't work. The thread that the DP's are created on (on the [TestInitialize]'s thread), has actually gone away. So, since you need to invoke on the thread the DP was created on... oops. It's gone away. This is a bit of a problem.

My 'quick fix' solution is to code up all the [TestMethod]s in the test class as normal, but mark them with [Ignore], so they can't be run. Then, create one [TestMethod] that is Called 'AllTests'. This method will then call all the other [TestMethod]s, and ensure they are all called on the same thread. To make this really work you should call initialize & cleanup before and after. I did the following pattern:

public delegate void TestDelegate();

public void DoTest(TestDelegate test)
{
TestInitialize();
test();
TestCleanup();
}

With my AllTests method looking like:

[TestMethod]
public void RunAllWatermarkingTextboxTests()
{
DoTest(WatermarkProperty);
DoTest(ResetOnFocusLossPropertySettingGetting);
DoTest(HasInputtedTextProperty);
DoTest(PreviousTextPropertyDefaultValue);
DoTest(LostFocusEventing);
DoTest(ResetOnFocusLossBehaviour);
}

But this doesn't completely solve it...
You loose out on [ExpectedException], data driven, etc. This is not particularly good, but this is a good start. Additionally, the 'investigation' can be tiresome since you no-longer get the actual test that failed, just the call stack, so you have to do the leg work (Made a little bit easier thanks to the clickable call stack in Orcas).

The solution here is to build a more complex base class to do all the init on a thread that isn't going to die. When I get some time to do that, I'll make sure I post it here. :)

[1] We have a chunk of code that is 'old school' and while we've brought it forward with INotifyPropertyChanged/INotifyCollectionChanged, it's not quite there yet, and we really want to do some rich binding with dependency objects, so I ended up creating a class which aggregates some events into some house keeping code & dependency properties. It's been very powerful so far.

Comments

  • Anonymous
    September 21, 2007
    Hosam Kamel on Get up to speed on Visual Studio 2008. The US ISV Developer Evangelism Team Blog on Hierarchical...