2009-06-08 2 views
5

ВопросВ TDD и DDD, как вы обрабатываете свойства только для чтения в подделках?

Как вы обрабатываете поля только для чтения при создании подделок?

фон

Я в начинающих этапах с помощью ASP.Net MVC, и я с помощью спортивного магазина Стивена Сандерсона и Nerd ужина Скотта Гу в качестве примеров. Одна небольшая проблема, с которой я только что столкнулась, заключается в том, как работать с свойствами только для чтения при создании подделок. Я использую LINQToSQL.

Мой интерфейс:

public interface IPersonRespository 
{ 
    Person GetPerson(int id); 
} 

и моя подделка становится

public class FakePersonRepository 
{ 
    public Person GetPerson(int id) 
    { 
     return new Person {id="EMP12345", name="John Doe", age=47, ssn=123-45-6789, totalDrWhoEpisodesWatched=42}; 
    } 
} 

Вот моя проблема. Поля id, ssn и totalDrWhoEpisodesWatched доступны только для чтения, поэтому приведенный выше код фактически не работает. Однако я не понимаю, как создать поддельного нового человека и установить свойство только для чтения. Я уверен, что есть решение, но я еще не встречал его в своих поисках.

Обновление: Наследование + Скрытие собственности как потенциальное решение?

Я еще не решил решительного решения проблемы. Мне не нравится понятие модификации моих классов домена для создания подделок. Для меня добавление разметки к классам домена для проведения тестирования - это форма добавленной связи - к реализации вашего теста. Теперь я изучаю еще одну возможность, которая заключается в создании класса FakePerson, который наследуется от Person, но скрывает свойства с новыми свойствами read-write.

public class FakePerson: Person 
{ 
    public new int age { get; set; } 
    public new string ssn { get; set; } 
    public new int totalDrWhoEpisodesWatched { get; set; } 
} 

До сих пор это решение заключается в том, как я склоняюсь. Это нарушает Принцип замещения Лискова, однако это не влечет меня в тестовый проект. Я был бы рад услышать любую критику и/или отзывы об этом в качестве решения.

Победитель: Mock Каркасы

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

+0

Почему это собирается быть только для чтения? –

+0

Он установлен только для чтения, поскольку ни один пользователь домена не имеет права изменять свойства. Это может быть по ряду причин, но часто, потому что приложение не имеет прав на изменение поля либо потому, что оно является полем с расчетом/автоматическим генерированием, либо из-за политик безопасности базы данных. – John

+3

В слегка несвязанной теме я бы рекомендовал изменить имена своих свойств на PascalCase, чтобы придерживаться соглашений об именах .NET. http://msdn.microsoft.com/en-us/library/ms229043(loband).aspx –

ответ

6

Рассмотрите насмешку над типом Person в своем тесте. Пример использования Moq:

var mock = new Mock<Person>(); 
mock.SetupGet(p => p.id).Returns("EMP12345"); 
mock.SetupGet(p => p.ssn).Returns("123-45-6789"); 
mock.SetupGet(p => p.totalDrWhoEpisodesWatched).Returns(42); 
return mock.Object; 

В противном случае, попробуйте выяснить, как LINQ к SQL устанавливает те только для чтения свойства.

EDIT: Если вы попытаетесь выше и Moq бросает ArgumentException в SetupGet вызов с сообщением «Invalid установки на не переопределение члена: р => p.id», то вам необходимо отметить свойство виртуальное. Это необходимо сделать для каждого свойства, которое вы хотите переопределить.

В LINQ к SQL, это может быть сделано в OR дизайнер, выбрав свойство, а затем в окне Свойства установите Наследование Модификатор в виртуальный.

+0

Может ли moq обходить только реквизит? Я помню, что у меня были проблемы с носорогами. :/ –

+0

Вы имеете в виду только поля "readonly" или свойства без общедоступных сеттеров? В первом случае я не думаю, что Мок может помочь. В последнем случае сеттеры не имеют значения - Moq просто переопределяет геттер. – mvr

+0

Свойства только для чтения (т. Е. Свойства с множеством). Linq to SQL устанавливает частную переменную в классе Person, которая в этом примере будет _id, _ssn и _totalDrWhoEpisodesWatched. – John

1

Вы можете устанавливать только свойства readonly в конструкторе класса. Объект Person должен иметь конструктор, который принимает id, ssn и totalDrWhoEpisodesWatched. Конечно, если это объект, созданный linqtosql, у вас могут возникнуть проблемы с модификацией этого кода при автоматическом создании кода.

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

+0

@ Joel- Вы находите накладные расходы на использование полностью POCO-модели, которая требует дополнительных усилий, необходимых для двойного сопоставления? – RichardOD

+0

Мне не нравится идея добавления ssn и totalDrWhoEpisodesWitched к конструктору. Добавление дополнительных параметров, которые на самом деле не нужны в запущенной программе только для проведения тестирования, кажется противоречащим хорошему дизайну API, потому что это означает обнародовать метод, который не должен использоваться потребителями (кроме тестового потребителя). Что вы подразумеваете под «отображаемым объектом»? – John

+0

@RichardOD, иногда :-) @John, по отображаемому объекту, я имею в виду использовать объект, который вы закодировали сами (вместо того, чтобы полагаться на автогенерируемый класс, который предоставляет linq2sql). Таким образом, когда вы выбираете из своего хранилища данных, вы бы сделали (выберите новый MyPerson() {...}) и установите там свои свойства. Это позволяет избежать зависимости от автогенерируемого класса, который может или не может измениться в будущем. –

1

В .NET вы можете пометить свои сеттеры как «внутренние» и использовать атрибут сборки InternalsVisibleTo, чтобы внутренние элементы были видимыми для вашей тестовой сборки. Таким образом, ваши сеттеры не будут публичными, но вы все равно сможете получить к ним доступ.

примечание: даже если вопрос не отмечен.NET, я предположил, что он основан на использовании синтаксиса инициализатора объекта. Если мое предположение было неправильным, это предложение не применяется (если, конечно, язык, который вы используете, не имеет эквивалентной функции).

+0

Я не отмечал это .Net, однако я полагал, что упоминания ASP.net и LinqToSQL отдают это. Благодарю. – John

0

Если это для испытаний - подумайте об использовании отражения. Это не связано с запутыванием вашей модели домена.

Например, у меня есть класс FactoryBase, который использует отражение для установки необходимой поддержки посредством выражения лямбда через параметры (например, this). Работает как очарование - создание новой фабрики прост как определение типа репозитория и данных сущности по умолчанию.

+0

Ответ интригует, но я не вижу, как он решает проблему, о которой я спрашивал, а именно о том, как обрабатывать свойства только для чтения в подделках. Foo имеет сеттеры для всех 3 свойств (Bar, Baz и Bling). Что бы вы сделали, если бы у BAZ был только геттер? – John

+0

Отражение может справиться с этим. Получили некоторые объекты домена с защищенными и частными сеттерами. –

0

Я также использую Moq. Я люблю его, и он отлично работает. Но, прежде чем я начал использовать Moq, я написал много подделок. Вот как я решил проблему, используя подделки.

Поскольку подделка может иметь дополнительные методы, которые не имеют реализации «производства», я бы добавил несколько дополнительных методов для моей поддельной реализации, чтобы обрабатывать установку только для чтения.

Как это:

public class FakePersonRepository : IPersonRespository 
{ 
    private IDictionary<int, Person> _people = new Dictionary<int, Person>(); 

    public Person GetPerson(int id) // Interface Implementation 
    { 
     return _people(id); 
    } 

    public void SetPerson(int id, Person person) // Not part of interface 
    { 
     _people.Add(id, person); 
    } 

} 
Смежные вопросы