我正在尝试使用单元测试来测试一个方法,但是也要确保它在内部以预期的方式执行。这是简化的方法;它从数据库返回一个值,但也将其保存到缓存中,因此如果在5秒内再次请求它,则将从缓存而不是数据库中检索该值。
public static string GetValue()
{
var cache = HttpRuntime.Cache;
string value = (string)cache["test"];
// if value is null then it was never in cache or it expired.
if (value == null)
{
// Imagine here complex code that retrieves and sets "value"
value = "OK";
// Add it to cache to retrieve it faster if requested again within 5 sec.
cache.Add("test", value, null, DateTime.Now + TimeSpan.FromSeconds(5),
System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);
Debug.WriteLine("From DB");
}
else
{
// Value was in cache, so it's ready to return
Debug.WriteLine("From Cache");
}
return value;
}
这基本上从假设数据库返回值“OK”。但是,因为它使用HttpRuntime.Cache的绝对过期,如果在5秒内再次请求该值,则从Cache返回,而不是从DB返回。
现在我的问题是,如何编写一个TestMethod,它不仅验证它返回OK,而且还有缓存逻辑正常工作。
请注意,根据它是使用DB还是Cache来获取值,会在调试输出中添加相应的调试行。
所以测试方法应该是这样的:
[TestMethod]
public void GetValue_OK()
{
Assert.IsTrue(Helpers.GetValue() == "OK");
Thread.Sleep(4000);
Assert.IsTrue(Helpers.GetValue() == "OK");
// Assert that it wrote "From Cache"
Assert.IsTrue(LastDebugLine().Contains("Cache"));
Thread.Sleep(2000);
Assert.IsTrue(Helpers.GetValue() == "OK");
// Assert that it wrote "From DB", because theoretically over 5 seconds passed
// So it has expired and the routine loaded it again from the DB.
Assert.IsTrue(LastDebugLine().Contains("DB"));
}
string LastDebugLine()
{
// Imagine a string named LastDebugOutput that contains the
// last line of output from Debug.WriteLine
// The code below retrieves somehow this line.
return String.Empty;
}
在testmethod中,我不仅验证了正确的输出,还验证了它是从缓存还是数据库中检索出来的。我运行该方法3次,在每次检索之间插入延迟。我还想测试是否从数据库或缓存中检索该值。
我认为这可能发生的方式是使用一种方法,我调用LastDebugLine()从被测试的方法中检索最后一个调试信息。通过读取它的状态,测试方法知道方法的内部,因此可以与预期结果进行比较。
现在我的问题有两个部分:
1)测试所有这些的正确方法是什么?我的想法是使用调试输出并在单元测试中检查它是否正确?我在这里对于一般概念可能是非常错误的,所以也许这可以做得更好。
2)但是,如果我的概念是正确的,那么LastDebugLine()中的代码到底应该从Debug.WriteLine获取最后一行呢?
即使我是正确的,仍然存在问题,因为单元测试可能会运行多线程,因此读取这样的调试输出可能会带来意想不到的结果。
如何正确测试此方法?
答案 0 :(得分:1)
有几种方法可以解决这个问题。要完成整个测试,你可以
如果从缓存中获取值,那么您将获得正确的值。如果方法调用db,则测试将失败。
作为替代方案,您可以隔离访问db和缓存的组件,并将执行此操作的对象注入到测试的类中。然后,您可以在测试中提供模拟,以断言以正确的顺序调用对象。
在你目前的情况下,由于你有一个静态方法很困难,所以注入依赖很难,但如果你改变你的GetValue方法不是静态的,你可以传递一些包装你的HttpRuntime.Cache和实现一个类似的接口,然后传递该对象的模拟,并验证当缓存中的对象不存在时,其他代码被调用,当它在缓存中存在时,它不会被调用
如果您无法更改模型以使GetValue不是静态的,那么您可以查看使用MS Fakes框架或其他可以模拟静态方法的商业产品。