Как протестировать класс, который реализует IApplicationContextAware? - CodeHelper

Как протестировать класс, который реализует IApplicationContextAware?

1

Собственно, у меня есть класс презентера, который из ApplicationContext вытаскивает пару других (подчиненных) презентеров. Мне бы не хотелось в конфигурации родительского презентера сразу прописывать эти дочерние призентеры, т.к. при инициализации презентера создается окно.

Alexander

Возникает соблазн для дочерних презентеров написать что-то вот такое:

    private IChildPresenter childPresenter;
    public IChildPresenter ChildPresenter
    {
        get
        {
            if (childPresenter == null)
                childPresenter = ApplicationContext.GetInstance<IChildPresenter>();
            return childPresenter;
        }
        set { childPresenter = value; }
    }
Alexander

Тогда при тестировании можно заглушку вставить будет. Но это как-то... не по-людски...

p.s. 500 символов для комментария - маловато.

Лучший ответ:

2

Можно вместо объекта ApplicationContext подсунуть тестируемому классу заглушку и в методе теста настраивать заглушку на соответствующие ожидания. Например что-то в этом роде:

[TestFixture]
public class PresenterTests 
{
    private Presenter _presenter;            1

    private IApplicationContext _context;    2

    private MockRepository _mockRepository;

    [SetUp]
    public void Init()
    {
        _mockRepository = new MockRepository();
        _presenter = new Presenter();              3
        _context = _mockRepository.DynamicMock();  4
        _presenter.ApplicationContext = _context;  5
    }

    [Test]
    public SomeMetod_SomeFacts_SomeExpectations()  6
    {
        _context.Expect(c => c.GetObject("firstObjectId")).Return(fakeFirstObject);
        _context.Expect(c => c.GetObject("firstSecondId")).Return(fakeSecondObject);

        // Логика теста
    }
}

Класс теста хранит ссылку на тестируемый объект 1 и на заглушку контекста 2. Перед каждым тестом объект presenter создается заново 3, а контекст создается как заглушка 4 (в примере Rhino Mocks). Потом, в presenter передается созданный контекст-заглушка 5. В конкретном тесте 6 нам нужно только настроить заглушку контекста так, чтобы по запросу объектов она возвращала какие-нибудь реализации. Это тоже могут быть заглушки Rhino Mocks или собственные mock/stub-классы.

Alexander

Посоветуй какую-нибудь хорошую статью про заглушки и ожидания. А то я "плаваю".

admax

Вот есть на хабре статья

safonovea

Только в пункте 4 вместо Mock объекта наверное стоит использовать Stub (если конечно в тесте не требуется удостовериться в том, что будет вызван какой либо метод Mock объекта)

Alexander

Начал использовать Moq вместо RinoMock. Показалось удобнее.

Новые ответы


1

Техника приведенная в комментариях к вопросу тоже имеет право на существование.

private IChildPresenter childPresenter;

public IChildPresenter ChildPresenter
{
    get
    {
        if (childPresenter == null)
            childPresenter = ApplicationContext.GetInstance<IChildPresenter>();
        return childPresenter;
    }
    set { childPresenter = value; }
}

То есть если зависимость определена — возвращаем её, в противном случае — достаем зависимость из ioc-контейнера (который в этом случае работает как сервис-локатор). Особенно часто такая техника применяется для объектов, которым нельзя подсунуть зависимости во время выполнения программы, но можно во время тестирования. Классический пример — инъекция зависимостей в атрибуты. В runtime атрибуты создаются автоматически и нет возможности инъекции собственных зависимостей, а в классе теста можно легко самому инстанцировать объект атрибута и передать ему все, что нужно. Поэтому атрибуту делают 2 конструктора — один без параметров, а другой со всеми нужными зависимостями. Причем во время выполнения, если атрибуту не задана какая-то зависимость, то он сам «лезет» за ней в ioc-контейнер.

Вот пример:

public class MyAttribute : Attribute
{
    private Dependency _dependency;

    // Этот конструктор используется в runtime
    public MyAttribute(){} 

    // Этот конструктор используем мы сами во время тестирования
    public MyAttribute(Dependency dependency)
    {
        _dependency = dependency;
    }

    protected Dependency
    {
        get
        {
            // Если зависимость не задана, то дастаем ее из контейнера
            if (_dependency == null)
                return ServiceLocator.Get<Dependency>()
            return _dependency;
        }
    }
}
Alexander

Мало, чего понял :)

Чак Норрис

Написал пояснительный пример)

Alexander

Не, техника-то понятна, непонятно - где и как это применяется. Да это сейчас и неважно.


v1.7.123.556
© 2009—2010 CodeHelper FAQ | О сайте | Обратная связь | История изменений | Статьи
Creative Commons LicenseМатериалы сайта распространяются под лицензией Creative Commons Attribution-Share Alike 3.0 Unported.