Страницы

вторник, 27 марта 2012 г.

Эффективное тестирование. Скажи нет мock’ам – дополнение о скорости.

  Предыдущее сообщение  заканчивал грустными строками:

“Но чтобы быть честным, упомяну о еще одном параметре – время выполнения тестов. Тест с mock-объектами выполняется примерно 110 мс, а тест с инфраструктурой и заглушками примерно 950 мс (+ 2000мс на инициализацию NHibernate, но это время может быть размазано время тестов всех тестов). Да, время увеличилось, но увеличилось приемлемо”.

  Это меня печалило. Не хотелось верить, что NHibernate работает настолько медленно. И к счастью, дело обстоит не совсем так. Что включает в себя время теста? Это время на загрузку сборок и jit-компиляцию. А NHibernate содержит по-настоящему много кода. Поэтому я решил провесит небольшой эксперимент. Скопировать, и дважды выполнить каждый из тестов: с mock-объектами и без них. И вот что из этого получилось:

image

  Совсем другое дело! На повтором прогоне тест с NHibernate стабильно обгоняет тест с Moq на 2-3 миллисекунды! Что это значит? Это значит, что кроме первого теста, который будет инициализировать и компилировать NHibernate, остальные будут работать со скоростью модульных тестов. Последняя причина использовать mock-объекта окончательно растворилась.

воскресенье, 25 марта 2012 г.

Эффективное тестирование. Скажи нет мock’ам – NHibernate нам в помощь.

  В прошлый раз я завел часы (IClock) в тестовый проект, и заставил их исправно работать в пользу тестов. Теперь время за вторым объектом инфраструктуры – NHibernate в лице ISessionFactory. Кто читал мои сообщения про NHibernate и SQLite – наверняка знает, что я собираюсь предложить. NHibernate – это как раз пример весьма хорошей инфраструктуры, которая позволяет тестировать быстро.

  NHibernate = быстрые тесты доступа к базе данных.

  Я буду использовать вспомогательный класс из своей библиотеки Enhima - SQLiteInMemoryTestHelper. Он открывает сессии на открытом соединении SQLite in memory, поэтому сохранённые данные не будут потеряны. Кроме того, нам понадобится ввести дополнительный интерфейс, ISessionManager. Просто потому, что реализовывать полноценный ISessionFactory было бы слишком накладно. (Хотя можно подумать о Castle.DynamicProxy – но это сложнее, а избыточная сложность нам не к чему). ISessionManager включается в себя лишь два метода от ISessionFactory:

public interface ISessionManager
{
    ISession OpenSession();
    IStatelessSession OpenStatelessSession();
}
public class EnhimaSessionManager : ISessionManager
{
    private readonly SQLiteInMemorySchemaMaker _schemaMaker;
    public EnhimaSessionManager(SQLiteInMemorySchemaMaker schemaMaker)
    {
        _schemaMaker = schemaMaker;
    }
    public ISession OpenSession()
    {
        return _schemaMaker.OpenSession();
    }
    public IStatelessSession OpenStatelessSession()
    {
        return _schemaMaker.OpenStatelessSession();
    }
}

  Удалённые вызовы.


  Осталось поговорить о последний зависимости – IRateProvider. Как я и говорил раннее, IRateProvider – это удалённый сервис. Использовать удалённый сервис, даже специально предназначенный для тестирования не стоит как минимум по двум важнейшим причинам:



  • Детерминизм. Сеть может быть недоступена, на сервере проводятся регламентные работы. Тысячи причин по которым сервис может оказаться недоступен. И любая из них приведёт к ошибкам в тестах, хотя наш код по-прежнему работает.
  • Скорость. Удалённые вызовы всегда медленные, на 4-5 порядков медленнее внутрипроцессных вызовов. А полное тестирование системы как правило вызывает удалённые сервисы сотни раз. Тесты начинают занимать минуты - непозволительно долго.

  Поэтому выбора нет – необходимо написать заглушку. Не стоит пытаться сделать заглушку на интерфейс удалённой системы. Лучше сделать заглушку только на собственный интерфейс адаптера, и использовать во всех тестах времени разработки. При этом сам адаптер под реальную систему должен разрабатываться в отдельном проекте, и подключать к проекту уже готовую сборку адаптера. Степень сложности реализованной заглушки зависит от сложности задачи, и вполне возможно имеет смысл написать многорежимную заглушку: статический тестовый режим, и режим эмуляции реального сервиса. Почему нужен первый режим понятно думаю понятно – так проще тестировать. Чтобы понять, зачем нужен второй представим ситуацию, что мы показываем клиенту нашу систему, и внезапно, отключается сеть. Тогда у нас есть два варианта ответа:



  • Мы зайдём в следующий раз, когда сеть появится.
  • Сеть недоступна, но сейчас мы подключим эмулятор сервиса, и покажем работу системы.

  “Более лучший” ответ очевиден. В рамках моей задачи хватит и статической реализации теста. Правда, я люблю облегчать себе будущую работу, поэтому снабжу заглушку IRateProvider значением по-умолчанию:

public interface IRateProvider
{
    decimal GetRateOn(DateTime date);
}
public class RateProviderStub : IRateProvider
{
    public decimal Rate;
    public RateProviderStub()
    {
        Rate = 32.5m;
    }
    public decimal GetRateOn(DateTime date)
    {
        return Rate;
    }
}

  Собираем всё вместе.


  Теперь у нас всё есть для написания нового теста, основанного на использовании инфраструктуры и заглушек:

[Test]
public void Ensure_valid_prices_would_be_updated()
{
    var configuration = new Configuration().DataBaseIntegration(db => db.LogSqlInConsole = true );
    configuration.ConfigureSQLiteInMemory();
    configuration.MapEntities(From.ThisApplication());
    var testHelper = new SQLiteInMemoryTestHelper(configuration);
    testHelper.CreateSchema();
    var manager = new EnhimaSessionManager(testHelper);
    var today = Clock.FixedTime.Today;
    var yesterday = today.AddDays(-1);
    var priceToUpdate = new Price(10, yesterday) { ValidFrom = yesterday, ValidTo = today };
    var priceToSkip = new Price(20, yesterday) { ValidFrom = yesterday, ValidTo = yesterday };
    testHelper.Persist(priceToUpdate);
    testHelper.Persist(priceToSkip);
    var rateProviderStub = new RateProviderStub();
    var task = new UpdatePricesTask(manager, Clock.FixedTime, rateProviderStub);
    task.Run();
    var savedPriceToUpdate = testHelper.Load<Price>(priceToUpdate.Id);
    var savedPriceToSkip = testHelper.Load<Price>(priceToSkip.Id);
    savedPriceToUpdate.Satisfy(x =>
                                x.LocalAmount == 325m &&
                                x.LastUpdated == Clock.FixedTime.Today);
            
    savedPriceToSkip.Satisfy(x =>
                                x.LocalAmount == 0 &&
                                x.LastUpdated == yesterday);
}

  На мой вкус, так намного проще и понятнее, а значит быстрее в реализации и поддержке. Поэтому нет причин использовать mock-объекты. Но чтобы быть честным, упомяну о еще одном параметре – время выполнения тестов. Тест с mock-объектами выполняется примерно 110 мс, а тест с инфраструктурой и заглушками примерно 950 мс (+ 2000мс на инициализацию NHibernate, но это время может быть размазано время тестов всех тестов). Да, время увеличилось, но увеличилось приемлемо. Кроме того, увеличился и объём тестируемого кода, а значит потребуется меньше тестов. Всё это существенно повышает производительность труда.


  PS. Пока я писал полуинтеграционный тест – обнаружил ошибку в запросе, которую не мог обнаружить mock-тест. Улыбка

среда, 21 марта 2012 г.

Эффективное тестирование. Скажи нет мock’ам – инфраструктура спешит на помощь.

  В прошлый раз я показал, к чему может приводить разнузданное использование mock-объектов. Один вид теста вводит меня в перманентную депрессию, а так хочется радоваться жизни. Откуда взялись болевые точки, мерзкие флюиды негатива, и как от них избавиться читайте в этом сообщении Улыбка.

mock не принесут мне счастья.

   Тесты с использованием Mock-объектов всегда основаны на поведении объектов, и подсчитывают вызванные методы и проверяют их аргументы в процессе выполнения некоего действия. Взгляните еще раз на код теста. Львиную долю кода занимают установки разного рода ответов на вызовы методов зависимостей. Очевидно, что мы должны уменьшить количество вызываемых методов для упрощения теста. Основной способ – агрегирование сервисов, т.е. к примеру в нашем случае можно собрать вызовы к ISession, и спрятать за IRepository. Но такой подход противен резким раздроблением кода на сверхмелкие, не оправдывающие своего существования  классы (уменьшение cohesion). И  последующим адом зависимостей. Четыре строки NHibernate-вызовов не заслуживают того, чтобы поместить их в отдельный класс. Так если не использовать Mock-объекты (и поведенческое тестирования), то остаётся один выход: заглушки (Stub).

Заглушки могли бы, но…

  Принципиальная разница между использованием заглушек и mock-объектов в подходе: мы проверяем не поведение, а состояние. Такой подход намного проще и понятнее, однако требует своей цены: заглушки придётся писать вручную, что дорого и не слишком эффективно. Давайте подумаем еще немного: если писать заглушки дорого – значит надо тестировать систему с максимальным вовлечением реальных объектов, и использовать минимальное количество заглушек. Но некоторые из них всё равно слишком сложно и дорого написать (Одно определение интерфейса ISession содержит несколько десятков свойств и методов, не говоря уже о других элементах API вроде IQuery, ISqlQuery и т.д.).  Похоже мы пришли в тупик? Нет! мы забыли ключевой элемент системы – инфраструктура.

седлаем инфраструктуру.

  Все проекты можно разделить на два вида инфраструктурные и бизнес-проекты. Бизнес проекты всегда пишутся поверх инфраструктурных проектов, и активно их используют. Выбранная инфраструктура прямо влияет на то, как вы тестируете (или можете ли тестировать) приложения, ибо код полностью зависит от нее. Из этого вытекает следствие: правильно выбирайте, и/или пишите свою инфраструктуру. Хорошая инфраструктура – ключ к лёгкому и быстрому тестированию. Основная мысль в том, чтобы отказаться от чистого модульного тестирования, и перейти к полу-интеграционному тестированию: инфраструктура переведена в специальный режим для обеспечения скорости и простоты тестирования, а при развёртывании приложения переходит в рабочий режим.
  Вернёмся к нашему примеру. Взглянем, какие зависимости есть у UpdatePricesTask.
public UpdatePricesTask(ISessionFactory sessionFactory, IClock clock, IRateProvider rateProvider)

  Я классифицирую их так:




  • ISessionFactory и IClock – инфраструктурные класс.
  • IRateProvider – удалённый сервис.

  На ISessionFactory мы повлиять не можем, но IClock – полностью рукописный класс, и при его реализации мы обязаны проектировать его с мыслью о тестирование в голове. Вот так могла бы выглядеть простейшая реализация IClock:
public interface IClock
{
    DateTime Today {get;}
    
    DateTime Now {get;}
}
public class Clock : IClock
{
    public DateTime Today
    {
        get { return DateTime.Today;}
    }
    
    public DateTime Now 
    {
        get { return DateTime.Now;}
    }
}


  Готов поспорить, ваша первая реализация выглядела именно так (и моя первая тоже). Они отлично работают, но совершенно непригодны для тестирования. Исправить ситуация просто, давайте подумаем, что нам надо от часов?




  • Возможность работы в нормальном режиме.
  • Возможность работы в тестовом режиме: фиксировать и менять значение текущего времени и даты.
  • Делать предыдущих два пункта простым способом.


 



Не жалейте хороший код для себя



  Если с реальным режимом работы всё понятно, то над тестовым есть где раскинуть мозгом. Какой обычный сценарий в большинстве тестов? Вызвать операцию над некоторым объектом, и затем проверить, что в состоянии этого объекта присутствует  текущее время. Т.е. часы должны иметь возможность установки какого-либо “текущего” времени. Но я ленивый, поэтому хочу сделать немного больше – пусть часы сами фиксируют текущее время, и дают возможность его прочитать. Оставим слова, лучше ближе к коду:
public class Clock : IClock
{
    public enum ClockMode 
    {
        RealTime, 
        FixedTime
    }
    
    /// <summary>
    /// Current ClockMode
    /// </summary>
    public readonly ClockMode Mode;
    
    private readonly object _latch;
    private DateTime? _freezedValue;
    public static readonly Clock RealTime;
    public static readonly Clock FixedTime;
    static Clock()
    {
        RealTime = new Clock();
        FixedTime = new Clock(ClockMode.FixedTime);
    }
    /// <summary>
    /// Creates new instance of <see cref="Clock"/> using realtime mode. 
    /// </summary>
    public Clock() : this(ClockMode.RealTime) 
    {
    }
    /// <summary>
    /// Creates new instance of <see cref="Clock"/> using supplied mode. 
    /// </summary>
    public Clock(ClockMode mode)
    {
        Mode = mode;
        _latch = new object();
    }
    /// <summary>
    /// Returns current date.
    /// </summary>
    public DateTime Today
    {
        get { return GetNow().Date;}
    }
    
    /// <summary>
    /// Returns current date and time. 
    /// </summary>
    public DateTime Now 
    {
        get { return GetNow();}
    }
    
    /// <summary>
    /// Sets user supplied datetime as current time.
    /// </summary>
    public void SetCurrentTime(DateTime value)
    {
        SetFreezedValue(value);
    }
    /// <summary>
    /// Forces <see cref="Clock"/> to pick and fix current datetime at the next call to Now or Today properties.
    /// </summary>
    public void NextValue()
    {
        SetFreezedValue(null);
    }
    private DateTime GetNow()
    {
        if(Mode == ClockMode.RealTime) return DateTime.Now;
            
        lock(_latch)
        {
            if(_freezedValue.HasValue == false)
            {
                SetFreezedValue(DateTime.Now);
            }
            return _freezedValue.Value;
        }
    }
    private void EnsureFixedMode()
    {
        if(Mode == ClockMode.RealTime)
            throw new InvalidOperationException("This operation is possible in Fixed mode only.");
    }
    private void SetFreezedValue(DateTime? value)
    {
        EnsureFixedMode();
        lock(_latch)
        {
            _freezedValue = value;
        }
    }
}


  На написание кода я потратил минут 20 максимум, но они окупятся неоднократно, экономя на установке ожиданий в каждом тесте. Он более дружественен по отношению к пользователю, позволяет разные варианты использования. Так я бы использовал его для тестов:
var task = new UpdatePricesTask(sessionFactoryMock.Object, Clock.FixedTime, rateProviderMock.Object);


   Я знаю, пример с часами слишком прост, но для демонстрации такой и нужен. Самую меньшую из проблем побороли, в следующий раз займёмся NHibernate в лице – ISessionFactory.



  Код из блога по-прежнему можно найти на github’e.

воскресенье, 18 марта 2012 г.

Эффективное тестирование. Скажи нет мock’ам – корни зла.

  В прошлый раз я рассказывал про подмену зависимостей с использованием mock-объектов. Настало время предупредить об опасноcтях этого пути. Пока я писал это сообщение, очень хотелось быть англоязычным блоггером, и озаглавить его незабвенным “Mocks considered harmful”. Я не первый: есть как минимум два единомышленника. И к своим выводам мы пришли независимо, основываясь на горьком опыте. Давайте посмотрим на пример, и потом объясню, почему считаю mock-объекты вредными.

Код

  Давайте представим себе задачу: некий планировщик раз в сутки запускает задачу по пересчёту цен из урюпинских ёжиков в деревянные рубли. Такая задача может выглядеть примерно так:

public class UpdatePricesTask : ITask
{
    private ISessionFactory _sessionFactory;
    private IClock _clock;
    private IRateProvider _rateProvider;
    
    public UpdatePricesTask(ISessionFactory sessionFactory, IClock clock, IRateProvider rateProvider)
    {
        _sessionFactory = sessionFactory;
        _clock = clock;
        _rateProvider = rateProvider;
    }
    
    public void Run()
    {
        var rate = _rateProvider.GetRateOn(_clock.Today);
        
        using(var session = _sessionFactory.OpenSession())
        using(var tx = session.BeginTransaction())
        {
            var prices = session
                .CreateQuery("from Price p where p.ValidFrom >= :currentDate and :currentDate <= p.ValidTo")
                .SetParameter("currentDate", _clock.Today)
                .List<Price>();
            foreach(var price in prices)
            {
                price.UpdateLocalPriceUsing(rate);
            }
            session.Flush();
            tx.Commit();
        }
    }
}


  Просто, неправда ли? А теперь давайте посмотрим, как будет выглядеть классический тест с использованием mock’ов:

[Test]
public void TestUsingMocks()
{
    //Arrange.
    var currentDate = DateTime.Today;
    var price = new Price(10);
    var pricesToUpdate = new List<Price>{ price };
        
    var repo = new MockRepository(MockBehavior.Loose);
        
    var transactionMock = repo.Create<ITransaction>();
    transactionMock
        .Setup(x => x.Commit())
        .Verifiable();
    var queryMock = repo.Create<IQuery>();
    queryMock
        .Setup(x => x.SetParameter("currentDate", currentDate))
        .Returns(queryMock.Object);
            
    queryMock
        .Setup(x => x.List<Price>())
        .Returns(pricesToUpdate);
           
        
    var sessionMock = repo.Create<ISession>();
        
    sessionMock
        .Setup(x => x.BeginTransaction())
        .Returns(transactionMock.Object)
        .Verifiable();
        
    sessionMock
        .Setup(x => x.CreateQuery("from Price p where p.ValidFrom >= :currentDate and :currentDate <= p.ValidTo"))
        .Returns(queryMock.Object);
        
        
    var sessionFactoryMock = repo.Create<ISessionFactory>();
    sessionFactoryMock
        .Setup(x => x.OpenSession())
        .Returns(sessionMock.Object);
        
    var clockMock = repo.Create<IClock>();
    clockMock
        .SetupGet( x => x.Today)
        .Returns(currentDate);
        
    var rateProviderMock = repo.Create<IRateProvider>();
        
    rateProviderMock
        .Setup(x => x.GetRateOn(currentDate))
        .Returns(2);
        
    var task = new UpdatePricesTask(sessionFactoryMock.Object, clockMock.Object, rateProviderMock.Object);
        
    //Act.
    task.Run();
        
    //Assert.
    Assert.That(price.LocalAmount, Is.EqualTo(20));
    repo.VerifyAll();
}


  Ух-ты! Целый экран на один тест! В несколько раз больше тестируемого кода! Выглядит пострашнее, чем подделки под документальные фильмы от телекомпании НТВлжёт!  И это еще упрощённый случай. Ведь не тестируется обработка исключений, логгировние, и т.д. Каждый из этих случаев заслуживает отдельной портянки mock’-тестов. Давайте огласим весь список проблем по порядку.



Проблемы



  Сложность и многословность установки ожиданий затрудняет понимание теста. Эту проблему можно облегчить, вынеся установку ожидний в отдельные методы. Тест станет более читаемым, но сложность установки ожиданий никуда не денется. Ведь по-прежнему придётся лазить в код класса и код теста, чтобы понять логику теста. Это особенно сложно, когда надо учитывать последовательность вызовов, и менять возвращаемое значение на каждый вызов.



  Жесткая привязка к деталям реализации затрудняет рефакторинг. Тест знает каждую деталь реализации. И это ужасно. Представим себе, что я прознал про новое API NHibernate – QueryOver, и хочу переписать код так:

var prices = session.QueryOver<Price>
    .Where(x => x.Validrom >= _clock.Today && _clock.Today <= x.ValidTo)
    .List<Price>();


  Очевидно, код по-прежему рабочий, функциональность не сломана. Но тест красный. Надо снова вгрызаться в мельчайшие детали Setup’ов. Это муторная демотивирующая работа. Разработчики будут стараться не трогать тест и функциональность, лишь бы не мучить себя этой нудной задачей, либо отключать тест до лучших времён (да-да, лучшие времена обычно не настают, тест лежит мёртвым грузом). И с этим невозможно ничего поделать. Если что-то сложно делать – оно не будет сделано вообще, или сделано из рук вон плохо, для галочки. Как следствие код будет постепенно деградировать.



  Логика установки ожиданий дублирует логику класса. Каждый вызыванный метод, каждый аргумент, буквально на каждую строчку в рабочем тесте есть несколько строк в коде теста. Фактически, мы не тестируем результат работы класса, а тестируем идентичность кода класса коду установки ожиданий.



  Не гарантирует работу всей системы. Mock’и подменяют зависимости на другие компоненты. Но большая часть ошибок приходится именно на неправильное взамодействивия между компонентами, особенно в случае сторонних компонентов. Поэтому моки ничего нам не гарантируют. Нам надо заново перетестировать всё в процессе интеграционного тестирования. В теории это звучит хорошо, но на самом деле чрезвычайно трудоёмко. К тому же интеграционной тест будет частично дублировать логику модульного теста. Ваш клиент готов оплачивать вам неделю на тестирование одного класса? Боюсь что нет.



  Не позволяет тестировать все аспекты класса. Посмотирте внимальнее на запрос к БД. Мы ищем все цены, которые действительны на заданную дату. И мы устанавливаем в качестве результа некий список цен. А как мы проверим тот факт, что в реальности в него войдут только корректные цены? Никак. Совсем никак. Только интеграционное тестирование может выявить этот момент.



Выводы



  Главная цель автоматизированного тестирования – облегчение разработки, поддержка действий программиста. Mock’и не только не помогают решать их, но и активно мешают этому процессу. Избегайте их всеми силами.



  Единственный вариант использования, который не расстраивал меня, если я могу ограничиться кодом:

[Test]
public void FooWorksFine()
{
    var fooMock = new Mock<IFoo>();
    fooMock.SetupAllProperties();
    //other stuff
}


  Думаю, мой читатель уже затаил вопрос: если выкинуть mock’и, то как тестировать? Об этом в следующий раз.



  Полностью код блога доступен на github.

вторник, 13 марта 2012 г.

Эффективное тестирование. Боремся с зависимостями.

  Рано или поздно в процессе тестирование мы натыкаемся на зависимости, которые мешают протестировать систему в изолированном состоянии, или просто делают это невозможным (попробуйте сделать MessageBox.Show() в неинтерактивном режиме). В таких случаях нам надо как-то отделить нетестопригодные зависимости. Для этих случаев нам надо некие заглушки для тестов. Как быть в этой ситуации, рассмотрим на примере. Пусть у нас есть класс, который мы собираемся протестировать:

public class Order
{
    //skipped
}
public interface IOrderListView
{
    IEnumerable<Order> Orders { get; set; }
}
    
public class OrderListPresenter
{
    private readonly ISession _session;
    private readonly IOrderListView _view;
    public OrderListPresenter(ISession session, IOrderListView view)
    {
        _session = session;
        _view = view;
    }
    public void Start()
    {
        _view.Orders = _session.QueryOver<Order>().Take(100).List<Order>();
    }
}

  Мы хотим протестировать простую вещь: после вызова метода Start() в IOrderView передан Order  и свойство Visible == true. В коде две зависимости: IOrderListPresenter и ISession. В былые времена единственным способом протестировать OrderListPresenter было использование рукописных реализаций интерфейсов (и сейчас много случаев, когда стоит воспользоваться такой возможностью, но об этом в следующих сообщениях). К счастью, научно-технический прогресс уже решил эту проблему: в мире тестов хозяйствуют Mock-объекты.  А сейчас посмотрим на модные гаджеты для создания Mock-объектов на лету.


Moq


  В текущий момент пожалуй наиболее популярным фреймворк. Хотя его разработка находится в анабиозном состоянии. Гугл по по привычке ведёт на старый официальный сайт, хотя проект некоторое время назад переехал на codeplex (хотя процесс переезда еще не завершён, и содержание codeplex’а весьма куцее). Итак, наше тест с Moq  будет выглядеть так:

[Test] 
public void On_start_puts_in_view_some_orders_behaviour_style()
{
    //Создаём двойник интерфейса IOrderView
    var viewMock = new Mock<IOrderView>();
    var order = new Order(10);
    //Создаём двойник интерфейса ISession
    var sessionMock = new Mock<ISession>();
    sessionMock
        //При вызове метода Get<Order> с аргументов 10
        .Setup(x => x.Get<Order>(It.Is<long>(id => id.Equals(10))))
        //И вернём 
        .Returns(order);
    var presenter = new OrderPresenter(sessionMock.Object, viewMock.Object);
    presenter.Start(10);
    viewMock.VerifySet(x => x.Visible = true);
    viewMock.VerifySet(x => x.Order = order);
}

  Как можно догадаться, в последних двух строчках мы проверяем ожидания вызовов методов. Но я люто ненавижу код, где мне приходится выполнять такие проверки. Проверка вызовов методов приоткрывает чёрный ящик реализации класса, тем самым жёстко привязывая к ней тест. Любое изменение в коде класса тут же приводит к необходимости менять код ожиданий. Надо ли объяснять, что это называется связанность (coupling), и вообще плохо?  На мой вкус, этот тест будет гораздо проще, если переписать его в стиле state-driven тестирование:

[Test] 
public void On_start_puts_in_view_some_orders_state_style()
{
    var viewMock = new Mock<IOrderView>();
    //Устанавливаем поведение всех свойств по-умолчанию. 
    viewMock.SetupAllProperties();
    var order = new Order(10);
    var sessionMock = new Mock<ISession>();
    sessionMock
        //При вызове метода Get<Order> с аргументов 10
        .Setup(x => x.Get<Order>(It.Is<long>(id => id.Equals(10))))
        //И вернём 
        .Returns(order);
    var presenter = new OrderPresenter(sessionMock.Object, viewMock.Object);
    presenter.Start(10);
    viewMock.Object.Satisfy(view =>
                            view.Visible == true &&
                            view.Order == order);
}

  Полностью чёрный ящик всё равно не получится, но степень информированности о внутренностях класса уменьшилась. Этот вопрос хорошо расписал Фаулер, рекомендую к прочтению.


  Стоит отметить, что Moq умеет подделывать не только интерфейсы, но и non-sealed классы и даже переопределять виртуальные методы. Эти ограничения вытекают из Castle.DynamicProxy – подделки генерируются на лету в динамическую сборку, и на них распространяются все ограничения C#.


  Впрочем, я бы отметил два существенных недостатка Moq:



  • Полузаброшенное состоянии. Это не так страшно само по себе, но наличие неприятного бага может существенно осложнить жизнь.
  • Для динамической генерации двойников Moq использует интегрированный Castle.DynamicProxy. Это удобно, но может приводить к конфликту имён.

  Тем не менее – это взрослый фреймворк, готовый разрешить почти все необходимые случаи. Из альтернатив стоит отметить FakeItEasy, но на мой взгляд его API не самое удобное, и не позволяет тестировать вызовы свойств. В общем, Moq всё же верней. Есть еще Rhino.Mocks, но его API пресыщено наследием ранних версий, сейчас выглядит неуклюжим. Аyende Rahien похоже более не собирается его поддерживать, несмотря на заявленные планы по выпуску версии 4.0.


  Несмотря на то, что возможности современных mock-библиотек впечатляют, я бы рекомендовал свести их использование к минимуму. Почему и как это делать в следующий раз.