TDD-дружественный синглтоноподобный класс - PullRequest
2 голосов
/ 23 апреля 2009

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

Ответы [ 5 ]

3 голосов
/ 23 апреля 2009

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

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

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

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

2 голосов
/ 23 апреля 2009

Один из ответов для части TDD - учить насмешки.

Посмотрите эту замечательную статью Стивена Вальтера:

http://stephenwalther.com/blog/archive/2008/03/23/tdd-introduction-to-rhino-mocks.aspx

1 голос
/ 23 апреля 2009

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

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

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

== Игнорируйте ответ ниже здесь ==

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

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

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

Давайте сделаем интерфейс из репозитория, назовем это IRepository

public interface IRepository
{
}

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

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's assume new Criteria() will return all results
        return respository.Find(new Criteria());
    }
}

Вы хотите протестировать SomeObject таким образом, чтобы FindAll () вернул ожидаемый набор результатов - вот где Mh 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 .

1 голос
/ 23 апреля 2009

Рассмотрите случаи кэширования для повышения производительности, прежде чем рассматривать синглтоны. Но для TDD-дружественных проектов рассмотрите внедрение стратегии, чтобы «медленные» биты могли быть удалены для тестирования и заменены заглушками и насмешками. Старайтесь не делать вызовы БД в тестах, если можете.

1 голос
/ 23 апреля 2009

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

...