Шаблоны единиц работы и репозитория предназначены для совместного использования, или это два решения? - PullRequest
4 голосов
/ 13 июля 2011

После прочтения этой статьи Tesability and Entity Framework 4.0 вчера около 75 раз, мне не ясно, что автор пытался сказать.Кажется, он указывает на то, что шаблоны UoW и Repository - это два разных шаблона, которые можно использовать для решения похожих проблем.Другими словами, UoW был больше для нескольких объектов, которые должны быть организованы до фиксации, тогда как Repository - для более простого сохранения.

Когда я продолжаю поиск в Интернете, чтобы найти платформу Святого Грааля, позволяющую мне выполнить модульное тестирование своего приложения, я вижу больше статей, которые, по-видимому, подразумевают, что UoW является частью шаблона Repository, или наоборот.*

Какая интерпретация верна?

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

Ответы [ 3 ]

3 голосов
/ 13 июля 2011

Проблема, с которой вы сталкиваетесь, заключается в том, что люди неправильно истолковывают шаблон хранилища.

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

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

В плане тестирования либо работает. Это действительно легко (на примере Moq) ..

public void UpdateAddress()
{
   // Arrange
   User user = // Build your user somehow..
   myUnitOfWorkMock.Setup(x => x.Get<User>(It.IsAny<long>())).Returns(user);

   MyService sut = new MyService(myUnitOfWorkMock);

   // Act
   sut.ChangeAddress(userId, "1234 Copperhead Road");

   //Assert
   Assert.AreEqual("1234 Copperhead Road", user.Address);
}
1 голос
/ 13 июля 2011

Будьте внимательны при следовании этому совету на этой странице.Я сделал, и это не сработало слишком хорошо, и я немного сгорел.

Вам НУЖНО проверить свои бизнес-классы с помощью интеграционных тестов (тестов, использующих реальную базу данных) вместо чистых модульных тестов (с использованиемв хранилище данных памяти) вместо юнит-тестов.Причина в том, что вы закончите с вашими модульными тестами, дающими ложные проходы и ложные отрицания.

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

var requests = _unitOfWork.Requests.Where(x => x.RequestDate.Date = DateTime.Now.Today).ToList();

Это будет хорошо работать при работе с объектами памяти, но вызовет NotImplementedException, потому что EF не может эффективно обработать этот тип запроса.Существует множество запросов Linq, которые не поддерживаются Entity Framework, но вы не поймаете их, если не будете выполнять интеграционные тесты.Таким образом, ваши модульные тесты теперь дают вам ложные срабатывания, они утверждают, что это работает, когда это действительно не так.

Теперь возьмем случай, когда ваша бизнес-логика хочет запрашивать запросы, которые были сделаны конкретным пользователем.Скорее всего, ваша бизнес-логика сделает это, в результате вы получите оператор Linq, такой как:

var requests = _unitofWork.Requests.Where(x => x.UserId == userid).ToList();

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

[TestMethod]
public void Can_Retrieve_User_Requests()
{
    // Setup
    var user = new User();
    var req1 = new Request();       
    user.Requests.Add(req1);

    _unitOfWork.Add(user);
    _unitOfWork.Add(req1);
    _unitOfWork.Commit();

    // Act
    var result = BusinessLogicClass.GetRequestsByUserId(user.Id);

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(1, result.count);
}

Этот модульный тест работает и будет проходить как есть (все остальные аспекты работают, конечно), если _unitOfWork использует вашСистема Entity Framework.

Если вы используете единицу работы в памяти, это не удастся по двум причинам.Во-первых, user.Id не задано, 99% идентификаторов времени будут сгенерированы Entity Framework.Даже если вы явно установите user.Id = X, это все равно не будет выполнено, потому что вам также нужно установить request.UserId.В EF поле request.UserId будет автоматически заполняться при создании нового отношения, но этого не произойдет при использовании набора данных в памяти.Это означает, что вы должны точно знать, что GetRequestsByUserId() реализует запрос, посмотрев на поле Request.UserId вместо Request.User.Id, и это также означает, что изменение вашего модульного теста для прохождения в этом случае завершится неудачей, если вы измените свой запросиспользовать Request.User.Id, даже если бизнес-логика и результат в EF практически одинаковы.

Один последний пример - это то, что происходит, если в поле вашей базы данных недостаточно места для хранения длинной строки (EFCodeFirst по умолчанию имеет длину строки в БД varchar (128)).Если у вас есть тест с более чем 128 символами, ваше хранилище данных в памяти будет хорошо хранить строку, но EF сделает исключение, потому что она слишком длинная.

TLDR : в тестах модулей памятидля запросов данных и хранения данных не даст вам уверенности в том, что ваше приложение работает правильно.Вам все еще нужно создавать интеграционные тесты, но ваши интеграционные тесты должны будут в любом случае проверять 99% того, что охватывают ваши юнит-тесты, и, таким образом, это удвоит ваши усилия по TDD, замедлит вас и затруднит сопровождение тестов.

Вместо этого интеграционные тесты с Sql Server Compact Edition дадут вам подтверждение того, что ваша бизнес-логика не только работает правильно, но и корректно работает с действующей базой данных.

В качестве примечания яговорить о тестировании бизнес-логики с помощью интеграционных тестов.Тестирование контроллеров MVC должно проводиться только на реальной базе данных, если контроллеры действительно обращаются к базе данных напрямую (что не должно быть imho, контроллеры MVC должны вызывать ваши бизнес-классы для выполнения операций с БД), и если контроллеры не 'Если говорить напрямую с базой данных, то вы можете просто использовать среду моделирования, такую ​​как Moq, для моделирования памяти ваших бизнес-классов (поскольку они уже проверены с помощью интеграционных тестов).

0 голосов
/ 13 июля 2011

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

Шаблон репозитория обычно требует загрузки информации - и, если вы решите, - сохранения информации. Из MSDN: «Используйте репозиторий, чтобы отделить логику, которая извлекает данные и отображает их в модели сущностей, из бизнес-логики, действующей в модели. T»

Загрузите ваши данные из вашего хранилища. Шаблон вашей единицы работы, несомненно, может инкапсулировать транзакцию вокруг всех сохранений вашего репозитория.

Нет причины, по которой вы не можете использовать одно, другое или оба вместе. Теперь - тестирование ваших объектов зависит от того, ЧТО вы хотите протестировать. Если вы тестируете сохранение в магазине - тогда точно - это не модульный тест. Если вы хотите издеваться над своим магазином и просто вернуть объект в памяти, пусть будет так. Я думаю, что мне не хватает, почему здесь есть беспокойство с других постеров - зови меня блаженно невежественным.

Ваши юнит-тесты должны проверять «юнит», а не какой-то потенциальный вызов, который ваше приложение может или не может сделать. Вы хотите проверить на экс. метод - не поставщик linq

...