Effort-FirstOrDefault возвращает null при подделке базы данных

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

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

MyContext contextx = new MyContext(Effort.DbConnectionFactory.CreateTransient());

var client = new Client
{
    ClientId = 2,
    PersonId = 3,
    Person = new Person
    {
        PersonId = 3,
        EMail = "[email protected]"
    }
};
contextx.Client.Add(client); //<-- client got added, I checked it and is there

var email = contextx.Client.Select(c => c.Person.EMail).FirstOrDefault(); 

В последней строке выше я не могу вернуть адрес электронной почты [email protected], вместо этого он всегда возвращает ноль.

Любые идеи?


person General Electric    schedule 14.08.2016    source источник
comment
@ Фабио, да, это исправило   -  person General Electric    schedule 14.08.2016


Ответы (1)


Отвечая на ваш прямой вопрос

Что касается конкретного вопроса, который вы задали, я бы предложил две вещи:

  1. Взгляните на contextx.Client.ToArray() и посмотрите, сколько членов у вас действительно есть в этой коллекции. Может случиться так, что коллекция Client на самом деле пуста, и в этом случае вы действительно получите null. Или может случиться так, что первый элемент в коллекции Client имеет нулевое значение для EMail.

  2. Как изменится поведение, если вы вызовете contextx.SaveChanges() перед запросом коллекции Client в DbContext? Мне любопытно посмотреть, приведет ли вызов SaveChanges к тому, что вновь вставленное значение будет существовать в коллекции. Это действительно не должно требоваться, но может быть какое-то странное взаимодействие между Усилием и DbContext.

EDIT: SaveChanges() оказывается ответом.

Общие предложения по тестированию

Поскольку вы отметили этот вопрос тегом «модульное тестирование», я дам несколько общих советов по модульному тестированию, основанных на десяти годах, проведенных в качестве специалиста по модульному тестированию и тренера. Модульное тестирование — это изолированное тестирование различных небольших частей вашего приложения. Обычно это означает, что модульные тесты взаимодействуют только с несколькими классами одновременно. Это также означает, что модульные тесты не должны зависеть от внешних библиотек или зависимостей (таких как база данных). И наоборот, тест интеграции одновременно проверяет несколько частей системы и может иметь внешние зависимости от таких вещей, как базы данных.

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

В этом случае либо вы действительно хотите провести модульное тестирование какой-то части функциональности, зависящей от DbContext, либо вы пытаетесь протестировать уровень доступа к данным. Если вы пытаетесь написать изолированный модульный тест чего-то, что напрямую зависит от DbContext, вам нужно сломать зависимость от DbContext. Я объясню это ниже в разделе Разрушение зависимости от DbContext ниже. В противном случае вы действительно пытаетесь интеграционно протестировать свой DbContext, включая то, как отображаются ваши сущности. В этом случае я всегда считал, что лучше изолировать эти тесты и использовать реальную (локальную) базу данных. Вероятно, вы захотите использовать локально установленную базу данных того же типа, что и в рабочей среде. Часто SqlExpress работает просто отлично. Направьте свои тесты на экземпляр базы данных, который тесты могут полностью уничтожить. Позвольте вашим тестам удалить все существующие данные перед запуском каждого теста. Затем они могут настроить любые данные, которые им нужны, не беспокоясь о том, что существующие данные будут конфликтовать.

Разрыв зависимости от DbContext

Итак, как написать хорошие модульные тесты, если ваша бизнес-логика зависит от доступа к DbContext? Вы этого не сделаете.

В моих приложениях, которые используют Entity Framework для сохраняемости данных, я уверен, что доступ к DbContext содержится в отдельном проекте доступа к данным. Как правило, я создаю классы, реализующие шаблон репозитория, и этим классам разрешено зависеть от DbContext. Итак, в этом случае я бы создал ClientRepository, реализующий интерфейс IClientRepository. Интерфейс будет выглядеть примерно так:

public interface IClientRepository {

    Client GetClientByEMail(string email);

}

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

В качестве дополнительного преимущества реализация этого интерфейса определяет, что значит найти Client по адресу электронной почты в одном унифицированном месте. Интерфейс IClientRepository позволяет быстро ответить на вопрос: "Как нам запрашивать Client объектов в нашей системе?"

Принятие зависимости от DbContext — это примерно тот же масштаб проблемы тестирования, что и предоставление классам предметной области зависимости от строки подключения и повсеместное наличие кода ADO.Net. Это означает, что вам нужно создать реальное хранилище данных (даже с поддельной базой данных) с реальными данными в нем. Но если вы поместите свой доступ к DbContext в определенную сборку доступа к данным, вы обнаружите, что ваши модульные тесты писать намного проще.

Что касается организации проекта, я обычно разрешаю своему проекту доступа к данным только ссылаться на Entity Framework. У меня будет отдельный основной проект, в котором я определяю сущности. Я также определю интерфейсы доступа к данным в проекте Core. Затем конкретные реализации интерфейса включаются в проект доступа к данным. Большинство проектов в вашем решении могут тогда просто получить зависимость от основного проекта, и только исполняемый файл верхнего уровня или веб-проект действительно должен зависеть от проекта доступа к данным.

person AggieEric    schedule 14.08.2016
comment
вызов SaveChanges исправил это, что касается шаблона репозитория, я пошел по этому пути в других проектах, и он дошел до того, что у меня слишком много функций в моем классе репозитория для минимальных вещей, в этом случае я специально не хотел чтобы использовать шаблон репозитория с UnitOfWork из-за этой проблемы, для каждого варианта запроса необходимо создать новую функцию, IMO слишком хлопотно делать это так, как вы предлагаете, но все же спасибо. - person General Electric; 14.08.2016
comment
Я понимаю. У всех нас есть свои предпочтения. Ради других, читающих это, я уточню свои рассуждения. Я предпочитаю тестировать доступ к данным изолированно, потому что тогда я знаю, что это работает. Именно по причинам, о которых вы говорите, я предпочитаю эту изоляцию. Я хочу протестировать каждый запрос, чтобы убедиться, что все работает правильно. Вне DAL я хочу иметь настройки, в которых я могу легко заглушить эти результаты, не настраивая реальные объекты. - person AggieEric; 14.08.2016
comment
@ Фабио О чем ты говоришь, что я не ответил на вопрос? Вершина моего ответа была contextx.SaveChanges(). См. пункт №2. Оригинальный постер даже имеет такой комментарий, как первый комментарий к этому ответу. Пожалуйста, прочитайте ответ более внимательно, прежде чем проголосовать против. - person AggieEric; 14.08.2016
comment
Из пункта 2: Мне любопытно посмотреть, приведет ли вызов SaveChanges к тому, что вновь вставленное значение будет существовать в коллекции. На самом деле этого не требуется, но между Effort и DbContext может быть какое-то странное взаимодействие . Прошу прощения, но это больше похоже на комментарий, чем на ответ. Я на сто процентов согласен с вами в отношении разрыва зависимости от уровня базы данных. - person Fabio; 14.08.2016