2010-11-23 4 views
7

Ich habe diese statische Hilfsfunktion:Wie kann ich Einheiten testen, die VisualTreeHelper verwenden?

public static DependencyObject GetParentObject(DependencyObject child) 
    { 
     if (child == null) return null; 
     ContentElement contentElement = child as ContentElement; 

     if (contentElement != null) 
     { 
      var parent = ContentOperations.GetParent(contentElement); 
      if (parent != null) return parent; 

      var fce = contentElement as FrameworkContentElement; 
      return fce != null ? fce.Parent : null; 
     } 

     //if it's not a ContentElement, rely on VisualTreeHelper 
     return VisualTreeHelper.GetParent(child); 
    } 

Es in einer realen Anwendung funktioniert, aber ich versuche, einige Unit-Tests für sie zu schreiben. Hier ist mein erster Versuch:

[Test] 
    public void GetParentObject_returns_immediate_parent() 
    { 
     var contentControl = new ContentControl(); 
     var textBox = new TextBox(); 

     contentControl.BeginInit(); 
     contentControl.Content = textBox; 
     contentControl.EndInit(); 

     var result = UIHelper.GetParentObject(textBox); 
     Assert.AreSame(contentControl, result); 
    } 

Leider scheitert es daran, dass VisualTreeHelper null zurückkehrt. Wie kann ich einen visuellen Baum nachahmen, der funktioniert?

Antwort

2

Aus diesem Grund ist die Statik problematisch.

Sie können die Funktionalität hinter einer Schnittstelle abstrahieren und eine Standardimplementierung erstellen, die die statische Methode verwendet. Sie können dann die Abhängigkeitsinjektion verwenden, wodurch dieser Komponententest trivial wird. Machen Sie sich die Abhängigkeit von IVisualTreeHelper zunutze oder rollen Sie Ihre eigene Stub-Implementierung, die Sie so konfigurieren können, dass alle von Ihnen zugewiesenen Werte zurückgegeben werden.

public class Foo 
{ 
    static IVisualTreeHelper visualTreeHelper; 

    static Foo() 
    { 
     Foo.visualTreeHelper = new FrameworkVisualTreeHelper(); 
    } 

    public Foo(IVisualTreeHelper visualTreeHelper) 
    { 
     Foo.visualTreeHelper = visualTreeHelper; 
    } 

    public static DependencyObject GetParentObject(DependencyObject child) 
    { 
     if (child == null) return null; 
     ContentElement contentElement = child as ContentElement; 

     if (contentElement != null) 
     { 
      var parent = ContentOperations.GetParent(contentElement); 
      if (parent != null) return parent; 

      var fce = contentElement as FrameworkContentElement; 
      return fce != null ? fce.Parent : null; 
     } 

     //if it's not a ContentElement, rely on the IVisualTreeHelper 
     return visualTreeHelper.GetParent(child); 
    } 
} 

public interface IVisualTreeHelper 
{ 
    DependencyObject GetParent(DependencyObject reference); 
} 

public class FrameworkVisualTreeHelper : IVisualTreeHelper 
{ 
    public DependencyObject GetParent(DependencyObject reference) 
    { 
     return VisualTreeHelper.GetParent(reference); 
    } 
} 

Offensichtlich können Sie andere VisualTreeHelper Methoden zu Ihrer Schnittstelle und Standardimplementierung hinzufügen müssen, wenn Sie andere Methoden an anderer Stelle verwenden sind.

Es ist immer noch nicht vollständig sauber, weil die Einheit, die Sie testen, selbst statisch ist, und Sie werden genau dasselbe Problem bekommen, wenn Sie versuchen, jede Klasse zu testen, die auf den statischen Methoden Ihrer UIHelper-Klasse beruht .

-1

Um einen visuellen Baum zu verspotten, müssen Sie einen erstellen und rendern. Sie müssen also ein tatsächliches Fenster erstellen, das für einen Komponententest nicht besonders geeignet ist.

2

Basierend auf dieser Antwort hier auf printing documents via Wpf-controls and convert to XPS kam ich mit der folgenden Erweiterungsmethode, um den visuellen Baum zu erstellen. Es funktioniert gut in NUnit ohne STA-Thread oder irgendetwas.

/// <summary> 
/// Render a UIElement such that the visual tree is generated, 
/// without actually displaying the UIElement 
/// anywhere 
/// </summary> 
public static void CreateVisualTree(this UIElement element) 
{ 
    var fixedDoc = new FixedDocument(); 
    var pageContent = new PageContent(); 
    var fixedPage = new FixedPage(); 
    fixedPage.Children.Add(element); 
    pageContent.ToMaybeOf<IAddChild>().Do(c => c.AddChild(fixedPage)); 
    fixedDoc.Pages.Add(pageContent); 

    var f = new XpsSerializerFactory(); 
    var w = f.CreateSerializerWriter(new MemoryStream()); 
    w.Write(fixedDoc); 
} 

Bitte beachten Sie, dass

  • die andere Antwort eine API des reach-DLL verwendet, die nicht wie die API sehen ich sehen werde. Ich gehe davon aus, dass es Unterschiede zwischen .NET Framework-Versionen 3.5 und 4.0
  • die ToMaybeOf Sachen im Grunde bedeutet pageContent als IAddChild zu behandeln und auf dieser Schnittstelle
  • dies eine Aktion tun wird nicht Arbeit mit einem Element des Typs Fenster da das Element im Wesentlichen als Kind zu einem Visual hinzugefügt wird und Window sich darüber bitter beschweren wird.
+0

Ich warf 'pageContent' zu' IAddChild' und ausgeführt, um den Betrieb direkt, anstatt sich auf die 'ToMaybeOf' verlassen und' Do': '((IAddChild) pagecontent) .AddChild (FixedPage);' –