在我的单元测试中,我当前正在制作 SQLite 文件的临时副本,运行我正在测试的方法,然后删除该文件。
我遇到的问题是,当我使用同一文件进行多个单元测试时,测试要么全部通过,要么只有其中一个测试失败并出现错误
System.IO.IOException : The process cannot access the file 'C:\~\temp_飲newKanji_食欠人良resourceKanji_decks.anki2' because it is being used by another process.
。我什至连续运行每个测试 20 次,以确保它不是随机地自行失败,而且它们都通过了。
我遇到了类似的问题,在尝试删除临时文件后会出现此错误,并且测试总是失败,即使是单独测试。事实证明,问题是 EF 一直保持连接,即使在我处理它之后也是如此,我可以通过在
SqliteConnection.ClearPool()
之前调用 _context.Dispose()
来修复它。
堆栈跟踪显示它在删除临时文件行仍然失败,但我不确定还有什么可能导致它。此外,也许第一个单元测试是如何保持文件打开的,但如果是这种情况,当它尝试在自己的单元测试中删除文件时,它也应该失败。
我能找到的唯一替代方法是将文件复制到内存中,这不应该使任何文件保持打开状态,但我似乎不知道如何实现这一点。我所看到的有关内存中 SQLite 的所有内容都有
new DbContextOptionsBuilder<YourDbContext>().UseSqlite("DataSource=:memory:").Options
,然后手动添加所有数据,但我找不到任何有关如何使用现有 SQLite 文件中的数据进行设置的示例。我该怎么做呢?
我正在使用 C#、.NET 8、EF Core 8 Sqlite 和 xUnit。
public class Anki2Context : DbContext, IAnki2Context
{
public DbSet<Card> Cards { get; protected set; }
public DbSet<Deck> Decks { get; protected set; }
public DbSet<Note> Notes { get; protected set; }
public Anki2Context(string dbPath) : base(GetOptions(dbPath))
{
}
public Anki2Context(DbContextOptions<Anki2Context> options) : base(options)
{
}
private static DbContextOptions<Anki2Context> GetOptions(string dbPath)
{
return new DbContextOptionsBuilder<Anki2Context>()
.UseSqlite($"Data Source={dbPath}")
.Options;
}
}
public class Anki2Controller : IDisposable
{
private readonly Anki2Context _context;
public Anki2Controller(Anki2Context context)
{
_context = context;
}
public Anki2Controller(string dbPath) : this(new Anki2Context(dbPath))
{
}
public void Dispose()
{
SqliteConnection.ClearPool((SqliteConnection) _context.Database.GetDbConnection());
_context.Dispose();
}
}
[Theory]
[InlineData("飲newKanji_食欠人良resourceKanji_decks.anki2", new[] { 1707169497960, 1707169570657, 1707169983389, 1707170000793 }, 1707160682667)]
public void Move_notes_between_decks(string anki2File, long[] noteIdsToMove, long deckIdToMoveTo)
{
//Arrange
string originalInputFilePath = _anki2FolderPath + anki2File;
string tempInputFilePath = _anki2FolderPath + "temp_" + anki2File;
File.Copy(originalInputFilePath, tempInputFilePath, true);//Copy the input file to prevent changes between unit tests
Anki2Controller anki2Controller = new Anki2Controller(tempInputFilePath);
List<Card> originalNoteDeckJunctions = anki2Controller.GetTable<Card>()
.Where(c => noteIdsToMove.Contains(c.NoteId))
.ToList();//Grab the current note/deck relations for the give note ids
//Act
bool movedNotes = anki2Controller.MoveNotesBetweenDecks(noteIdsToMove, deckIdToMoveTo);
//Assert
movedNotes.Should().BeTrue();//Function completed successfully
List<Card> finalNoteDeckJunctions = anki2Controller.GetTable<Card>()
.Where(c => noteIdsToMove.Contains(c.NoteId))
.ToList();//Grab the current note/deck relations for the give note ids after running the function
finalNoteDeckJunctions.Count().Should().Be(originalNoteDeckJunctions.Count());//No note/deck relations should have been removed/added
finalNoteDeckJunctions.Select(c => c.DeckId).Should().AllBeEquivalentTo(deckIdToMoveTo);//All junction deckIds should be the given deckId
//Cleanup
anki2Controller.Dispose();
File.Delete(tempInputFilePath);
}
这是将 sqlite 从物理文件复制到内存中的方法:
创建Fixture类:
public class TestDatabaseFixture: IDisposable
{
private static readonly object _lock = new();
private static bool _databaseInitialized;
private const string SourceFile = @"This is absolute path to Sqlite file";
private readonly SqliteConnection? _inMemoryDbConnection;
private readonly DbContextOptions<ApplicationDbContext>? _contextOptions;
public TestDatabaseFixture()
{
lock (_lock)
{
if (!_databaseInitialized)
{
_inMemoryDbConnection = new SqliteConnection(String.Format("Data Source = {0}", ":memory:"));
_inMemoryDbConnection.Open();
using (SqliteConnection source = new SqliteConnection(String.Format("Data Source = {0}", SourceFile)))
{
source.Open();
source.BackupDatabase(_inMemoryDbConnection);
}
_contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(_inMemoryDbConnection)
.Options;
_databaseInitialized = true;
}
}
}
public ApplicationDbContext CreateContext()
=> new ApplicationDbContext(_contextOptions);
public void Dispose()
{
_inMemoryDbConnection.Close();
_inMemoryDbConnection?.Dispose();
}
}
然后您可以在测试中使用 DbContext:
public class UnitTests1: IClassFixture<TestDatabaseFixture>
{
TestDatabaseFixture fixture;
public UnitTests1(TestDatabaseFixture fixture)
{
this.fixture = fixture;
}
[Fact]
public void Test1()
{
var context = fixture.CreateContext();
using var viewCommand = context.Database.GetDbConnection().CreateCommand();
viewCommand.CommandText = @"
SELECT count(*) FROM Artist;
";
var result = viewCommand.ExecuteScalar();
Assert.IsType<long>(result);
Assert.NotEqual(0, result);
}
}