【问题标题】:Unit Testing Application_Start单元测试应用程序_开始
【发布时间】:2008-12-16 17:19:12
【问题描述】:

我正在寻找有关如何在 Global.asax 中对 Application_Start 方法进行单元测试的任何类型的信息(首选 Moq)。我正在使用 ASP.NET MVC 并试图达到难以捉摸的 100% 代码覆盖率!

我使用 MVC 的事实并不是重点。并且说不需要测试 Start 也不是真正的答案。如果我有其他代码怎么办?我需要知道如何测试它。

【问题讨论】:

    标签: c# asp.net-mvc unit-testing


    【解决方案1】:

    一些组织确实需要这些无意义的数字,并且存在成本以外的问题。对于处理敏感信息的公司来说,“足够好”还不够好。我遇到了完全相同的问题,就像 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 上调用,所以我不会对此大惊小怪。 我当然会对其他人在这方面所做的工作感兴趣。

    【讨论】:

    • 我似乎记得你的雇主有很多超出成本或原因的问题。 (得到我实际回答问题的投票)
    【解决方案2】:

    在典型的 ASP.NET MVC 应用程序中,Application_Start 事件通常用于注册自定义路由。这是一个很好的 post 解释如何对您的自定义路由进行单元测试。

    【讨论】:

    • 对自定义路由本身进行单元测试是有意义的,但不会涵盖 Application_Start 方法。如果您可以测试 Application_Start 是否设置了自定义路由,那会(有点)有趣,但我想不出办法。
    【解决方案3】:

    第一次访问您的网站时调用此函数。 回收应用池会导致它再次被触发

    在大多数情况下,这个事件处理程序没有代码,所以不要浪费时间去追求无意义的数字!

    【讨论】:

    • 我同意。没有理由测试这个事件。如果您正在做一些需要测试的逻辑,请将其提取出来并单独测试。测试具有这种应用程序池依赖性的东西是非常不切实际的。
    【解决方案4】:

    如果您想像这样测试基于事件的项目的操作,请将这些操作分离到一个单独的、可测试的方法中,然后从处理程序中调用它。或者,正如 darin 建议的那样,测试该方法的效果 - 在这种特殊情况下,您的路由表已正确注册。

    顺便说一句,我从来没有在一个重要的应用程序上达到 100% 的覆盖率......我们通常将 80% 的目标定位为“足够好”。有些模块是 100% 可测试的,但许多模块并不实际。

    【讨论】:

    • 测试该方法的效果肯定是一个好策略,但要做到这一点,您仍然必须直接或间接调用该方法。如果你不这样做,你实际上是在测试应用程序的另一部分(例如 RouteConfig 中的 Route 配置)
    【解决方案5】:

    我发现在 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 法律的“精神”:)

    【讨论】:

    • 有趣的想法,但您是否成功地实现了它?如果我将 Application_Start 的内容分解为公共静态方法并尝试在测试中调用它,我在运行它时会收到以下错误:“System.InvalidOperationException:在应用程序的预启动初始化阶段无法调用此方法”
    • @KlasMellbourn 是的,我实际上已经这样做了。听起来您正试图提前通过实际会话?此方法假定您将在 HttpSessionStateBase 中模拟出您需要模拟出的任何内容并将其发送到测试中。那有意义吗?如果没有,请在某处发布一些代码,我会看看我是否能弄清楚你想要做什么..
    • 该方法仅在 MVC 4 应用程序中运行 Application_Start 的标准行:AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles);
    猜你喜欢
    • 1970-01-01
    • 2011-07-19
    • 2017-08-05
    • 1970-01-01
    • 2015-08-13
    • 2022-10-14
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    相关资源
    最近更新 更多