在不使用生產資料庫系統的情況下進行測試
在此頁面中,我們會討論撰寫自動化測試的技術,這些測試不會牽涉到應用程式在生產環境中執行的資料庫系統,方法是將資料庫交換為 測試雙精度浮 點數。 有不同類型的測試雙精度浮點數和方法,建議您徹底閱讀 選擇測試策略 ,以充分瞭解不同的選項。 最後,也可以針對生產資料庫系統進行測試:這涵蓋在針對生產資料庫系統 進行測試中 。
存放庫模式
如果您已決定撰寫測試而不涉及生產資料庫系統,則建議的作法是存放庫模式;如需此內容的詳細資訊,請參閱 本節 。 實作存放庫模式的第一個步驟是將 EF Core LINQ 查詢擷取到個別層,稍後我們會將其存根或模擬。 以下是部落格系統的存放庫介面範例:
public interface IBloggingRepository
{
Blog GetBlogByName(string name);
IEnumerable<Blog> GetAllBlogs();
void AddBlog(Blog blog);
void SaveChanges();
}
...以下是生產環境使用的部分範例實作:
public class BloggingRepository : IBloggingRepository
{
private readonly BloggingContext _context;
public BloggingRepository(BloggingContext context)
=> _context = context;
public Blog GetBlogByName(string name)
=> _context.Blogs.FirstOrDefault(b => b.Name == name);
// Other code...
}
它沒有太多:存放庫只會包裝 EF Core 內容,並公開執行資料庫查詢和更新的方法。 要注意的重點是,我們的 GetAllBlogs
方法會傳 IEnumerable<Blog>
回 ,而不是 IQueryable<Blog>
。 傳回後者表示查詢運算子仍可透過結果組成,而要求 EF Core 仍參與轉譯查詢;這會在第一個位置取代存放庫的目的。 IEnumerable<Blog>
可讓我們輕鬆地存根或模擬存放庫傳回的內容。
針對 ASP.NET Core 應用程式,我們需要將下列內容新增至應用程式的 ConfigureServices
,以將存放庫註冊為相依性插入中的服務:
services.AddScoped<IBloggingRepository, BloggingRepository>();
最後,我們的控制器會插入存放庫服務,而不是 EF Core 內容,並在其上執行方法:
private readonly IBloggingRepository _repository;
public BloggingControllerWithRepository(IBloggingRepository repository)
=> _repository = repository;
[HttpGet]
public Blog GetBlog(string name)
=> _repository.GetBlogByName(name);
此時,您的應用程式會根據存放庫模式來建構:與資料存取層 - EF Core 的唯一接觸點現在會透過存放庫層,在應用程式程式碼與實際資料庫查詢之間做為調解器。 測試現在只要擷取存放庫,或用您慣用的模擬程式庫進行模擬,即可撰寫測試。 以下是使用熱門 Moq 程式庫的模擬型測試範例:
[Fact]
public void GetBlog()
{
// Arrange
var repositoryMock = new Mock<IBloggingRepository>();
repositoryMock
.Setup(r => r.GetBlogByName("Blog2"))
.Returns(new Blog { Name = "Blog2", Url = "http://blog2.com" });
var controller = new BloggingControllerWithRepository(repositoryMock.Object);
// Act
var blog = controller.GetBlog("Blog2");
// Assert
repositoryMock.Verify(r => r.GetBlogByName("Blog2"));
Assert.Equal("http://blog2.com", blog.Url);
}
您可以在這裡 檢視 完整的範例程式碼。
SQLite 記憶體中的 SQLite
SQLite 可以輕鬆地設定為測試套件的 EF Core 提供者,而不是生產資料庫系統(例如 SQL Server):如需詳細資訊, 請參閱 SQLite 提供者檔。 不過,在測試時,通常最好使用 SQLite 的 記憶體內部資料庫 功能,因為它可在測試之間提供簡單的隔離,而且不需要處理實際的 SQLite 檔案。
若要使用記憶體內部 SQLite,請務必瞭解每當開啟低階連線時就會建立新的資料庫,並在該連接關閉時刪除。 在一般使用量中,EF Core 會 DbContext
視需要開啟並關閉資料庫連線,每次執行查詢時,以避免將連線保留不必要的時間。 不過,在記憶體內部 SQLite 中,這會導致每次重設資料庫;因此,為了因應措施,我們會在將連線傳遞至 EF Core 之前開啟連線,並只在測試完成時加以安排關閉:
public SqliteInMemoryBloggingControllerTest()
{
// Create and open a connection. This creates the SQLite in-memory database, which will persist until the connection is closed
// at the end of the test (see Dispose below).
_connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
// These options will be used by the context instances in this test suite, including the connection opened above.
_contextOptions = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(_connection)
.Options;
// Create the schema and seed some data
using var context = new BloggingContext(_contextOptions);
if (context.Database.EnsureCreated())
{
using var viewCommand = context.Database.GetDbConnection().CreateCommand();
viewCommand.CommandText = @"
CREATE VIEW AllResources AS
SELECT Url
FROM Blogs;";
viewCommand.ExecuteNonQuery();
}
context.AddRange(
new Blog { Name = "Blog1", Url = "http://blog1.com" },
new Blog { Name = "Blog2", Url = "http://blog2.com" });
context.SaveChanges();
}
BloggingContext CreateContext() => new BloggingContext(_contextOptions);
public void Dispose() => _connection.Dispose();
測試現在可以呼叫 CreateContext
,它會使用我們在建構函式中設定的連接來傳回內容,以確保我們有具有植入資料的全新資料庫。
您可以在這裡 檢視 SQLite 記憶體中測試的完整範例程式碼。
記憶體內部提供者
如測試概觀頁面中 所 討論,強烈建議不要使用記憶體內部提供者進行測試; 請考慮改 用 SQLite,或 實作存放庫模式 。 如果您決定使用記憶體內部,以下是一般測試類別建構函式,會在每次測試之前,先設定並植入新的記憶體內部資料庫:
public InMemoryBloggingControllerTest()
{
_contextOptions = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase("BloggingControllerTest")
.ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.Options;
using var context = new BloggingContext(_contextOptions);
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.AddRange(
new Blog { Name = "Blog1", Url = "http://blog1.com" },
new Blog { Name = "Blog2", Url = "http://blog2.com" });
context.SaveChanges();
}
您可以在這裡 檢視 記憶體內測試的完整範例程式碼。
記憶體內部資料庫命名
記憶體內部資料庫是透過簡單的字串名稱來識別,而且可以藉由提供相同的名稱來連線到相同的資料庫數次(這就是為什麼上述範例必須在每次測試之前呼叫 EnsureDeleted
的原因)。 不過,請注意,記憶體內部資料庫會根目錄在內容的內部服務提供者中;雖然在大部分情況下,內容會共用相同的服務提供者,但使用不同的選項設定內容可能會觸發使用新的內部服務提供者。 如果是這種情況,請針對應該共用記憶體內部資料庫的所有內容,明確傳遞 相同的 實例 InMemoryDatabaseRootUseInMemoryDatabase
(這通常是透過具有靜態 InMemoryDatabaseRoot
欄位來完成的)。
交易
請注意,根據預設,如果交易已啟動,記憶體內部提供者會擲回例外狀況,因為不支援交易。 您可能想要藉由將 EF Core 設定為忽略,以無訊息方式忽略 InMemoryEventId.TransactionIgnoredWarning
交易,如上述範例所示。 不過,如果您的程式碼實際上依賴交易式語意,例如取決於回復實際回復變更,您的測試將無法運作。
檢視
記憶體內部提供者允許透過 LINQ 查詢定義檢視,使用 ToInMemoryQuery :
modelBuilder.Entity<UrlResource>()
.ToInMemoryQuery(() => context.Blogs.Select(b => new UrlResource { Url = b.Url }));
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應