You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#.NET单元测试中如何创建SQLite数据库并在类测试完成后删除

C#.NET 单元测试类级别SQLite测试库实现方案

核心逻辑是绑定测试类的全生命周期:测试类第一次加载时执行一次数据库创建、测试数据/文件导入,类下所有测试方法执行完毕后触发一次资源释放、数据库文件删除,既不需要每个测试方法重复建库拖慢速度,也能保证类内测试的上下文一致。

不同单元测试框架的生命周期钩子不一样,按你用的框架选对应实现就行:


xUnit 实现(.NET生态最常用)

xUnit 原生没有类级别的Setup/Teardown特性,需要通过IClassFixture实现类范围的共享上下文,fixture的Dispose方法会在类内所有测试跑完后自动执行:

  1. 先写独立的数据库生命周期管理类,把建库、初始化、清理逻辑全放进去
using Microsoft.Data.Sqlite;
using System.IO;

public class SharedSqliteDbFixture : IDisposable
{
    public string DbFilePath { get; }
    public SqliteConnection DbConnection { get; }

    public SharedSqliteDbFixture()
    {
        // 用GUID生成随机库名,避免并行测试时文件冲突
        DbFilePath = Path.Combine(Path.GetTempPath(), $"unit_test_{Guid.NewGuid():N}.sqlite");
        DbConnection = new SqliteConnection($"Data Source={DbFilePath}");
        DbConnection.Open();

        // 以下替换成你自己的初始化逻辑:建表、导入测试文件、插入种子数据
        var initCmd = DbConnection.CreateCommand();
        initCmd.CommandText = @"
            CREATE TABLE AppFiles (
                Id INTEGER PRIMARY KEY AUTOINCREMENT,
                FileName TEXT NOT NULL,
                FileContent BLOB NOT NULL,
                UploadTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
            );
            -- 示例:插入测试文件数据
            INSERT INTO AppFiles (FileName, FileContent) 
            VALUES ('test_contract.pdf', @fileData);
        ";
        // 读本地测试文件转字节数组存库
        var testFileBytes = File.ReadAllBytes("./TestAssets/test_contract.pdf");
        initCmd.Parameters.AddWithValue("@fileData", testFileBytes);
        initCmd.ExecuteNonQuery();
    }

    public void Dispose()
    {
        // 先释放连接,解除SQLite对文件的占用
        DbConnection.Close();
        DbConnection.Dispose();
        // 最后删除测试库文件
        if (File.Exists(DbFilePath))
        {
            File.Delete(DbFilePath);
        }
    }
}
  1. 测试类实现IClassFixture<SharedSqliteDbFixture>接口,通过构造函数注入fixture,类内所有测试方法自动共享同一个数据库实例:
public class FileServiceTests : IClassFixture<SharedSqliteDbFixture>
{
    private readonly SharedSqliteDbFixture _testDb;

    public FileServiceTests(SharedSqliteDbFixture testDb)
    {
        _testDb = testDb;
    }

    [Fact]
    public void GetFileById_WhenIdExists_ReturnsCorrectFile()
    {
        // 直接用_testDb.DbConnection写测试逻辑即可
        // 你的业务代码也可以把这个连接传给数据访问层做测试
    }

    [Fact]
    public void DeleteFile_WhenValidId_RemovesRecordFromDb()
    {
        // 第二个测试方法复用同一个库,不会重复初始化
    }
}

NUnit 实现

NUnit 自带类级别生命周期特性,直接用[OneTimeSetUp]标记类初始化方法,[OneTimeTearDown]标记类清理方法即可,不需要额外写fixture类:

using Microsoft.Data.Sqlite;
using NUnit.Framework;
using System.IO;

[TestFixture]
public class FileServiceNUnitTests
{
    private string _dbPath;
    private SqliteConnection _testConn;

    [OneTimeSetUp]
    public void RunBeforeAllTestsInClass()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"nunit_test_{Guid.NewGuid():N}.sqlite");
        _testConn = new SqliteConnection($"Data Source={_dbPath}");
        _testConn.Open();
        // 这里写建表、导入测试文件、插入种子数据的逻辑
    }

    [Test]
    public void TestCase1()
    {
        // 测试逻辑
    }

    [OneTimeTearDown]
    public void RunAfterAllTestsInClass()
    {
        _testConn.Close();
        _testConn.Dispose();
        if (File.Exists(_dbPath)) File.Delete(_dbPath);
    }
}

MSTest 实现

MSTest 用静态的[ClassInitialize]和[ClassCleanup]特性实现类级别的生命周期,注意这两个标记的方法必须是静态方法:

using Microsoft.Data.Sqlite;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;

[TestClass]
public class FileServiceMSTests
{
    private static string _dbPath;
    private static SqliteConnection _testConn;

    [ClassInitialize]
    public static void InitAllTests(TestContext ctx)
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"mstest_{Guid.NewGuid():N}.sqlite");
        _testConn = new SqliteConnection($"Data Source={_dbPath}");
        _testConn.Open();
        // 初始化库结构、测试数据
    }

    [TestMethod]
    public void TestCase1()
    {
        // 测试逻辑
    }

    [ClassCleanup]
    public static void CleanupAllTests()
    {
        _testConn.Close();
        _testConn.Dispose();
        if (File.Exists(_dbPath)) File.Delete(_dbPath);
    }
}

注意事项

  • 不要给测试库写固定文件路径,一定要用GUID这类随机值生成唯一文件名,不然开启测试并行执行时会出现文件占用、数据互相污染的问题
  • 清理数据库前必须先关闭并释放数据库连接,SQLite是文件型数据库,连接未释放时操作系统会锁定文件,导致删除失败
  • 如果你的测试需要每个方法跑之前都重置数据,把类级别的初始化/清理换成方法级别的(xUnit用构造函数/IDisposable、NUnit用[SetUp]/[TearDown]、MSTest用[TestInitialize]/[TestCleanup])即可,当前类级别的方案完全匹配你“所有测试跑完再删库”的需求
  • 测试要用到的文件建议单独放TestAssets目录,初始化时读成字节数组写入BLOB字段,不要硬编码文件内容

内容的提问来源于stack exchange,提问作者Jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:54:26