Как я могу модульно протестировать то, что использует VisualTreeHelper? - PullRequest
8 голосов
/ 23 ноября 2010

У меня есть эта статическая вспомогательная функция:

    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);
    }

Она работает в реальном приложении, но я пытаюсь написать для нее несколько модульных тестов.Вот моя первая попытка:

    [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);
    }

К сожалению, это не удается, потому что VisualTreeHelper возвращает ноль.Как я могу создать визуальное дерево, которое будет работать?

Ответы [ 3 ]

3 голосов
/ 01 марта 2012

На основе этого ответа здесь печать документов через Wpf-control и преобразование в XPS Я придумал следующий метод расширения для создания визуального дерева.Он хорошо работает в NUnit без STA-потока или чего-либо еще.

/// <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);
}

Обратите внимание, что

  • другой ответ использует API-интерфейс Reach-dll, который не похож наAPI я вижу.Я предполагаю, что между версиями .NEt Framework 3.5 и 4.0
  • * *1010* есть ToMaybeOf, что в основном означает обработку pageContent как IAddChild и выполнение действий на этом интерфейсе
  • this не будет работать с элементом типа Window , поскольку этот элемент по существу добавляется в качестве дочернего элемента к визуалу, и Window будет горько жаловаться на это.
2 голосов
/ 23 ноября 2010

Вот почему статика проблематична.

Вы можете абстрагировать функциональность интерфейса и создать реализацию по умолчанию, которая использует статический метод. Затем вы можете использовать внедрение зависимостей, что делает этот модульный тест тривиальным - смоделируйте зависимость от IVisualTreeHelper или откатите свою собственную реализацию-заглушку, которую вы можете настроить для возврата любого назначенного вами значения.

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);
    }
}

Очевидно, что вам может потребоваться добавить другие методы VisualTreeHelper в ваш интерфейс и реализацию по умолчанию, если вы используете другие методы в других местах.

Это все еще не совсем чисто, потому что тестируемый модуль сам по себе является статическим, и вы столкнетесь с точно такой же проблемой, когда попытаетесь выполнить модульное тестирование любого класса, который использует статические методы вашего класса UIHelper.

0 голосов
/ 23 ноября 2010

Чтобы смоделировать визуальное дерево, вам придется его создать и визуализировать. Таким образом, вам придется создать реальное окно, которое не идеально подходит для юнит-теста.

...