【发布时间】:2008-12-16 17:19:12
【问题描述】:
我正在寻找有关如何在 Global.asax 中对 Application_Start 方法进行单元测试的任何类型的信息(首选 Moq)。我正在使用 ASP.NET MVC 并试图达到难以捉摸的 100% 代码覆盖率!
我使用 MVC 的事实并不是重点。并且说不需要测试 Start 也不是真正的答案。如果我有其他代码怎么办?我需要知道如何测试它。
【问题讨论】:
标签: c# asp.net-mvc unit-testing
我正在寻找有关如何在 Global.asax 中对 Application_Start 方法进行单元测试的任何类型的信息(首选 Moq)。我正在使用 ASP.NET MVC 并试图达到难以捉摸的 100% 代码覆盖率!
我使用 MVC 的事实并不是重点。并且说不需要测试 Start 也不是真正的答案。如果我有其他代码怎么办?我需要知道如何测试它。
【问题讨论】:
标签: c# asp.net-mvc unit-testing
一些组织确实需要这些无意义的数字,并且存在成本以外的问题。对于处理敏感信息的公司来说,“足够好”还不够好。我遇到了完全相同的问题,就像 Klas Mellbourn 一样,需要达到 100%(如果不是更高的话!) 以下对我有用。虽然,我更愿意将其标记为“从代码覆盖范围中排除”
public class Global : HttpApplication
{
public override void Init()
{
AreaRegistration.RegisterAllAreas(); //will error out on app_start
base.Init();
}
/// <summary>
/// Application_Start method.
/// </summary>
/// <param name="sender">The caller</param>
/// <param name="e">The event arguments</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "KMM: This method is called dynamically by the framework.")]
protected void Application_Start(object sender, EventArgs e)
{
var container = StructureMapRegistry.Initialize();
GlobalConfiguration.Configuration.DependencyResolver = new StructureMapResolver(container);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
然后单元测试看起来像这样:
public class GlobalTest : Global
{
private HttpRequestMessage FakeRequest;
DateTime? effectiveDate = DateTime.Now.AddYears(-4);
private string policyNumber = "1234567890";
[TestMethod]
public void ApplicationStart()
{
var sender = new object();
var e = new EventArgs();
try
{
Application_Start(sender, e); // this will error b/c not fully loaded yet.
}
catch (InvalidOperationException)
{
Thread.Sleep(2000); // give the app time to launch
Application_Start(sender, e);
}
Assert.IsTrue(true);
}
}
最后,我需要在我的 WebApiConfig 中设置一个标志,以防止路由被注册两次。
public static class WebApiConfig
{
private static bool isRegistered;
/// <summary>
/// Registers the configuration.
/// </summary>
/// <param name="config">The Http Configuration.</param>
public static void Register(HttpConfiguration config)
{
if (isRegistered)
{
return;
}
config.MapHttpAttributeRoutes();
现在,在仇恨者和纯粹主义者开始对此进行标记之前,任务是测试所有代码。我个人讨厌修改代码以适应测试。这与使代码可测试不同。添加 isRegistered 标志是支持需要调用 app_start 2x 的测试所必需的工件类型的示例。这是一件小事,因为该代码仅在 app_start 上调用,所以我不会对此大惊小怪。 我当然会对其他人在这方面所做的工作感兴趣。
【讨论】:
在典型的 ASP.NET MVC 应用程序中,Application_Start 事件通常用于注册自定义路由。这是一个很好的 post 解释如何对您的自定义路由进行单元测试。
【讨论】:
第一次访问您的网站时调用此函数。 回收应用池会导致它再次被触发
在大多数情况下,这个事件处理程序没有代码,所以不要浪费时间去追求无意义的数字!
【讨论】:
如果您想像这样测试基于事件的项目的操作,请将这些操作分离到一个单独的、可测试的方法中,然后从处理程序中调用它。或者,正如 darin 建议的那样,测试该方法的效果 - 在这种特殊情况下,您的路由表已正确注册。
顺便说一句,我从来没有在一个重要的应用程序上达到 100% 的覆盖率......我们通常将 80% 的目标定位为“足够好”。有些模块是 100% 可测试的,但许多模块并不实际。
【讨论】:
我发现在 Global.asax 中对内容进行单元测试的最佳方法是确保整个内容都在可测试的静态方法中。
然后将 Global.asax 所需的所有内容传递给该方法。
例如,如果您在应用程序启动时检查 Session,您可以使用如下方法:
public static void CheckSession(HttpSessionStateBase session)
{
...
}
然后,您的应用程序开始将是这样的:
protected void Application_Start(object sender, EventArgs e)
{
CheckSession(new HttpSessionStateWrapper(Session));
}
这个例子显然有点傻,因为你可能会在 Session Start 中做这样的事情 :) 你可以将它需要的任何东西传递给该方法,请求、响应、缓存等。
但是,它明白了这一点。唯一不会涉及的代码是 Application_Start 中的实际单行调用。使用 Moq 的测试可以涵盖其他所有内容,如下所示:
var session = new Moq<HttpSessionStateBase>();
...Set Expectations...
Global.CheckSession(session.Object);
...Do Asserts...
你不会达到你想要的 100% 的代码覆盖率,但你会非常接近,如果不完全符合 TDD 法律的“精神”:)
【讨论】: