Подтвердить что ты не робот

Вымывание транзакций базы данных?

У меня есть пара таблиц с родительскими/дочерними отношениями - инцидент и инцидент. У меня есть viewmodel, который содержит информацию из обеих этих таблиц. И у меня есть метод бизнес-уровня, которому передается экземпляр viewmodel, который должен обновлять обе таблицы.

Итак, в методе я использую новый механизм транзакций EF6:

using (var transaction = this.db.Database.BeginTransaction())
{
    try
    {
        // various database stuff
        this.db.SaveChanges();
        // more database stuff
        this.db.SaveChanges();
        // yet more database stuff
        this.db.SaveChanges();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        this.logger.logException(ex, "Exception caught in transaction, rolling back");
        throw;
    }
}

Итак, моя проблема. Как проверить это?

Я использую платформу тестирования модулей Microsoft с помощью Moq, и у меня не было проблем с издевательством DBContexts и DbSet < > s, но я не могу понять, как обойти транзакционные вещи.

Если я не пытаюсь издеваться над транзакцией, я получаю InvalidOperationException:

"В приложении не может быть найдена соединительная строка с именем xxx config."

Что имеет смысл - нет файла конфигурации приложения, и нет никакой базы данных.

Но если я попытаюсь высмеять BeginTransaction(), я получу ошибки инициализации: NotSupportedException:

"Неверная настройка для не виртуального элемента: m = > m.Database.BeginTransaction".

И это заставило меня преследовать сорняки, глядя на декомпилирующие методы .NET, пытаясь определить какой-то класс, который может быть получен из удобного интерфейса или что-то еще, где я мог бы каким-то образом ввести насмешливый объект.

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

У меня Googled вокруг, и не нашел ничего полезного. Кто-нибудь сталкивался с этой проблемой? У кого-нибудь есть идеи о том, как действовать?

4b9b3361

Ответ 1

Тестирование такого рода вещей всегда сложно, но прежде всего вы должны спросить себя, хотите ли вы unit test свою бизнес-логику или хотите интегрировать тестирование своего приложения.

Если вы хотите unit test свою логику, вы в принципе не должны даже пытаться фальсифицировать инфраструктуру сущности, потому что вы не хотите тестировать EF, вы просто хотите проверить свой код, правильно? Для этого высмеивайте любой объект доступа к данным и только unit test свою бизнес-логику.

Но если вы хотите проверить, работает ли ваш уровень доступа к данным, например, если ваш код может обрабатывать все выполненные вами CRUD-операции, вы должны выполнить тесты интеграции с реальной базой данных. Не пытайтесь издеваться над объектами доступа к данным (EF) в этом случае, просто выполните тесты с тестовой базой данных или sql-express localDB, например.

Ответ 2

Вы можете обернуть контекст и транзакцию в интерфейсе, а затем реализовать интерфейс с помощью некоторого класса поставщика:

public interface IDbContextProvider
{
    YourContext Context { get; set; }
    DbContextTransaction DbTransaction { get; set; }
    void Commit();
    void Rollback();
    void BeginTransaction();
    void SaveChanges();
}

а затем выполните его:

public class EfContextProvider : IDbContextProvider
{
    public EfContextProvider(YourContext context)
    {
        Context = context;
    }
    public YourContext Context { set; get; }
    public DbContextTransaction DbTransaction { set; get; }

    public void Commit()
    {
        DbTransaction.Commit();
    }

    public void Rollback()
    {
        DbTransaction.Rollback();
    }

    public void BeginTransaction()
    {
        DbTransaction=Context.Database.BeginTransaction();
    }

    public void SaveChanges()
    {
        Context.SaveChanges();
    }
}

теперь давайте вашему классу зависимость IDbContextProvider и работаем с ним (у него также есть контекст внутри). Возможно, замените используемый блок на _contextProvider.BeginTransaction(); а затем также _contextProvider.Commit(); или _contextProvider.Rollback();

Ответ 3

Я потратил несколько часов, пытаясь понять это, я полагал, что это может быть сделано MS Fakes напрямую без оболочки или нового класса.

Вам нужно выполнить три шага:

  • Создайте объект shim для DbContextTransaction и объедините его методы Commit и Rollback, чтобы ничего не делать.
  • Создайте объект shim для базы данных. И объедините его метод BeginTransaction, чтобы вернуть объект Shim объекта DbContextTransaction, созданный на шаге 1.
  • Свойство Detour DbContext.Database для всех экземпляров, чтобы вернуть объект Shim базы данных, созданный на шаге 2.

И все.

    static void SetupDBTransaction()
    {
        System.Data.Entity.Fakes.ShimDbContextTransaction transaction = new System.Data.Entity.Fakes.ShimDbContextTransaction();
        transaction.Commit = () => { };
        transaction.Rollback = () => { };

        System.Data.Entity.Fakes.ShimDatabase database = new System.Data.Entity.Fakes.ShimDatabase();
        database.BeginTransactionIsolationLevel = (isolationLevel) =>{return transaction.Instance;};

        System.Data.Entity.Fakes.ShimDbContext.AllInstances.DatabaseGet = (@this) => { return database.Instance; };
    }

Ответ 4

Вы можете представлять классы EF как классы POCO и изолировать все взаимодействия с базами данных в классах адаптеров базы данных. У этих классов адаптеров был бы интерфейс, который вы могли бы высмеять при тестировании бизнес-логики.

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

Итак, как насчет тестирования бизнес-кода, заключенного в транзакции?

Чтобы изолировать бизнес-код от адаптеров базы данных, вам нужно будет создать интерфейс для области транзакций EF, который вы можете высмеять.

Я ранее работал с таким дизайном, хотя не с EF, но с аналогичной упаковкой POCO (в псевдо С#, а не с синтаксисом или проверкой здравого смысла):

interface IDatabaseAdapter 
{
    ITransactionScope CreateTransactionScope();
}

interface ITransactionScope : IDisposable
{
    void Commit();
    void Rollback();        
}

class EntityFrameworkTransactionScope : ITransactionScope
{
    private DbContextTransaction entityTransaction;
    EntityFrameworkTransactionScope(DbContextTransaction entityTransaction)
    {
        this.entityTransaction = entityTransaction;
    }

    public Commit() { entityTransaction.Commit(); }
    public Rollback() { entityTransaction.Rollback(); }
    public Dispose() { entityTransaction.Dispose(); }

}

class EntityFrameworkAdapterBase : IDatabaseAdapter
{
   private Database database;
   protected EntityFrameworkAdapterBase(Database database)
   {
       this.database = database;
   }

   public ITransactionScope CreateTransactionScope()
   {
       return new EntityFrameworkTransactionScope(database.BeginTransaction());
   }
}

interface IIncidentDatabaseAdapter : IDatabaseAdapter
{
    SaveIncident(Incident incident);
}

public EntityIncidentDatabaseAdapter : EntityFrameworkAdapterBase, IIncidentDatabaseAdapter
{
    EntityIncidentDatabaseAdapter(Database database) : base(database) {}

    SaveIncident(Incident incident)
    {
         // code for saving the incident
    }
}

Вышеупомянутая конструкция должна позволять вам создавать unit test для операций с инфраструктурой сущностей, не беспокоясь о бизнес-логике или транзакции, а также создавать модульные тесты для бизнес-логики, где вы можете издеваться над сбоями базы данных и использовать MOQ или аналогичные для проверки того, что откат находится в факт вызвал ваш ITransactionScope макет. Что-то вроде выше, вы должны быть способны покрыть практически любую транзакционную неудачу на любом этапе бизнес-логики, о котором вы можете думать.

Конечно, вы должны дополнить свои модульные тесты хорошими интеграционными тестами, поскольку транзакции могут быть сложными, особенно сложные взаимоблокировки могут возникать при одновременном использовании, и их трудно поймать в издеваемом тесте.

Ответ 5

Что вам нужно - это то, что вы можете называть Commit() и Rollback() и имеет форму System.Data.Entity.DbContextTransaction, правильно? Таким образом, оказывается, что вы можете использовать реальное DbContextTransaction в ЛЮБОЙ реальной базе данных. Затем, пока ни один из ваших тестовых кодов не внесет никаких реальных изменений в базу данных, используемую для транзакции, Commit() или Rollback() будут успешными и ничего не сделают.

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

var scope = (new PConn.DataAccess.PressConnEntities()).Database.BeginTransaction();
var bizl = new Mock<IOrderMgr>();
bizl.Setup(m => m.CreateNewOrder(7, It.IsAny<string>(), It.IsAny<string>())).Returns(_testOrder1);
// .GetOrdersQuery(channel, beginUTC, endUTC);
bizl.Setup(m => m.GetOrdersQuery(7, It.IsAny<DateTime>(), It.IsAny<DateTime>())).Returns(matchedOrdersList.AsQueryable());
bizl.Setup(m => m.BeginTransaction()).Returns(scope);

Для проблемы, которую вы пытаетесь решить, важна только первая строка фрагмента кода и последняя строка.

Подводя итог:

  • Измените свой код, чтобы получить транзакцию из метода, который вы можете высмеять в тестах.
  • Возвратите реальную транзакцию в тестовую базу данных, когда вы издеваетесь над методом, который возвращает транзакцию.
  • Убедитесь, что ваш тестовый файл App.Config имеет законную конфигурацию для подключения к тестовой базе данных.
  • Commit() и откат() для вашего сердечного контента. Никакие данные не изменяются, потому что вы издевались над всеми операциями DbSet и DbContext.

Вот пример тестируемого кода, в котором я использую (не очень) фальшивую транзакцию:

using (var scope = this.OrderManager.BeginTransaction())
{
    PrintOrder pconnOrder = this.OrderManager.CreateNewOrder(channel, payload, claimsIdentity.Name);
    bool parseResult = this.OrderManager.ParseNewOrder(pconnOrder, claimsIdentity.Name, out parseErrorMessage);

    if (!parseResult)
    {
        // return a fault to the caller
        HttpResponseMessage respMsg = new HttpResponseMessage(HttpStatusCode.BadRequest);
        respMsg.Content = new StringContent(parseErrorMessage);

        throw (new HttpResponseException(respMsg));
    }

    scope.Commit();
    return (pconnOrder.PrintOrderID);
}