在我的应用程序中,我使用自定义UIElement
,它处理自己的布局和渲染。虽然我可以对其中的大部分进行单元测试,但我无法正确地对渲染进行单元测试。原因是渲染是通过OnRender方法完成的,但是我没办法测试实际渲染的内容,因为DrawingContext是一个带有内部构造函数的抽象类,所以我不能从它派生出来进行测试目的。
我知道如何做的唯一测试是尝试不同的场景(基于实现的代码)并检查没有抛出异常。有没有办法测试更多(除了使用TypeMock Isolator或JustMock)?
答案 0 :(得分:5)
我在这个帖子中写了答案:unit testing custom OnRender-Method
但是我仍然将答案复制到这个帖子中(有人可能会删除它,如果不需要的话)。
解决方案是,从DrawingContext
创建DrawingGroup
。
public class TestingMyControl : MyControl
{
public DrawingGroup Render()
{
var drawingGroup = new DrawingGroup();
using (var drawingContext = drawingGroup.Open())
{
base.OnRender(drawingContext);
}
return drawingGroup;
}
}
所以夹具看起来像:
[Test]
public void Should_render()
{
var controlToTest = new TestingMyControl();
var drawingGroup = controlToTest.Render();
var drawing = drawingGroup.Children[0] as GeometryDrawing;
Assert.That(drawing.Brush, Is.EqualTo(Brushes.Black));
Assert.That(drawing.Pen.Brush, Is.EqualTo(Brushes.SeaGreen));
Assert.That(drawing.Pen.Thickness, Is.EqualTo(0.6));
Assert.That(drawing.Bounds.X, Is.EqualTo(5));
Assert.That(drawing.Bounds.Y, Is.EqualTo(15));
Assert.That(drawing.Bounds.Width, Is.EqualTo(25));
Assert.That(drawing.Bounds.Height, Is.EqualTo(35));
}
这需要以下生产代码:
public class MyControl : Canvas
{
protected override void OnRender(DrawingContext dc)
{
dc.DrawRectangle(Brushes.Black, new Pen(Brushes.SeaGreen, 0.6), new Rect(5, 15, 25, 35));
}
}
答案 1 :(得分:0)
不幸的是,您必须致电InvalidateVisual
,在内部调用InvalidateArrange
。 OnRender方法作为排列阶段的一部分被调用,因此您需要告诉WPF重新排列控件(InvalidateArrange会这样做)并且需要重绘(InvalidateVisual会这样做)。
FrameworkPropertyMetadata.AffectsRende
r选项只是告诉WPF在关联属性更改时调用InvalidateVisual
。
如果你有一个控件(让我们调用这个MainControl)覆盖OnRender
并包含几个后代控件,那么调用InvalidateVisual
可能需要重新安排后代控件,甚至重新测量。但我相信WPF已经进行了优化,以防止后代控件在可用空间不变的情况下重新排列。
您可以通过将渲染逻辑移动到单独的控件(比如NestedControl)来解决这个问题,该控件将是MainControl的可视子级。 MainControl可以将其作为可视子项自动添加或作为其ControlMmplate的一部分添加,但它需要是z顺序中的最低子项。然后,您可以在MainControl上公开一个InvalidateNestedControl类型方法,该方法将在NestedControl上调用InvalidateVisual。
这就是我的所作所为。为了测试这个,我创建了这个子类......
public class TestPanel : DockPanel
{
protected override Size MeasureOverride(Size constraint)
{
System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
return base.MeasureOverride(constraint);
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
return base.ArrangeOverride(arrangeSize);
}
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
System.Console.WriteLine("OnRender called for " + this.Name + ".");
base.OnRender(dc);
}
}
...我这样布局(注意它们是嵌套的):
<Button Content="Test" Click="Button_Click" DockPanel.Dock="Top" HorizontalAlignment="Left" />
<l:TestPanel x:Name="InnerPanel" Background="Red" Margin="16" />
当我调整窗口大小时,我得到了这个......
MeasureOverride调用MainTestPanel。 MeasureOverride调用了InnerPanel。 ArrangeOverride调用MainTestPanel。 ArrangeOverride调用了InnerPanel。 OnRender呼吁InnerPanel。 OnRender呼吁MainTestPanel。 但是当我在&#39; MainTestPanel&#39;上调用InvalidateVisual时(在按钮&#39; Click&#39;事件中),我得到了这个......
为MainTestPanel调用了ArrangeOverride。 OnRender呼吁MainTestPanel。 注意没有调用任何测量覆盖,只调用外部控件的ArrangeOverride。
它并不完美,好像你的子类中的ArrangeOverride(我们不幸的是,我们这样做)中的计算非常繁重,仍然会被(重新)执行,但至少孩子们不会堕入同样的命运。
但是,如果您知道没有任何子控件具有设置了AffectsParentArrange位的属性(我们这样做了),您可以更好地使用Nullable Size作为标志来抑制ArrangeOverride逻辑除非需要,否则请进入...
public class TestPanel : DockPanel
{
Size? arrangeResult;
protected override Size MeasureOverride(Size constraint)
{
arrangeResult = null;
System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
return base.MeasureOverride(constraint);
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
if(!arrangeResult.HasValue)
{
System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
// Do your arrange work here
arrangeResult = base.ArrangeOverride(arrangeSize);
}
return arrangeResult.Value;
}
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
System.Console.WriteLine("OnRender called for " + this.Name + ".");
base.OnRender(dc);
}
}
现在除非特别需要重新执行排列逻辑(作为对MeasureOverride的调用),否则只能获得OnRender,如果要显式强制使用排列逻辑,只需将大小调出,调用InvalidateVisual。