单元测试 ASP.NET 数据缓存






4.08/5 (8投票s)
一个用于在 SQL Server 中单元测试 ASP.NET 数据缓存的类。
引言
在本文中,我将描述一种在 SQL Server 中单元测试 ASP.NET 数据缓存的方法,该方法利用 TraceServer
类。
背景
在夏天,我花了大量时间构建应用程序层 API,其核心部分是基于从 SQL Server 数据库检索的数据创建对象的工厂。由于 API 必须构建为能够处理高负载,因此所有数据库请求都进行了缓存。
我尝试以测试驱动的方式构建 API,因此编写了 xUnit 测试。当我准备测试实现缓存的工厂方法时,我发现自己陷入了两难境地。我应该如何测试对象实际上被缓存并且每次都没有从数据库读取,以及我应该如何测试缓存的对象在需要时是否已从缓存中释放?
我脑海中闪过几个显而易见的解决方案。我可以让工厂暴露它们使用的缓存键,并在我的测试中查询缓存系统。这将使我能够检查对象是否实际存储在缓存中,并且在需要时它们已从缓存中释放。然而,这些测试将无法测试工厂实际上是否检查了缓存并返回缓存的对象,而不是从数据库构建新对象。这还要求我必须暴露工厂中的缓存键构建方法。
另一个可能的解决方案是修改我的工厂以跟踪对象何时从数据库或缓存中获取。虽然理论上我可以通过这种方法执行所有必要的测试,但这将迫使我在工厂中构建大量额外的逻辑,而这些逻辑仅在测试期间使用。我还怀疑这可能很容易出错。
解决方案
在没有自动化单元测试的情况下测试缓存时,我会使用 SQL Server Profiler 对数据库运行跟踪,并计算某个存储过程运行的次数,而我真正需要做的是找到一种方法来自动化这类测试。经过一番搜索,我找到了 Microsoft.SqlServer.ConnectionInfo
程序集,其中包含 TraceServer
类。TraceServer
类根据必须提供的跟踪定义文件,在给定的 SQL Server 上创建一个新的跟踪。它实现了 IDataReader
接口,其行由跟踪定义文件中指定的列组成。为了使用 TraceServer
类来单元测试我的工厂方法,我着手构建一个我自己的类,该类将具有三个 public
方法
StartTrace()
- 在 SQL Server 实例上启动跟踪。StopTrace()
- 停止跟踪。CountOccurencesInTextData(string textToMatch)
- 返回跟踪中包含指定字符串的行数。
我将继续简要描述如何实现它。但是,您可以下载一个包含完整类、跟踪定义文件和配置功能的 Visual Studio 2008 项目,在此处。
必需的命名空间
我们需要做的第一件事是在我们的项目中添加对 Microsoft.SqlServer.ConnectionInfo
的引用,然后导入以下命名空间
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Trace;
开始跟踪
为了开始跟踪,我们将需要一个 TraceServer
、一个 SqlConnectionInfo
类实例(该实例将保存连接到服务器的必要信息)和一个跟踪定义文件。SqlConnectionInfo
对象将需要 SQL Server 的名称(例如 localhost 或 123.456.123.456)以及具有在服务器上启动跟踪所需权限的用户的用户名和密码。至于跟踪定义文件,可以通过从 SQL Server Profiler(通过单击文件 -> 模板 -> 导出模板)导出跟踪模板来创建。在可下载的示例项目和本示例中,我使用了一个仅包含 RPC:Completed
和 SQL:BatchCompleted
事件的定义文件,只有三列:事件名称、文本数据和必需的 SPID 列。
SqlConnectionInfo connectionInfo = new SqlConnectionInfo
("serverName", "username", "password");
TraceServer traceServer = new TraceServer();
string traceDefinitionFilePath = "BatchOrSPCompleted.tdf";
traceServer.InitializeAsReader(connectionInfo, traceDefinitionFilePath);
停止跟踪和分析数据
一旦我们完成了跟踪,我们就会停止它,然后计算 TextData
列包含指定字符串的行数。
traceServer.Stop();
string textToMatch = "Some string to match, "
+ "such as the name of a stored procedure";
int count = 0;
while(traceServer.Read())
{
if (traceServer.IsDBNull(1))
continue;
string textData = _traceServer.GetString(1);
if (textData.Contains(textToMatch))
count++;
}
traceServer.Close();
示例用法
上述功能,特别是可下载项目中的完整类,可以用于多种方式。一个简单的示例场景是,我们希望在第一次时检查一个名为 BlogFactory
的工厂类从数据库获取某个博客,并在第二次时从缓存中获取。
[Test]
public void TestBlogCaching()
{
string blogSelectQuery = "SELECT ID, name FROM blog";
GetBlogWhileTracing();
int numberOfDatabaseHits =
TraceCounter.CountOccurencesInTextData(blogSelectQuery);
//As this is the first time we fetch the Blog it should be fetched
//from the database
Assert.AreEqual(1, numberOfDatabaseHits);
GetBlogWhileTracing();
numberOfDatabaseHits =
TraceCounter.CountOccurencesInTextData(blogSelectQuery);
//The Blog should now be fetched from the cache and the method call
//should not result in any database hits
Assert.AreEqual(0, numberOfDatabaseHits);
}
private void GetBlogWhileTracing()
{
TraceCounter.StartTrace();
BlogFactory.GetBlog(1);
TraceCounter.StopTrace;
}
更新和其他文章
虽然我会尝试在此处发布任何更新,但您肯定会在 bloodsweatand.net 找到它们,以及我写的其他文章,我在那里有一个博客。这篇文章可以在这里找到。