2

我正在使用 Castle ActiveRecord 进行持久性,并且我正在尝试为我的持久性测试编写一个基类,它将执行以下操作:

  • 为每个测试用例打开一个事务并在测试用例结束时将其回滚,这样我就可以为每个测试用例获得一个干净的数据库,而无需为每个测试用例重建架构。
  • 提供刷新我的 NHibernate 会话并在测试过程中获得一个新会话的能力,这样我就知道我的持久性操作确实影响了数据库,而不仅仅是 NHibernate 会话。

为了证明我的基类 ( ARTestBase) 有效,我提出了以下示例测试。

[TestFixture]
public class ARTestBaseTest : ARTestBase
{
    [Test]
    public void object_created_in_this_test_should_not_get_committed_to_db()
    {
        ActiveRecordMediator<Entity>.Save(new Entity {Name = "test"});

        Assert.That(ActiveRecordMediator<Entity>.Count(), Is.EqualTo(1));
    }

    [Test]
    public void object_created_in_previous_test_should_not_have_been_committed_to_db()
    {
        ActiveRecordMediator<Entity>.Save(new Entity {Name = "test"});

        Assert.That(ActiveRecordMediator<Entity>.Count(), Is.EqualTo(1));
    }

    [Test]
    public void calling_flush_should_make_nhibernate_retrieve_fresh_objects()
    {
        var savedEntity = new Entity {Name = "test"};
        ActiveRecordMediator<Entity>.Save(savedEntity);
        Flush();
        // Could use FindOne, but then this test would fail if the transactions aren't being rolled back
        foreach (var entity in ActiveRecordMediator<Entity>.FindAll())
        {
            Assert.That(entity, Is.Not.SameAs(savedEntity));
        }
    }
}

这是我在基类上的最大努力。它正确实现Flush()了,所以第三个测试用例通过了。但是它不会回滚事务,因此第二个测试失败。

public class ARTestBase
{
    private SessionScope sessionScope;
    private TransactionScope transactionScope;

    [TestFixtureSetUp]
    public void InitialiseAR()
    {
        ActiveRecordStarter.ResetInitializationFlag();
        ActiveRecordStarter.Initialize(typeof (Entity).Assembly, ActiveRecordSectionHandler.Instance);
        ActiveRecordStarter.CreateSchema();
    }

    [SetUp]
    public virtual void SetUp()
    {
        transactionScope = new TransactionScope(OnDispose.Rollback);
        sessionScope = new SessionScope();
    }

    [TearDown]
    public virtual void TearDown()
    {
        sessionScope.Dispose();
        transactionScope.Dispose();
    }

    protected void Flush()
    {
        sessionScope.Dispose();
        sessionScope = new SessionScope();
    }

    [TestFixtureTearDown]
    public virtual void TestFixtureTearDown()
    {
        SQLiteProvider.ExplicitlyDestroyConnection();
    }
}

请注意,我正在使用带有内存数据库的自定义 SQLite 提供程序。我的自定义提供程序取自这篇博文,它始终保持连接打开以维护架构。删除它并使用常规 SQL Server 数据库不会改变行为。

有没有办法实现所需的行为?

4

2 回答 2

1

Not too sure about ActiveRecord, but in NHibernate a transaction belongs to a session, not the otherway round.

If you've used ADO.Net a lot, this will make more sense, as to create an IDbTransaction you need to use the connection. ActiveRecord's TransactionScope (and NHibnerate's ITransaction) essentially wrap an IDbTransaction, so you need to create the SessionScope before the TransactionScope.

What you might also find (depending on if you're using NHibernate 1.2 GA or NHibernate 2.*, and what FlushMode your SessionScope has) is that your call to FindAll() may cause the session to flush anyway, as NHibernate will realise that it can't retrieve the correct data without actioning the last call to Save.

This all said and done, have you tried using SessionScope.Flush() instead of creating a new SessionScope?

于 2008-11-03T10:49:47.917 回答
0

使用SessionScope.Flush()使我的第三次测试失败。据我了解,Flush()执行 SQL 将我的记录推送到数据库中,但不会从会话中驱逐对象。这符合你所说的FindAll()引起冲洗的说法。

我真正想要的是SessionScope.Flush()(将数据库的状态与会话同步)加上SessionScope.EvictAll()(以确保我在后续查询中获得新的对象)。我new SessionScope()的尝试是模拟EvictAll().

您对包含交易的会话的评论而不是相反的评论确实给了我一个想法。我不确定在 flushedSessionScope内部 创建一个 new并期望它参与事务是多么的洁净,但它似乎有效:TransactionScopeSessionScope

public abstract class ARTestBase
{
    private SessionScope sessionScope;
    private TransactionScope transactionScope;
    private bool reverse;
    private IList<SessionScope> undisposedScopes;

    [TestFixtureSetUp]
    public void InitialiseAR()
    {
        ActiveRecordStarter.ResetInitializationFlag();
        ActiveRecordStarter.Initialize(typeof (Entity).Assembly, ActiveRecordSectionHandler.Instance);
        ActiveRecordStarter.CreateSchema();
        InitialiseIoC();
        undisposedScopes = new List<SessionScope>();
    }

    [SetUp]
    public virtual void SetUp()
    {
        sessionScope = new SessionScope();
        transactionScope = new TransactionScope(OnDispose.Rollback);
        transactionScope.VoteRollBack();
        base.CreateInstanceUnderTest();
        reverse = false;
    }

    [TearDown]
    public virtual void TearDown()
    {
        if (reverse)
        {
            sessionScope.Dispose();
            transactionScope.Dispose();
        }
        else
        {
            transactionScope.Dispose();
            sessionScope.Dispose();
        }
    }

    [TestFixtureTearDown]
    public virtual void TestFixtureTearDown()
    {
        foreach (var scope in undisposedScopes)
        {
            scope.Dispose();
        }
        SQLiteProvider.ExplicitlyDestroyConnection();
    }

    protected void Flush()
    {
        reverse = true;
        sessionScope.Flush();
        undisposedScopes.Add(sessionScope);
        sessionScope = new SessionScope();
    }
}

进一步考虑,这将不允许您在每个测试用例中多次刷新。我想我可以通过更仔细地跟踪范围来处理这个问题。我以后可能会研究一下。

于 2008-11-03T13:00:56.217 回答