19

最终编辑:我找到了问题的解决方案(在问题的底部)。

我有一个让我很伤心的 Nunit 问题。编辑:实际上它看起来更像是一个 SQLite 问题,但我还不是 100% 确定。

我的 TestFixture 有一个设置,它生成一个随机文件名,在我的每个测试中用作 SQLite 数据库。

[Setup]
public void Setup()
{
    // "filename" is a private field in my TestFixture class
    filename = ...; // generate random filename
}

我的每个测试都在访问数据库的每个方法中使用此构造:

[Test]
public void TestMethod()
{
    using (var connection = Connect())
    {
        // do database activity using connection

        // I've tried including this line but it doesn't help
        // and is strictly unnecessary:
        connection.Close();
    }
}

private DbConnection Connect()
{
    var connection = DbProviderFactories.GetFactory("System.Data.SQLite").CreateConnection();
    connection.ConnectionString = "Data Source=" + filename;
    connection.Open();
    return connection;
}

因此,Connect()所有方法都使用一种辅助方法。我假设using() { }构造Dispose()在结束时调用连接TestMethod()并释放与 SQLite 数据库文件的连接。

我遇到的问题在于我的 [TearDown] 方法:

    [TearDown]
    public void Cleanup()
    {
        File.Delete(filename); // throws an IOException!
    }

每次测试我都会遇到一个异常:

System.IO.IOException: The process cannot access the file 'testdatabase2008-12-17_1030-04.614065.sqlite' because it is being used by another process.

所有的测试在到达 [TearDown] 时都失败了,所以我最终得到了一个充满临时数据库文件的目录(每个测试一个,每个都有不同的名称)和一大堆失败的测试。

什么进程正在访问该文件?我不明白第二个进程如何访问该文件。在connection我尝试删除文件时,它已完全超出范围并已被 Dispose()d,因此它不能与 SQLite 相关。它可以?

请注意,如果我运行所有测试或仅运行一个测试,我会得到相同的结果。

更新:所以我也尝试了我的 DbCommand 对象的 Dispose()ing,因为我没有这样做(我假设 Dispose()ing DbConnection 的所有其他 ADO.NET 提供程序也 Dispose()s 该连接上的任何命令.) 所以现在它们看起来像:

[Test]
public void TestMethod()
{
    using (var connection = Connect())
    {
        using (var command = connection.CreateCommand())
        {
        // do database activity using connection

        }
    }
}

它没有任何区别—— File.Delete() 行仍然抛出一个 IOException。:-(

如果我删除 [TearDown] 中的那一行,那么我的所有测试都会通过,但我会留下一大堆临时数据库文件。

另一个更新: 这很好用:

var filename = "testfile.sqlite";
using (var connection = BbProviderFactories.GetFactory("System.Data.SQLite").CreateConnection())
{
    connection.ConnectionString = "Data Source=" + filename;
    connection.Open();
    var createCommand = connection.CreateCommand();
    createCommand.CommandText =
        "CREATE TABLE foo (id integer not null primary key autoincrement, bar text not null);";
    createCommand.ExecuteNonQuery();
    var insertCommand = connection.CreateCommand();
    insertCommand.CommandText = "INSERT INTO foo (bar) VALUES (@bar)";
    insertCommand.Parameters.Add(insertCommand.CreateParameter());
    insertCommand.Parameters[0].ParameterName = "@bar";
    insertCommand.Parameters[0].Value = "quux";
    insertCommand.ExecuteNonQuery();
}
File.Delete(filename);            

我不明白!

更新:找到解决方案:

    [TearDown]
    public void Cleanup()
    {
        GC.Collect();
        File.Delete(filename);
    }

我通过调试器运行单元测试,当[TearDown]方法启动时,肯定不再有对 SQLite DbConnection 的引用。但是,强制 GC 必须清理它们。SQLite 中一定有一个错误。

4

11 回答 11

9

感谢您在底部发布的答案。我为完全相同的案例挖掘了几个小时

GC.Collect ();
GC.WaitForPendingFinalizers ();

成功了。

于 2012-09-14T11:58:07.170 回答
1

try calling Close on the dbconnection

make sure the sqllite process is terminated

you can see what process has your file locked with the Unlocker (free) utility

this may be a 'known' issue with SqlLite; the forum banter suggests closing the connection and disposing the command, and suggests that this will be fixed in a future version (since this behavior is not consistent with other ADO providers)

于 2008-12-17T13:54:54.010 回答
1

我知道这个答案迟了一年多,但对于任何将来阅读的人......

我遇到了与您类似的问题 - 由于 SQLite 数据库文件保持打开状态,尝试在测试之间删除测试数据库失败。我将代码中的问题追溯到未明确关闭的 SQLiteDataReader 对象。

SQLiteDataReader dr = cmd_.ExecuteReader();
while (dr.Read())
{
    // use the DataReader results
}
dr.Close();  //  <-- necessary for database resources to be released
于 2010-01-25T11:50:00.207 回答
1

调用静态方法

SqliteConnection.ClearAllPools()

在此调用之后,数据库文件被解锁,您可以在 [TearDown] 中删除该文件。

于 2010-10-21T19:14:04.107 回答
0

Tear down is executed after each test.

This attribute is used inside a TestFixture to provide a common set of functions that are performed after each test method is run.

You should try to delete them all with TestFixtureTearDown:

    [TestFixtureTearDown]
    public void finish()
    {
        //Delete all file
    }

Maybe one test is using the file that you try to delete in an other test. <-- [Stewart] As I stated in the question, it happens when I run only one test, so this isn't possible.

Update You do not give enough information about what you are doing. Have you try to clean up all the Test file with only 1 test in your test file and try it? [Stewart] Yes. If it works [Stewart] (it doesn't) then it's that you have multiple test problem (they access each other). You need to cut down the problem to find the source. Then, come back here we will help you. for the moment it's only guessing that we can give you. [Stewart] I have already done these cut-downs of the problem that you suggest, you'll find that they're in my original question!

于 2008-12-17T14:07:45.077 回答
0

你有杀毒软件运行吗?一些 AV 产品在看到文件被关闭时会扫描文件。如果 AV 软件在您删除它时仍然打开文件,您会看到这个问题。您可以在稍作延迟后重试删除。

我也看到搜索索引器会发生这种情况。

于 2008-12-17T15:36:54.413 回答
0

第一步是找出谁在持有相关文件的句柄。

获取Process Explorer的副本运行它。按 Ctrl+F 并输入文件名。它会向您显示访问它的进程列表。

于 2008-12-17T15:50:02.687 回答
0

Thanks for pointing this out!

The problem is not related to SQLite, but rather to memory management in general. After your test has run, the objects pointing to files have no scope anymore, but they still exist in memory.

Hence, there's still references to the files and you can't delete / move them.

于 2009-07-02T09:04:25.340 回答
0

你用什么来打开你的数据库?您是否使用此处的 ADO 2.0 连接器。如果有一个使用它的应用程序,我可以与它进行多个连接(关闭/打开)。如果你不使用这个连接器,你可以试一试。你的方法 Connect() 返回什么?

于 2008-12-17T16:58:11.427 回答
0

我需要查看您的文件名创建逻辑,但您可能正在打开文件来创建它,但在创建后没有关闭它。我认为如果您使用 System.IO.Path.GetTempFileName() 它只会创建文件并在文件关闭的情况下返回文件的名称。如果您正在生成自己的随机名称并使用 File.Open,则需要确保之后将其关闭。

另一方面,我衷心建议采用模拟策略来抽象出数据库,而不是在单元测试中从实际数据库中读取/写入。单元测试的重点是它们应该非常快,而文件 I/O 会真正减慢这些速度。

于 2008-12-17T14:52:48.963 回答
0

文件是否可能正在关闭(即 SQLLite 不会立即释放它)?

您可以尝试将数据库关闭在延迟循环中(每次尝试之间可能有一秒钟),并且仅在循环中的几次(5 次左右)迭代后抛出异常。

于 2008-12-17T16:44:43.573 回答