8 Stimmen

Zufälliger Fehler beim Testen mit NHibernate auf einer in-Memory SQLite-Datenbank

Ich habe ein System, das, nachdem es eine Nachricht erhalten hat, diese in die Warteschlange einreiht (in eine Tabelle schreibt), und ein anderer Prozess fragt die Datenbank ab und nimmt sie aus der Warteschlange zur Verarbeitung. In meinen automatischen Tests habe ich die Operationen im selben Prozess zusammengeführt, kann aber (konzeptionell) die NH-Sessions aus den beiden Operationen nicht zusammenführen.

Natürlich treten Probleme auf.

Ich habe alles gelesen, was ich über die Kombination SQLite-InMemory-NHibernate in der Testwelt finden konnte, aber jetzt bin ich auf zufällig fehlschlagende Tests gestoßen, aufgrund von "no such table" Fehlern. Um es klar zu machen - "zufällig" bedeutet, dass der gleiche Test mit der genau gleichen Konfiguration und dem gleichen Code manchmal fehlschlägt.

Ich habe die folgende SQLite-Konfiguration:

return SQLiteConfiguration
 .Standard
 .ConnectionString(x => x.Is("Data Source=:memory:; Version=3; New=True; Pooling=True; Max Pool Size=1;"))
 .Raw(NHibernate.Cfg.Environment.ReleaseConnections, "on_close");

Am Anfang meines Tests (jedes Tests) hole ich den "statischen" Sitzungsanbieter ab und bitte höflich darum, die bestehende DB zu löschen und das Schema neu zu erstellen:

public void PurgeDatabaseOrCreateNew()
{
    using (var session = GetNewSession())
    using (var tx = session.BeginTransaction())
    {
            PurgeDatabaseOrCreateNew(session);
            tx.Commit();
    }
}

private void PurgeDatabaseOrCreateNew(ISession session)
{
    // http://ayende.com/Blog/archive/2009/04/28/nhibernate-unit-testing.aspx
    new SchemaExport(_Configuration)
        .Execute(false, true, false, session.Connection, null);
}

Ja, es ist eine andere Sitzung, aber die Verbindung wird bei SQLite gepoolt, daher wird die nächste Sitzung, die ich erstelle, das generierte Schema sehen. Aber während es die meiste Zeit funktioniert - manchmal wird die spätere "enqueue" Operation scheitern, weil sie keine Tabelle für meine eingehenden Nachrichten sehen kann. Außerdem - das scheint höchstens einmal oder zweimal pro Test-Suite-Lauf zu passieren; nicht alle Tests scheitern, nur der erste (und manchmal ein anderer. Ich bin mir nicht ganz sicher, ob es der zweite ist oder nicht).

Der schlimmste Teil ist die Zufälligkeit, natürlich. Ich habe mir mehrmals gesagt, dass ich das jetzt behoben habe, einfach weil es einfach aufgehört hat zu scheitern. Zufällig.

Dies geschieht auf FW4.0, System.Data.SQLite x86 Version, Win7 64b und 2008R2 (insgesamt drei verschiedene Maschinen), NH2.1.2, konfiguriert mit FNH, auf TestDriven.NET 32b-Prozessen und NUnit-Konsole 32b-Prozessen.

Hilfe?

8voto

dvdvorle Punkte 911

Hallo, ich bin mir ziemlich sicher, dass ich das genau gleiche Problem wie du habe. Ich öffne und schließe mehrere Sitzungen pro Integrationstest. Nachdem ich mich durch das SQLite-Verbindungspooling gearbeitet und einige Experimente durchgeführt habe, bin ich zu folgendem Schluss gekommen:

Der SQLite-Pooling-Code speichert die Verbindung mit WeakReferences im Cache, was nicht die beste Option für das Caching ist, da die Verbindung(en) gelöscht wird, wenn es keinen normalen (starken) Verweis auf die Verbindung gibt und der GC ausgeführt wird. Da man nicht vorhersagen kann, wann der GC ausgeführt wird, erklärt dies die "Zufälligkeit". Versuche mal, zwischen dem Schließen einer Sitzung und dem Öffnen einer anderen Sitzung ein GC.Collect(); hinzuzufügen, dein Test wird immer fehlschlagen.

Meine Lösung war, die Verbindung selbst zwischen dem Öffnen von Sitzungen zu zwischenspeichern, so wie hier:

public class BaseIntegrationTest
{
    private static ISessionFactory _sessionFactory;
    private static Configuration _configuration;
    private static SchemaExport _schemaExport;

    // Ich speichere die gesamte Sitzung, weil ich nicht will, dass sie
    // und die zugrunde liegende Verbindung geschlossen werden.
    // Die "Connection"-Eigenschaft der ISession ist das, was wir wirklich wollen.
    // Die Verbindung über den NHibernate SQLite-Treiber abzurufen würde wahrscheinlich
    // auch funktionieren.
    private static ISession _keepConnectionAlive;

    static BaseIntegrationTest()
    {
        _configuration = new Configuration();
        _configuration.Configure();
        _configuration.AddAssembly(typeof(Product).Assembly);
        _sessionFactory = _configuration.BuildSessionFactory();
        _schemaExport = new SchemaExport(_configuration);

        _keepConnectionAlive = _sessionFactory.OpenSession();
    }

    [SetUp]
    protected void RecreateDB()
    {
        _schemaExport.Execute(false, true, false, _keepConnectionAlive.Connection, null);
    }

    protected ISession OpenSession()
    {
        return _sessionFactory.OpenSession(_keepConnectionAlive.Connection);
    }
}

Jeder meiner Integrationstests erbt von dieser Klasse und ruft OpenSession() auf, um eine Sitzung zu erhalten. RecreateDB wird von NUnit vor jedem Test aufgrund des [SetUp]-Attributs aufgerufen.

Ich hoffe, das hilft dir oder jemand anderem, der diesen Fehler erhält.

0voto

Toni Parviainen Punkte 2137

Nur das, was mir in den Sinn kommt, ist, dass du die Sitzung nach dem Test zufällig offen lässt. Du musst sicherstellen, dass jede vorhandene ISession geschlossen ist, bevor du eine andere öffnest. Wenn du nicht die using() Anweisung verwendest oder Dispose() manuell aufrufst, könnte die Sitzung möglicherweise noch irgendwo aktiv sein und diese zufälligen Ausnahmen verursachen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X