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





5.00/5 (3投票s)
在自动化测试框架中使用 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 的实例的方法,该实例位于要服务的站点的物理路径上。
考虑到这些需求,我构造了一个 abstract
类 WebDevFixture.cs,它适用于任何测试框架。
此类封装了所有列出的需求。
最低使用要求是使用指向要服务的站点目录的绝对或相对路径调用 StartServer
。
可以通过覆盖派生类中的 readonly
属性或在可选的 app.config 中添加 appSettings
条目来更改默认的 Port
和 VDir
。
为了方便起见,NormalizeUri
方法将接受相对 URL 片段并返回绝对 URI。
列表 2 提供了一个 NUnit TestFixture
从使用默认值的 WebDevFixture
派生的示例。
列表 3 提供了一个使用被覆盖的属性来提供 Port
和 VDir
的示例。
列表 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 标签:ASP.NET, 测试, Salient, CodeProject