TDD-дружественный класс, похожий на синглтон

У меня есть класс репозитория, который используется как минимум из двух других классов. Этот класс репозитория должен быть инициализирован - это очень дорого (запрос базы данных). Теперь я создаю отдельные экземпляры репозитория там, где мне это нужно. Дело в том, что каждый раз, когда я создаю репозиторий, он должен быть инициализирован. Как создать такой репозиторий для TDD? Первое, на мой взгляд, было Singleton, но не решение.

Ответ 1

Используете ли вы какой-либо контейнер IOC? Unity является моим контейнером выбора и содержит ContainerControledLifetimeManager, который делает ваш класс одиночным, но не управляемым вами.

Ответ 2

Надеюсь, что TDD-friendly вы имеете в виду "тестируемый" код. Для Singleton ObjectX я считаю, что наиболее распространенным способом является разделение ответственности (SRP) "управления созданием" на другой класс, поэтому ObjectX выполняет все, что он должен делать.

Затем у вас есть еще один класс ObjectXFactory или Host или все, что вы хотите назвать, которое отвечает за предоставление одного экземпляра для всех клиентов (и предоставление синхронизации потоков при необходимости и т.д.)

  • Объект X может быть TDD независимо. Вы можете создать новый экземпляр в тестовом примере и проверить функциональность.
  • ObjectXFactory, с другой стороны, также легко тестируется. Вам просто нужно увидеть, возвращает ли несколько запросов GetInstance() один и тот же объект. ИЛИ лучше делегировать эту ответственность на структуру IOC, такую ​​как Spring, которая позволяет декларативно маркировать определение объекта для получения одноэлементного поведения (сохранение ваших усилий при написании тестов также).

Вам просто нужно обучить и соответствовать соглашению команды, что конструктор ObjectX не должен вызываться - всегда используйте ObjectXFactory.CreateInstance(). (Если вы обнаружите, что у вас есть проблема осведомленности/дисциплины, отметьте ObjectX ctor внутренним и видимым только для тестовой сборки через скрытую InternalsVisibleToAttribute) НТН

Ответ 4

Рассмотрите экземпляры кеширования для повышения производительности, прежде чем рассматривать одноточие. Но для TDD-дружественных дизайнов учитывайте стратегическую инъекцию, чтобы "медленные" биты можно было удалить для тестирования и заменить на заглушки и издевки. Попытайтесь не выполнять вызовы db в тестах, если можете.

Ответ 5

Вы не можете этого сделать - по крайней мере, не в истинном смысле TDD.

Опираясь на стратегии DI/IoC, такие как Unity, ваши тесты зависят от внешнего компонента и не тестируются изолированно.

Затем тесты становятся интеграционными тестами, а не модульными.

== Игнорируйте нижеприведенный ответ ==

Я думаю, вы хотели знать, как сделать тестовый репозиторий.

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

Я проиллюстрирую это, используя Rhino Mocks 3.5 для .NET 3.5:

Позвольте сделать интерфейс из Репозитория, позвольте называть это IRepository

public interface IRepository
{
}

Теперь, поскольку вам нужно использовать IRepository для двух разных объектов, давайте просто использовать generics, чтобы вы могли создать экземпляр своего репозитория с помощью этого:

public interface IRepository<T>

конечно, это означало бы, что у вас будет какой-то метод поиска:

{
    public IEnumerable<T> Find(Criteria criteria)
}

где ваш объект критериев - это некоторый объект, который позволяет вам установить, что искать, например, ваше предложение where.

Теперь у вас есть свой объект:

public class SomeObject
{
    IRepository<SomeObject> repository;

    public SomeObject(){}

    public IRepository<SomeObject> repository { get; set; }

    IEnumerable<SomeObject> FindAll()
    {
        //let assume new Criteria() will return all results
        return respository.Find(new Criteria());
    }
}

Вы хотите протестировать SomeObject таким образом, чтобы FindAll() вернул ожидаемый набор результатов - вот где Rhino Mocks будет входить:

[TestFixture]
public class SomeObjectTests
{
    [Test]
    public void TestSomeObjectFindAll()
    {
        IRepository<SomeObject> stubRepository = MockRepsitory.GenerateStub<IRepsitory<SomeObject>>();

        stubRepository.Expect(r => r.Find(new Criteria())
            .Return(new List<SomeObject>{ 
                        new SomeObject(), 
                        new SomeObject(), 
                        new SomeObject());

        var testObject = new SomeObject { Repository = stubRepository };
        IList<SomeObject> findAllResult = testObject.FindAll();

        //returned list contains 3 elements, as expected above
        Assert.AreEqual(3, findAllResult.Count)
    }
}

Обратите внимание, что приведенный выше код не является лучшей практикой TDD во всех отношениях, но это место для начала.

Ключевые понятия здесь представляют интерфейсы, позволяющие свободно связывать объекты, особенно когда объект стремится делать такие вещи, как базы данных доступа, файловые системы и т.д.

Существует более подробный пример и лучшие примеры статья Бен Холла о Rhino Mocks.