RavenDb:在单元测试时强制索引等到不陈旧

Posted

技术标签:

【中文标题】RavenDb:在单元测试时强制索引等到不陈旧【英文标题】:RavenDb : Force indexes to wait until not stale whilst unit testing 【发布时间】:2012-04-25 13:29:06 【问题描述】:

在使用 RavenDb 进行单元测试时,通常会检索或以其他方式处理新添加的数据。这可能导致“过时索引”异常,例如

Bulk operation cancelled because the index is stale and allowStale is false

根据一些答案

How should stale indexes be handled during testing? WaitForNonStaleResults per DocumentStore RavenDb : Update a Denormalized Reference property value

在处理查询或批处理操作之前强制数据库(IDocumentStore 实例)等到其索引不陈旧的方法是在IDocumentStore 初始化期间使用DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites,如下所示:

public class InMemoryRavenSessionProvider : IRavenSessionProvider

    private static IDocumentStore documentStore;

    public static IDocumentStore DocumentStore
    
        get  return (documentStore ?? (documentStore = CreateDocumentStore())); 
    

    private static IDocumentStore CreateDocumentStore()
    
        var store = new EmbeddableDocumentStore
        
            RunInMemory = true,
            Conventions = new DocumentConvention
            
                DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites,
                IdentityPartsSeparator = "-"
            
        ;
        store.Initialize();
        IndexCreation.CreateIndexes(typeof (RavenIndexes).Assembly, store);
        return store;
    

    public IDocumentSession GetSession()
    
        return DocumentStore.OpenSession();
    

很遗憾,上面的代码不起作用。我仍然收到有关过时索引的例外情况。这些可以通过放入包含.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()) 的虚拟查询来解决。

这很好,只要这些可以包含在单元测试中,但是如果它们不能包含呢?我发现这些 WaitForNonStaleResults* 调用正在悄悄进入生产代码,这样我就可以让单元测试通过。

那么,有没有一种可靠的方法,使用最新版本的 RavenDb,在允许处理命令之前强制索引更新 - 仅用于单元测试的目的?

编辑 1

这是基于下面给出的答案的解决方案,该解决方案强制等待索引不陈旧。为了单元测试的方便,我把它写成一个扩展方法;

public static class IDocumentSessionExt

    public static void ClearStaleIndexes(this IDocumentSession db)
    
        while (db.Advanced.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)
        
            Thread.Sleep(10);
        
    

这是一个单元测试,它使用了详细的WaitForNonStaleResultsAsOfLastWrite 技术,但现在使用了更简洁的扩展方法。

[Fact]
public void Should_return_list_of_Relationships_for_given_mentor()

    using (var db = Fake.Db())
    
        var mentorId = Fake.Mentor(db).Id;
        Fake.Relationship(db, mentorId, Fake.Mentee(db).Id);
        Fake.Relationship(db, mentorId, Fake.Mentee(db).Id);
        Fake.Relationship(db, Fake.Mentor(db).Id, Fake.Mentee(db).Id);

        //db.Query<Relationship>()
        //  .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
        //  .Count()
        //  .ShouldBe(3);

        db.ClearStaleIndexes();
        db.Query<Relationship>().Count().ShouldBe(3);
        MentorService.GetRelationships(db, mentorId).Count.ShouldBe(2);
    

【问题讨论】:

这现在是针对 DocumentStore 而不是 DocumentSession,因此扩展方法将更改为使用 db.Advanced.DocumentStore.DatabaseCommands.GetStatistics().StaleIndexes.Any() 之类的东西,或者只是如果可以的话,直接交出 DocumentStore 【参考方案1】:

如果您有 Map/Reduce 索引,DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites 将不起作用。您需要使用其他方法。

在您的单元测试中,在您插入任何数据后立即调用这样的代码,这将强制 所有 索引在您执行任何其他操作之前更新:

while (documentStore.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)

    Thread.Sleep(10);

更新如果您愿意,当然可以将其放入扩展方法中:

public static class IDocumentSessionExt

    public static void ClearStaleIndexes(this IDocumentSession db)
    
        while (db.Advanced.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)
        
            Thread.Sleep(10);
        
    

那么你可以说:

db.ClearStaleIndexes();

【讨论】:

我会尝试Thread.Sleep(10) 的想法。但是我有 Map 索引(它们有 map 函数但没有 reduce 函数),正是这些索引引发了 Bulk operation .. index is stale 异常。 ConsistencyOptions.QueryYourWrites 是否适用于仅地图索引? 应该可以,但是批量操作可能是问题所在。使用上面的代码确保所有索引都是最新的(在插入文档之后和查询之前),你应该没问题。 请参阅编辑 1。尽管您可能希望将答案更新为最新语法,但它很有效。将 wait 封装为扩展方法使一切变得整洁:-) 像这样的助手似乎包含在 Raven 的创建者的测试助手包中:ravendb.net/docs/samples/raven-tests/createraventests(我还没有使用它)【参考方案2】:

您实际上可以在 DocumentStore 上添加一个查询侦听器来等待非陈旧的结果。这可以仅用于单元测试,因为它在文档存储中,而不是每个操作。

// Initialise the Store.
var documentStore = new EmbeddableDocumentStore
                
                    RunInMemory = true
                ;
documentStore.Initialize();

// Force queries to wait for indexes to catch up. Unit Testing only :P
documentStore.RegisterListener(new NoStaleQueriesListener());

....


#region Nested type: NoStaleQueriesListener

public class NoStaleQueriesListener : IDocumentQueryListener

    #region Implementation of IDocumentQueryListener

    public void BeforeQueryExecuted(IDocumentQueryCustomization queryCustomization)
    
        queryCustomization.WaitForNonStaleResults();
    

    #endregion


#endregion

(无耻从RavenDB how to flush?盗取)

【讨论】:

我试过了,它适用于正常的 Query&lt;&gt;() 调用(无陈旧索引),但它对于通过 db.Advanced.DatabaseCommands.UpdateByIndex(..) 调用的 Map 索引(无 reduce)失败。这是预期的行为吗? Hrm,这与常规查询不同。我将不得不对此进行研究(或提出要实施的建议),因为应该有一种非侵入性的方式来做到这一点。 使用监听器肯定是比根据接受的答案手动调用扩展方法更好的低干预解决方案,但是到目前为止,DocumentStore 级别的修复似乎都不适用于批量更新,即当我通过UpdateByIndex(..) 使用索引。祝你好运! 有没有人找到适用于所有情况的全局、非侵入性解决方案?【参考方案3】:

请注意,StaleIndexes 还包括废弃和禁用的索引 - 它们永远不会更新。

所以为了避免无限等待,请改用这个属性:

 var staleIndices = store.DatabaseCommands.GetStatistics().CountOfStaleIndexesExcludingDisabledAndAbandoned;

【讨论】:

刚刚遇到了一个问题,因为 StaleIndexes 还包括被遗弃的索引。您的方法似乎可以解决它。

以上是关于RavenDb:在单元测试时强制索引等到不陈旧的主要内容,如果未能解决你的问题,请参考以下文章

Angular:运行Git push时如何强制运行单元测试?

Swift 单元测试 - 等到服务响应

对指令进行单元测试 - 无法强制使用虚假数据

我可以在运行 VS 单元测试时进行调试吗?

Swift之深入解析如何避免单元测试中的强制解析

23411之感