Разработайте класс для тестирования на модуле - PullRequest
3 голосов
/ 16 сентября 2011

Я собираюсь написать книгу Apress ASP.NET MVC 3 и пытаюсь обеспечить создание модульных тестов для всего возможного, но потратив большую часть дня, пытаясь понять, почему редактирование не будетсохранить (см. этот вопрос SO ) Я хотел создать модульный тест для этого.

Я решил, что мне нужно создать модульный тест для следующего класса:

public class EFProductRepository : IProductRepository {
    private EFDbContext context = new EFDbContext();

    public IQueryable<Product> Products {
        get { return context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            context.Products.Add(product);
        }
        context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        context.Products.Remove(product);
        context.SaveChanges();
    }
}

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}

Я использую Ninject.MVC3 и Moq и уже создал несколько модульных тестов (работая, хотя уже упоминавшуюся ранее книгу), поэтому медленно обдумываю это.Я уже (надеюсь, правильно) создал метод конструктора, чтобы позволить мне передать _context:

public class EFProductRepository : IProductRepository {
    private EFDbContext _context;

    // constructor
    public EFProductRepository(EFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        _context.SaveChanges();
    }
}

НО это - то, где у меня начинаются проблемы ... Я полагаю, что мне нужно создать Интерфейсдля EFDbContext (см. ниже), чтобы я мог заменить его на фиктивный репо для тестов, НО он построен на классе DbContext:

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}

из System.Data.Entity и не могуя работаю над тем, как создать для него интерфейс ... Если я создаю следующий интерфейс, я получаю ошибки из-за отсутствия метода .SaveChanges() из класса DbContext и не могу построить интерфейсиспользуя «DbContext», как «EFDbContext», так как это класс, а не интерфейс ...

using System;
using System.Data.Entity;
using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Concrete {
    interface IEFDbContext {
        DbSet<Product> Products { get; set; }
    }
}

Исходный источник можно получить из «Исходного кода / Загрузок» на thispage если я что-то пропустил во фрагментах кода выше (или просто спросите, и я добавлю это).

Я достиг предела того, что я понимаю, и не знаю, что я ищуили читать, я не могу понять, как пройти через это. Пожалуйста, помогите!

Ответы [ 2 ]

6 голосов
/ 17 сентября 2011

Проблема в том, что вы недостаточно абстрагировались.Задача абстракций / интерфейсов состоит в том, чтобы определить контракт, который демонстрирует поведение, не зависящее от технологии.

Другими словами, это хороший первый шаг, когда вы создали интерфейс для EFDbContext, но этот интерфейсвсе еще привязан к конкретной реализации - DbSet (DbSet).

Быстрое решение этой проблемы - выставить это свойство как IDbSet вместо DbSet.В идеале вы предоставляете что-то еще более абстрактное, например, IQueryable (хотя это не дает вам методы Add () и т. Д.).Чем абстрактнее, тем легче имитировать.

Затем вам остается выполнить остальную часть "контракта", на который вы полагаетесь, а именно метод SaveChanges ().

Ваш обновленный код будет выглядеть так:

public class EFProductRepository : IProductRepository {
    private IEFDbContext context;

    public EFProductRepository(IEFDbContext context) {
        this.context = context;
    }
    ...
}

public interface IEFDbContext {
   IDbSet<Product> Products { get; set; }
   void SaveChanges();
}

НО ... главный вопрос, который вы должны задать: что вы пытаетесь проверить (наоборот, что вы пытаетесьмакет / избежать тестирования)?Другими словами: пытаетесь ли вы проверить, как ваше приложение работает, когда что-то сохраняется, или вы тестируете фактическое сохранение .

Если вы просто тестируете, как работает ваше приложение и не заботитесь о фактическом сохранении в базе данных, я бы подумал о насмешках на более высоком уровне - IProductRepository.Тогда вы вообще не обращаетесь к базе данных.

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

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

2 голосов
/ 16 сентября 2011

Полагаю, ваш текущий код выглядит примерно так (я вставил в интерфейс):

public class EFProductRepository : IProductRepository {
    private IEFDbContext _context;

    // constructor
    public EFProductRepository(IEFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        **_context.SaveChanges();**
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        **_context.SaveChanges();**
    }
}

public class EFDbContext : DbContext, IEFDbContext {
    public DbSet<Product> Products { get; set; }
}

public interface IEFDbContext {
    DbSet<Product> Products { get; set; }
}

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

Определение метода SaveChanges в интерфейсе IEFDbContext решает вашу проблему:

public interface IEFDbContext {
    DbSet<Product> Products { get; set; }

    void SaveChanges();
}
...