Создание репозиториев для модульного тестирования

Я написал веб-приложение на C #, MVC, Entity-Frame, LINQ и т. Д., И теперь я хочу задним числом создать модульные тесты для всего проекта.

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

У меня 3 основных модели; Категория, пароль, пароль пользователя.

Категории содержат категории и пароли. И пароли содержат UserPasswords (которые ссылаются на учетные записи пользователей). Итак, я создал 3 репозитория с основными методами, такими как; Создать, удалить, обновить и т. Д.

Однако, глядя на этот запрос LINQ:

var selectedCategoryItem = DatabaseContext.Categories
                .Where(c => c.CategoryId == ParentCategoryId)
                .Include(c => c.SubCategories)
                .Include(c => c.Passwords)
                .Include(c => c.Passwords.Select(p => p.Creator))
                .ToList()
                .Select(c => new Category()
                {

                SubCategories = c.SubCategories
                            .Where(sub => !sub.Deleted)
                            .OrderBy(sub => sub.CategoryOrder)
                            .ToList(),                          //make sure only undeleted subcategories are returned

                Passwords = c.Passwords
                            .Where(pass => !pass.Deleted
                            && (
                            (UserPasswordList.Any(up => up.PasswordId == pass.PasswordId && up.Id == UserId))
                            || (userIsAdmin && ApplicationSettings.Default.AdminsHaveAccessToAllPasswords)
                            || pass.Creator_Id == UserId
                            )
                            )   //make sure only undeleted passwords - that the current user has acccess to - are returned
                            .OrderBy(pass => pass.PasswordOrder)
                            .ToList(),

                CategoryId = c.CategoryId,
                CategoryName = c.CategoryName,
                Category_ParentID = c.Category_ParentID,
                CategoryOrder = c.CategoryOrder,
                Parent_Category = c.Parent_Category,
                Deleted = c.Deleted
                })
                .SingleOrDefault();

Если в репозиториях есть только базовые методы, такие как Save, Update, Delete, Insert, FindById и т. Д., Как мне разбить запрос на репозитории? Там также много бизнес-логики, такой как фильтрация паролей, к которым у пользователя есть доступ, где это должно быть?

Я читал кое-что о возврате объектов IQueryable, чтобы запросы LINQ можно было изменять на уровне сервиса ... как это будет выглядеть?


person binks    schedule 01.12.2014    source источник
comment
Вы смотрели на Усилие? Labor.codeplex.com   -  person Bringer128    schedule 02.12.2014


Ответы (1)


Если вы хотите писать модульные тесты, весь этот LINQ to Entities должен находиться за интерфейсом,

e.g.

public interface ICategoryRepository {
    List<Category> GetCategoriesForParentId(int id);
}

затем класс, который реализует этот метод и содержит LINQ и логику фильтрации.

Теперь в клиентских классах, которым требуется доступ к нему, передайте ICategoryRepository через конструктор (рекомендуется). А для модульного тестирования передайте имитируемый репозиторий в конструктор, который возвращает заранее определенный набор результатов.

Для тестирования фактической логики фильтрации / преобразования репозитория лучше всего было использовать SQLite или SQL CE и провести интеграционный тест, который загружает заранее определенную базу данных, которая находится в известном состоянии (либо из сохраненной копии или воссоздавая его для каждого теста) и иметь интеграционные тесты, указывая класс репозитория на базу данных SQLite или SQL CE, а не на ваш реальный, чтобы он всегда был в известном состоянии, запустите запрос и проверьте набор результатов .

РЕДАКТИРОВАТЬ:

Либо передайте в метод детали, необходимые репозиторию, например:

List<Category> GetCategoriesForParentIdAnduser(int id, int passwordId, int userId);

Или, поскольку Пользователь, вероятно, находится на сайте, вы можете передать

public interface IUserConfig {
     int PasswordId{ get; set; }
     int UserId { get; set;}
}


// Constructor for CategoryRepository : ICategoryRepository 
public CategoryRepository(DbContext context, IUserConfig)  
{
   ....
}

тогда

public List<Category> GetCategoriesForParentId(int id){
     return _context.Categories
                .....
                Passwords = c.Passwords.Where(
                            .....
                            (UserPasswordList.Any(up => up.PasswordId == _userConfig.PasswordId 
                                                     && up.Id == _userConfig.UserId))                            
                .....
}

EDIT2: только что заметил .SingleOrDefault(), вместо того, чтобы возвращать List<Category>, вы вернете Category.

person Michal Ciechan    schedule 01.12.2014
comment
Я не могу понять, давайте возьмем List ‹Category› GetCategoriesForParentId - это вернет список категорий, но мне нужно будет вернуть список категорий и паролей ... так как это будет реализовано с использованием шаблона репозитория? - person binks; 02.12.2014
comment
2 репозитория, с 2 обращениями - person Michal Ciechan; 02.12.2014
comment
Итак, что-то вроде: var root = new Category () {subcategories = GetCategoriesForParentId (1), password = GetPasswordsForParentId (1)}; - person binks; 02.12.2014
comment
Или вы также можете передать UserDetails в этот метод или передать какой-то IConfig / IUserConfig конструктору репозиториев - person Michal Ciechan; 02.12.2014
comment
Что на самом деле может содержать UserDetails или IConfig? - person binks; 02.12.2014
comment
PasswordId и UserId - person Michal Ciechan; 02.12.2014
comment
Чаще всего IUserConfig, как правило, используется в большинстве других мест, так как вам обычно требуются сведения о пользователях для большинства действий. - person Michal Ciechan; 02.12.2014
comment
Все, что можно логически связать вместе, должно быть в собственном интерфейсе / абстракции. первый принцип ТВЕРДЫХ, принцип единой ответственности - person Michal Ciechan; 02.12.2014
comment
Итак, если я в значительной степени просто вставлю весь этот запрос LINQ в GetCategoriesForParentId, это будет иметь смысл в шаблоне проектирования репозитория? - person binks; 02.12.2014