Създаване на хранилища за Unit Testing

Написах уеб приложение на 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();

Ако хранилищата имат само основни методи като Запазване, Актуализиране, Изтриване, Вмъкване, FindById и т.н.... как трябва да разбия заявката в хранилищата? Там също има много бизнес логика, като филтриране на пароли, до които потребителят има достъп, къде трябва да се намира?

Прочетох нещо за връщане на IQueryable обекти, така че LINQ заявките да могат да се променят в слоя на услугата... как ще изглежда това?


person binks    schedule 01.12.2014    source източник
comment
Гледахте ли Effort? effort.codeplex.com   -  person Bringer128    schedule 02.12.2014


Отговори (1)


Ако искате да пишете модулни тестове, целият този LINQ to Entities трябва да стои зад интерфейс,

e.g.

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

след това клас, който прилага този метод и съдържа LINQ и логиката за филтриране.

Сега в клиентските класове, които се нуждаят от достъп до него, предайте ICategoryRepository чрез конструктора (препоръчително). И за Unit Testing, предайте макетно хранилище в конструктора, който връща предварително дефиниран набор от резултати.

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

РЕДАКТИРАНЕ:

Или предайте подробностите, необходими на хранилището в метода, напр.:

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

Или, тъй като потребителят вероятно е в сайта, можете да подадете a

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 към конструктора на Repositories - 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
Всичко, което може да бъде логически обединено, трябва да бъде в собствен интерфейс/абстракция. първи принцип на SOLID, принцип на единична отговорност - person Michal Ciechan; 02.12.2014
comment
Така че, ако почти просто вкарам цялата тази LINQ заявка в GetCategoriesForParentId, това ще има смисъл в шаблона за проектиране на хранилище? - person binks; 02.12.2014