2011-02-01 2 views
9

Мне интересно, как обойти это. Я использую nhibernate и свободно.Единичное тестирование и nhibernate?

У меня есть класс домен, как этот

public class User 
{ 
    public virtual int UserId {get; private set;} 
} 

это, кажется, соглашение, делая NHibernate, как он останавливает людей от установки и идентификатор, как это автоматически генерируется.

Теперь проблема возникает, когда я тестирую устройство.

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

User user = repo.GetUser(email); 

Это должно вернуть объект пользователя.

Так что я хочу использовать MOQ сделать это

repo.Setup(x => x.GetUser(It.IsAny<string>())).Return(/* UserObject here */) 

теперь вот проблема

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

Так что я хотел бы сделать что-то вроде

User user = new User() 
{ 
    UserId = 10, 
} 

Но здесь проблема заключается Мне нужно установить Id, потому что я на самом деле использовать его в дальнейшем, чтобы сделать некоторые LINQ на некоторых коллекциях (в слое услуг как он не попадает в мой db, поэтому он не должен быть в моем репо), поэтому мне нужно его установить, но я не могу установить его, потому что это частный набор.

Итак, что мне делать? Должен ли я просто удалить частный или есть какой-то другой способ?

ответ

14

Вы можете иметь поддельный Repository объекта возвращает поддельный User объекта:

var stubUser = new Mock<User>(); 
stubUser.Setup(s => s.UserId).Returns(10); 

var stubRepo = new Mock<IUserRepository>(); 
stubRepo.Setup(s => s.GetUser(It.IsAny<string>())).Return(stubUser); 

Есть несколько вещей, чтобы наблюдать здесь:

  1. Moq может только поддельные члены конкретных классов если они отмечены как виртуальные. Это может быть неприменимо в некоторых сценариях, и в этом случае единственный способ подделать объект через Moq - это реализовать интерфейс.
    В этом случае, однако, решение работает хорошо, потому что NHibernate already imposes the same requirement по свойствам класса User для ленивой загрузки.
  2. Наличие поддельных объектов, возвращающих другие подделки, может иногда приводить к указанным единичным испытаниям. В этих ситуациях построение богатых объектных моделей, состоящих из окурков и издевательств, возрастает до такой степени, что становится трудно определить, что именно тестируется, что делает сам тест нечитабельным и трудноподдерживаемым. Это совершенно прекрасная практика тестирования модулей, чтобы быть понятной, но ее необходимо использовать сознательно.

Близкие по теме ресурсы:

+0

Благодаря это работает только в совершенстве. У меня могут быть симптомы с номером 2. Ему просто нужно было издеваться над такими вещами. Я пытаюсь сохранить это только то, что мне нужно проверить. Например, я не маскирую имя пользователя для пользователя, так как оно не используется в методе. Я хотел бы попробовать autofixture moq, но я не могу понять, как на самом деле использовать, и, похоже, на нем нет реальных обучающих программ, поэтому я не могу определить, поможет ли это мне или нет. – chobo2

2

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

В основном вы предоставляете доступ к частному члену через статический вложенный класс, который содержит метод установки свойства. Пример стоит тысячи слов:

public class User { 
    public int Id { get; private set; } 

    public static class Reveal { 
     public static void SetId(User user, int id) { 
      user.Id = id; 
     } 
    } 
} 

Вы использовать его как это:

User user = new User(); 
User.Reveal.SetId(user, 43); 

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

  • нет Intellisense запроса на сеттер свойства или методу SETID()
  • программистов должны явно использовать странный синтаксис для установки свойства с Reveal классом, тем самым побуждая их, что они не должны, вероятно, делать это
  • вы можете легко выполнить статический анализ на использований в Reveal классе, чтобы увидеть, какой код в обход стандартных моделей

доступа Если вы только ищете т o изменить частную собственность для целей модульного тестирования, и вы можете Moq-объект, тогда я бы по-прежнему рекомендовал предложение Enrico; но вы можете найти этот метод полезным время от времени.

+0

+1. Множество аналогичных опций - InternalsVisibleToAttribute, protected и subclass, трещины с отражением, помещают SetId на пользователя с ObsoleteAttribute с соответствующим предупреждающим текстом, передают Id в перегруженный конструктор (по моему предпочтению) и т. Д. Мне это нравится, потому что оно стимулирует тестирование с помощью Pocos/DTOS. Как я люблю Мока, @ Энрико прав насчет опасностей чрезмерной спецификации. – TrueWill

0

Вместо того, чтобы пытаться издеваться над вашим репозиторием, я предлагаю вам попробовать использовать базу данных SQLite в памяти для тестирования. Это даст вам скорость, которую вы ищете, и это сделает вещи намного проще. Если вы хотите увидеть рабочий образец, вы можете посмотреть один из моих проектов GitHub: https://github.com/dlidstrom/GridBook.

1

Другая альтернатива, если вы предпочитаете не издеваться над сущностными классами, - это установить частный/защищенный идентификатор с помощью отражения.

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

Мы можем попытаться реализовать его как минимум. В моем случае 95% моих сущностей используют единственный Guid как уникальный идентификатор, а всего несколько используют целое число.Поэтому наши классы сущностей, как правило, реализовать очень простой HasID интерфейс:

public interface IHasID<T> 
{ 
    T ID { get; } 
} 

В реальном классе сущностей, мы могли бы реализовать это следующим образом:

public class User : IHasID<Guid> 
{ 
    Guid ID { get; protected set; } 
} 

Этот идентификатор сопоставляется с NHibernate в качестве первичного ключа обычным способом.

Для установки этого в наших модульных тестов, мы можем использовать этот интерфейс, чтобы обеспечить удобный метод расширения:

public static T WithID<T, K>(this T o, K id) where T : class, IHasID<K> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { id }); 
    return o; 
} 

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

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

var testUser = new User("Test User").WithID(new Guid("DC1BA89C-9DB2-48ac-8CE2-E61360970DF7")); 

или на самом деле, так как для Guids я не волнует, что на самом деле ID , у меня есть еще один метод расширения:

public static T WithNewGuid<T>(this T o) where T : class, IHasID<Guid> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { Guid.NewGuid() }); 
    return o; 
} 

И в использовании:

var testUser = new User("Test User").WithNewGuid(); 
Смежные вопросы