65.9K
CodeProject 正在变化。 阅读更多。
Home

通用 Visual Studio 2008 开发服务器 (WebDev.WebServer.exe) 测试装置

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2010年4月14日

CPOL

2分钟阅读

viewsIcon

18672

在自动化测试框架中使用 Visual Studio 2008 开发服务器 (WebDev.WebServer.exe)

更新:这篇文章仍然有效作为参考资料,但是提供的代码已经重新设计,并可以在这里找到。


虽然我通常使用 CassiniDev 进行冒烟测试,但有时使用内置服务器对站点运行一些快速简单的测试会很方便。

启动一个 WebDev.WebServer.exe 实例相当简单。

该可执行文件可以在 C:\Program Files\Common Files\Microsoft Shared\DevServer\9.0\WebDev.WebServer.exe 找到 (对于 x64 Windows,为 Program Files (x86))。

如果调用不带参数的 .exe,您将看到下面列出的使用说明文档。

列表 1:WebDev.WebServer 用法

---------------------------
ASP.NET Development Server
---------------------------
ASP.NET Development Server Usage:
 WebDev.WebServer /port:<port number> /path:<physical path> [/vpath:<virtual path>]

	port number:
		[Optional] An unused port number between 1 and 65535.
		The default is 80 (usable if you do not also have 
				IIS listening on the same port).

	physical path:
		A valid directory name where the Web application is rooted.

	virtual path:
		[Optional] The virtual path or application root 
				in the form of '/<app name>'.
		The default is simply '/'.

Example:
WebDev.WebServer /port:8080 /path:"c:\inetpub\wwwroot\MyApp" /vpath:"/MyApp"

You can then access the Web application using a URL of the form:
	https://:8080/MyApp

有了这些信息,您就可以从测试装置中启动 webdev 的一个实例。

您会发现的问题是,如果 webdev 的一个实例已经在指定的端口上运行,则会显示一个程序崩溃对话框。现有实例仍然可以为您的测试提供请求,但是崩溃对话框是一个严重的用户体验问题,应该加以处理。

确保所需端口上没有现有实例正在运行的合适策略是查询 Windows Management API (WMI) 并返回启动每个实例的命令行,将其与您希望用于启动新实例的命令行参数进行比较。如果实例已经在运行,则无需启动另一个实例。

因此,在管理好这些问题后,最终需求似乎是

  • 确定 WebDev.WebServer.exe 的位置。
  • 提供用于服务站点的端口。
  • 提供用于根站点目录的虚拟目录。
  • 提供一种确定 WebDev.WebServer.exe 的实例是否已经在所需端口上运行的方法。
  • 提供一种在必要时启动 WebDev.WebServer.exe 的实例的方法,该实例位于要服务的站点的物理路径上。

考虑到这些需求,我构造了一个 abstractWebDevFixture.cs,它适用于任何测试框架。

此类封装了所有列出的需求。

最低使用要求是使用指向要服务的站点目录的绝对或相对路径调用 StartServer

可以通过覆盖派生类中的 readonly 属性或在可选的 app.config 中添加 appSettings 条目来更改默认的 PortVDir

为了方便起见,NormalizeUri 方法将接受相对 URL 片段并返回绝对 URI。

列表 2 提供了一个 NUnit TestFixture 从使用默认值的 WebDevFixture 派生的示例。

列表 3 提供了一个使用被覆盖的属性来提供 PortVDir 的示例。

列表 2:与 NUnit 一起使用 - 默认端口和 VDir

using System;
using System.Net;
using NUnit.Framework;

namespace Salient.Web.HttpLib.Tests
{
    [TestFixture]
    public class NUnitWebDevFixture : WebDevFixture
    {
        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            // default WebDevFixture port = 9999
            // default WebDevFixture vdir = '/'
            // thus the site under test will be found at https://:9999/

            // the path to the website under test can be absolute or relative

            StartServer(@"..\..\..\Salient.Web.HttpLib.TestSite");
        }

        [Test, Description("Simple Get with WebClient")]
        public void Get()
        {
            WebClient client = new WebClient();
            Uri uri = NormalizeUri("default.aspx"); // https://:9999/default.aspx
            
            string actual = client.DownloadString(uri);
            Assert.IsTrue(actual.IndexOf("This is Default.aspx") > -1);
        }
    }
}

列表 3:与 NUnit 一起使用 - 特定端口和 VDir

using System;
using System.Net;
using NUnit.Framework;

namespace Salient.Web.HttpLib.Tests
{
    [TestFixture]
    public class NUnitWebDevFixture2 : WebDevFixture
    {
        protected override int Port
        {
            get { return 12345; }
        }
        protected override string VDir
        {
            get { return "/myapp"; }
        }

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            // the site under test will be found at https://:12345/myapp
            StartServer(@"..\..\..\Salient.Web.HttpLib.TestSite");
        }

        [Test, Description("Simple Get with WebClient")]
        public void Get()
        {
            WebClient client = new WebClient();

            // https://:12345/myapp/default.aspx
            Uri uri = NormalizeUri("default.aspx"); 		

            string actual = client.DownloadString(uri);
            Assert.IsTrue(actual.IndexOf("This is Default.aspx") > -1);
        }
    }
}

列表 4:WebDevFixture.cs

// /*!
//  * Project: Salient.Web.HttpLib
//  * http://salient.codeplex.com
//  * Date: April 11 2010 
//  */

#region

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Management;

#endregion

namespace Salient.Web.HttpLib
{
    /// <summary>
    /// A general purpose Visual Studio 2008 Development Server 
    /// (WebDev.WebServer.exe) test fixture
    /// </summary>
    public abstract class WebDevFixture
    {
        private const string WebDevPathx64 =
            @"C:\Program Files (x86)\Common Files\
		Microsoft Shared\DevServer\9.0\WebDev.WebServer.exe";

        private const string WebDevPathx86 =
            @"C:\Program Files\Common Files\
		Microsoft Shared\DevServer\9.0\WebDev.WebServer.exe";

        private readonly int _port = 9999;
        private readonly string _vdir = "/";

        protected WebDevFixture()
        {
            // set testing port - default 9999 - 
            // can be changed by adding an appSettings 'port'
            // note: appSetting is optional, will just default to 9999
            int tmpPort;
            _port = int.TryParse(ConfigurationManager.AppSettings["port"], 
			out tmpPort) ? tmpPort : _port;

            // set vdir - default '/' - can be changed by adding an appSettings 'vdir'
            // note: appSetting is optional, will just default to '/'

            _vdir = ConfigurationManager.AppSettings["vdir"] ?? "/";

            // do some munging to ensure that the vdir is going to work out for us
            _vdir = string.Format(CultureInfo.InvariantCulture, 
			"/{0}/", _vdir.Trim('/')).Replace("//", "/");
        }

        // these can optionally be overriden in app.config 
        // using similarly named appSettings. e.g. 'port' and 'vdir'
        // or overridden by derived class
        protected virtual int Port
        {
            get { return _port; }
        }

        protected virtual string VDir
        {
            get { return _vdir; }
        }

        /// <summary>
        /// Returns an absolute Uri rooted on the running server.
        /// e.g. NormalizeUrl("default.aspx") will return 
        /// 'https://:9999/default.aspx'
        /// </summary>
        /// <param name="pathAndQuery">path and query relative to app vdir</param>
        /// <returns></returns>
        protected Uri NormalizeUri(string pathAndQuery)
        {
            // do some munging to ensure that the vdir is going to work out for us
            string vdir = string.Format(CultureInfo.InvariantCulture, 
		"/{0}/", VDir.Trim('/')).Replace("//", "/");
            return
                new Uri(string.Format(CultureInfo.InvariantCulture, 
		"https://:{0}{1}{2}", Port, vdir, pathAndQuery));
        }

        /// <summary>
        /// Ensure that an instance of WebDev.WebServer.exe is 
        /// running and serving the target site. 
        /// </summary>
        /// <param name="sitePath">path of the website to serve, 
        /// can be absolute or relative</param>
        protected void StartServer(string sitePath)
        {
            sitePath = Path.GetFullPath(sitePath);

            // try x86 first
            string webDevPath = WebDevPathx86;
            if (!File.Exists(webDevPath))
            {
                // then x64
                webDevPath = WebDevPathx64;
                if (!File.Exists(webDevPath))
                {
                    throw new FileNotFoundException("Cannot find WebDev.WebServer.exe");
                }
            }

            string devServerCmdLine = String.Format(CultureInfo.InvariantCulture,
                          "/port:{0} /path:\"{1}\" /vpath:\"{2}\"", Port, sitePath,
                          VDir);

            // check to see if we already have a server running
            bool running = false;
            foreach (string cmdLine in GetCommandLines("WebDev.WebServer.exe"))
            {
                if (cmdLine.EndsWith
		(devServerCmdLine, StringComparison.OrdinalIgnoreCase))
                {
                    running = true;
                    break;
                }
            }

            if (!running)
            {
                Process.Start(webDevPath, devServerCmdLine);
            }
        }

        /// <summary>
        /// Using WMI to fetch the command line that started all instances of a process
        /// </summary>
        /// <param name="processName">Image name, e.g. WebDev.WebServer.exe</param>
        /// <returns></returns>
        /// adapted from http://stackoverflow.com/questions/504208/
        /// how-to-read-command-line-arguments-of-another-process-in-c/504378%23504378
        /// original code by http://stackoverflow.com/users/61396/xcud
        private static IEnumerable<string> GetCommandLines(string processName)
        {
            List<string> results = new List<string>();

            string wmiQuery = string.Format(CultureInfo.InvariantCulture,
                 "select CommandLine from Win32_Process where Name='{0}'", processName);

            using (ManagementObjectSearcher searcher = 
			new ManagementObjectSearcher(wmiQuery))
            {
                using (ManagementObjectCollection retObjectCollection = searcher.Get())
                {
                    foreach (ManagementObject retObject in retObjectCollection)
                    {
                        results.Add((string) retObject["CommandLine"]);
                    }
                }
            }
            return results;
        }
    }
}

可以在这里找到利用此装置的完整解决方案。

与往常一样,您可以在http://salient.codeplex.com获取最新的源代码和测试。


Technorati 标签:, , ,

© . All rights reserved.