C#.NET单元测试中如何创建SQLite数据库并在类测试完成后删除
C#.NET 单元测试类级别SQLite测试库实现方案
核心逻辑是绑定测试类的全生命周期:测试类第一次加载时执行一次数据库创建、测试数据/文件导入,类下所有测试方法执行完毕后触发一次资源释放、数据库文件删除,既不需要每个测试方法重复建库拖慢速度,也能保证类内测试的上下文一致。
不同单元测试框架的生命周期钩子不一样,按你用的框架选对应实现就行:
xUnit 实现(.NET生态最常用)
xUnit 原生没有类级别的Setup/Teardown特性,需要通过IClassFixture实现类范围的共享上下文,fixture的Dispose方法会在类内所有测试跑完后自动执行:
- 先写独立的数据库生命周期管理类,把建库、初始化、清理逻辑全放进去
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); } } }
- 测试类实现
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
相关产品推荐
相关产品推荐

