在虚拟机中,使用数据绑定将视图连接到ViewModel是很正常的。
因此,如果在数据绑定到的某个Model对象上更改了属性的名称,则不会出现编译器错误。
但是,当编译器无法阻止bug时,我想到的下一件事就是“UnitTest”。
如何在不花费时间编写图形用户界面测试的情况下对其进行单元测试?
是否有一个系统可以检查绑定到的所有属性是否有效(而不必运行UI),以便我可以在单元测试中调用?
我正在寻找一些东西,它将获取视图,然后循环遍历所有控件,对于每个WPF控件,它将查看所有绑定并检查它们是否有效。
顺便说一句,这里有一些关于如何使OnPropertyChanged安全和/或如何测试它的好问题(但这些问题都是在WPF视图级别上完成的)。
我在这个问题上悬赏,因为一定有人仔细考虑过这个问题并提出了解决方案。
发布于 2010-10-06 21:40:50
我想我已经想出了一些方法,可以使用简单的反射,并修改我过去使用过的一些代码( FindBindingsRecursively
方法的代码是由Martin Bennedik的Enterprise WPF Validation Control编写的):
[TestMethod]
public void CheckWpfBindingsAreValid()
{
// instansiate the xaml view and set DataContext
var yourView = new YourView();
yourView.DataContext = YourViewModel;
FindBindingsRecursively(yourView,
delegate(FrameworkElement element, Binding binding, DependencyProperty dp)
{
var type = yourView.DataContext.GetType();
// check that each part of binding valid via reflection
foreach (string prop in binding.Path.Path.Split('.'))
{
PropertyInfo info = type.GetProperty(prop);
Assert.IsNotNull(info);
type = info.PropertyType;
}
});
}
private delegate void FoundBindingCallbackDelegate(FrameworkElement element, Binding binding, DependencyProperty dp);
private void FindBindingsRecursively(DependencyObject element, FoundBindingCallbackDelegate callbackDelegate)
{
// See if we should display the errors on this element
MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Static |
BindingFlags.Public |
BindingFlags.FlattenHierarchy);
foreach (MemberInfo member in members)
{
DependencyProperty dp = null;
// Check to see if the field or property we were given is a dependency property
if (member.MemberType == MemberTypes.Field)
{
FieldInfo field = (FieldInfo)member;
if (typeof(DependencyProperty).IsAssignableFrom(field.FieldType))
{
dp = (DependencyProperty)field.GetValue(element);
}
}
else if (member.MemberType == MemberTypes.Property)
{
PropertyInfo prop = (PropertyInfo)member;
if (typeof(DependencyProperty).IsAssignableFrom(prop.PropertyType))
{
dp = (DependencyProperty)prop.GetValue(element, null);
}
}
if (dp != null)
{
// Awesome, we have a dependency property. does it have a binding? If yes, is it bound to the property we're interested in?
Binding bb = BindingOperations.GetBinding(element, dp);
if (bb != null)
{
// This element has a DependencyProperty that we know of that is bound to the property we're interested in.
// Now we just tell the callback and the caller will handle it.
if (element is FrameworkElement)
{
callbackDelegate((FrameworkElement)element, bb, dp);
}
}
}
}
// Now, recurse through any child elements
if (element is FrameworkElement || element is FrameworkContentElement)
{
foreach (object childElement in LogicalTreeHelper.GetChildren(element))
{
if (childElement is DependencyObject)
{
FindBindingsRecursively((DependencyObject)childElement, callbackDelegate);
}
}
}
}
发布于 2010-02-18 22:01:47
真是个好问题。投票支持它。我也想知道答案。
我所知道的最佳实践之一(suggested by Josh Smith,感谢Gishu指出这一点)是让基视图模型类在OnPropertyChanged()
方法中检查属性是否真正存在。例如:
abstract class ViewModelBase
{
[Conditional("DEBUG")]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
if (this.ThrowOnInvalidPropertyName)
throw new ArgumentException(propertyName);
string msg = "Invalid property name: " + propertyName;
Debug.Fail(msg);
}
}
protected void OnPropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
但是这并不能帮助您发现XAML中的拼写问题。嗯..。我不知道任何现有的解决方案。也许WPF Disciples的人可以提点建议。我想我应该使用PresentationTraceSources.DataBindingSource并添加到他的TextWriterTraceListener的Listners
集合实例中,然后监视输出。一旦我们的雷达上出现错误或警告,我们就应该不能通过测试。
找到了很好的例子:WPF Snippet - Detecting Binding Errors
希望这能有所帮助。至少有一点:)。
干杯,安瓦卡。
发布于 2010-10-06 03:45:50
我知道这不是对你问题的直接回答。
如果你知道你期望绑定的控件元素的名称,你可以做一些类似下面的测试(使用nunit)。这是一个粗略的版本。但在这里,您使用表达式并显式测试该属性是否在绑定中
[Test]
public void TestBindings()
{
TestBinding<IndividualSolutionViewModel, string>(x => x.Name, "Name", TextBlock.TextProperty);
}
private void TestBinding<TViewModel,TResult>(Expression<Func<TViewModel, TResult>> property, string elementName,
DependencyProperty dependencyProperty)
{
string memberName = ExpressionHelper.GetPropertyName(property); // f.ex v => v.Name will return Name
TestBinding(memberName, elementName, dependencyProperty);
}
private void TestBinding(string memberName, string elementInControlName, DependencyProperty dependencyProperty)
{
//object viewModel = new IndividualSolutionViewModel();
var view = new IndividualSolutionView();
//Assert.That(view.DataContext, Is.EqualTo(viewModel));
var element = view.FindName(elementInControlName);
Assert.That(element, Is.Not.Null, string.Format("Unable to find the element {0} in view {1}", elementInControlName, view.Name));
Assert.That(element, Is.InstanceOf(typeof(DependencyObject)));
var binding = BindingOperations.GetBinding(element as DependencyObject, dependencyProperty);
Assert.That(binding, Is.Not.Null, string.Format("Could not find a binding for the control {0}", elementInControlName));
Assert.That(binding.Path.Path, Is.EqualTo(memberName));
}
Ps。您必须将此代码添加到app.config中
<configSections>
<sectionGroup name="NUnit">
<section type="System.Configuration.NameValueSectionHandler"
name="TestRunner"></section>
</sectionGroup>
</configSections>
<NUnit>
<TestRunner>
<add value="STA" key="ApartmentState"></add>
</TestRunner>
</NUnit>
https://stackoverflow.com/questions/2288765
复制相似问题