如何对使用 OWIN、SignalR 和硒/量角器的 Web 应用程序进行 E2E 测试 - 我只能获得第一个测试才能通
本文关键字:测试 E2E 第一个 应用程序 OWIN SignalR Web 量角器 和硒 | 更新日期: 2023-09-27 18:20:34
我正在尝试执行E2E测试,在每个测试中我创建一个新网站并回滚数据库。 我正在使用 SignalR 将数据推送到客户端。 我的问题是,当我一次运行所有测试时,只有我的第一个测试通过了,即使每个测试都单独通过。如何连续运行一堆测试,在每次运行之间清理 SignalR/Owin? 我找不到任何 SignalR + E2E 测试的示例,尽管官方文档确实显示了单元测试。
虽然有一些推断和日志记录,但我认为这是 SignalR 没有正确处理和/或初始化。 这是我为确认这是问题所做的:
- 打开Visual Studio 2013。
- 创建了一个使用Wewb API的Web应用程序,名为"SignalRSandbox"。
- 托管 NuGet 包 ->已安装
Microsoft.AspNet.Signalr
- 托管 NuGet 包 -> 已安装
angularjs
(我的网站使用角度;为了使内容尽可能接近我的真实环境,我使用的是量角器而不是硒( - 添加了一个名为
StartUp
的类: - 添加了一个名为
ChatHub
的类: - 添加了一个名为
index
的 html 文件: - 创建了一个名为"SignalRSandbox.Tests"的新类库项目。
- 添加了对网站项目的引用
- 托管 NuGet 包 ->已安装
xunit
- 托管 NuGet 包 ->已安装
Protractor
- 托管 NuGet 包 ->已安装
Selenium.WebDriver.ChromeDriver
- 托管 NuGet 包 ->已安装
Microsoft.Owin.Hosting
- 托管 NuGet 包 ->已安装
Microsoft.Owin.Host.HttpListener
- 托管 NuGet 包 ->已安装
Microsoft.Owin.StaticFiles
- 添加了一个名为
MyTest
的类:
果然,只有第一个测试通过:
启动.cs
using Microsoft.Owin;
using Owin;
namespace SignalRSandbox
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
聊天中心.cs
using System;
using System.Web;
using Microsoft.AspNet.SignalR;
namespace SignalRSandbox
{
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
}
}
}
索引.html(有些代码可能看起来有点时髦 - 我只是想让量角器/硒正确等待(。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body ng-app="ng">
<div class="container">
<input type="text" id="message" />
<button type="button" id="send" onclick="send()">Send</button>
<input type="hidden" id="displayname" />
<ul id="discussion"></ul>
</div>
<script src="Scripts/jquery-1.10.2.min.js"></script>
<script src="Scripts/angular.js"></script>
<script src="Scripts/jquery.signalR-2.1.1.min.js"></script>
<script src="signalr/hubs"></script>
<script type="text/javascript">
var send;
$(function () {
var q = [];
var loadMessages = send = function () {
q.push({
name: $('#displayname').val(),
message: $('#message').val()
});
$('#message').val('').focus();
};
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) {
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
$('#discussion').append('<li id="discussion' + $('#discussion').children().length + '"><strong>' + encodedName
+ '</strong>: ' + encodedMsg + '</li>');
};
$('#displayname').val(prompt('Enter your name:', ''));
$('#message').focus();
var sendMessages = function () {
q.forEach(function (i) {
chat.server.send(i.name, i.message);
});
q.length = 0;
};
$.connection.hub.start().done(function () {
sendMessages();
send = function () {
loadMessages();
sendMessages();
};
});
});
</script>
</body>
</html>
我的测试.cs
using Microsoft.Owin.Hosting;
using OpenQA.Selenium.Chrome;
using Protractor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Owin;
using Microsoft.Owin.StaticFiles;
using Microsoft.Owin.FileSystems;
using OpenQA.Selenium;
namespace SignalRSandbox.Tests
{
public class MyTest : IDisposable
{
private NgWebDriver _driver;
private IDisposable _site;
private string _url;
public MyTest()
{
this._url = "http://localhost:8765/index.html";
this._site = WebApp.Start(this._url, appBuilder =>
{
// SignalR
new Startup().Configuration(appBuilder);
// Allow static content through
var options = new FileServerOptions();
options.FileSystem = new PhysicalFileSystem("../../../SignalRSandbox");
appBuilder.UseFileServer(options);
});
this._driver = new NgWebDriver(new ChromeDriver());
this._driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromMinutes(1));
this._driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromMinutes(1));
this._driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(2));
// We have to use the WrappedDriver otherwise the alert will cause an error.
this._driver.WrappedDriver.Navigate().GoToUrl(this._url);
// Enter the name
var alert = this._driver.WrappedDriver.SwitchTo().Alert();
alert.SendKeys("j");
alert.Accept();
// Enter a message
this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test");
this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
// Enter another message
this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test1");
this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
Assert.Equal("j: test", this._driver.WrappedDriver.FindElement(By.Id("discussion0")).Text);
Assert.Equal("j: test1", this._driver.WrappedDriver.FindElement(By.Id("discussion1")).Text);
}
[Fact]
public void Test()
{
}
// All subsequent tests fail
[Fact]
public void Test1()
{
}
[Fact]
public void Test2()
{
}
[Fact]
public void Test3()
{
}
public void Dispose()
{
if (this._driver != null)
{
this._driver.Dispose();
}
if (this._site != null)
{
this._site.Dispose();
}
}
}
}
测试是使用测试资源管理器运行的(我想我也安装了一个 XUnit.NET 的Visual Studio插件(。
更新:通过切换到 NUnit 而不是 xUnit 并使用 ApplicationDomain 扩展,我能够执行以下操作,但这样做我最终在我的测试方法中调用初始化和处置,我不喜欢重复自己,但它确实完成了工作。
如果有人可以在不使用单独的应用程序域的情况下完成这项工作,我会接受。
以下是更新的MyTest.cs VER 2:
using Microsoft.Owin.Hosting;
using OpenQA.Selenium.Chrome;
using Protractor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Owin;
using Microsoft.Owin.StaticFiles;
using Microsoft.Owin.FileSystems;
using OpenQA.Selenium;
using NUnit.Framework;
namespace SignalRSandbox.Tests
{
public class MyTest
{
private NgWebDriver _driver;
private IDisposable _site;
private string _url;
public void Intialize()
{
this._url = "http://localhost:8765";
this._site = WebApp.Start(this._url, appBuilder =>
{
// SignalR
new Startup().Configuration(appBuilder);
// Allow static content through
var options = new FileServerOptions();
options.FileSystem = new PhysicalFileSystem("../../../SignalRSandbox");
appBuilder.UseFileServer(options);
});
this._driver = new NgWebDriver(new ChromeDriver());
this._driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromMinutes(1));
this._driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromMinutes(1));
this._driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(2));
// We have to use the WrappedDriver otherwise the alert will cause an error.
this._driver.WrappedDriver.Navigate().GoToUrl(this._url);
// Enter the name
var alert = this._driver.WrappedDriver.SwitchTo().Alert();
alert.SendKeys("j");
alert.Accept();
// Enter a message
this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test");
this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
// Enter another message
this._driver.WrappedDriver.FindElement(By.Id("message")).SendKeys("test1");
this._driver.WrappedDriver.FindElement(By.Id("send")).Click();
Assert.AreEqual("j: test", this._driver.WrappedDriver.FindElement(By.Id("discussion0")).Text);
Assert.AreEqual("j: test1", this._driver.WrappedDriver.FindElement(By.Id("discussion1")).Text);
}
[Test, RunInApplicationDomain]
public void Test()
{
this.Intialize();
this.Dispose();
}
// All subsequent tests fail
[Test, RunInApplicationDomain]
public void Test1()
{
this.Intialize();
this.Dispose();
}
[Test, RunInApplicationDomain]
public void Test2()
{
this.Intialize();
this.Dispose();
}
[Test, RunInApplicationDomain]
public void Test3()
{
this.Intialize();
this.Dispose();
}
public void Dispose()
{
if (this._driver != null)
{
this._driver.Dispose();
}
if (this._site != null)
{
this._site.Dispose();
}
}
}
}
如果要在同一 AppDomain 中多次调用MapSignalR
,则应每次传入一个新DefaultDependencyResolver
。如果你不把自己的IDependencyResolver
传递给MapSignalR
,它将使用GlobalHost.DependencyResolver
引用的那个。
你看到的测试失败可能是由释放MyTest._site
时释放的 SignalR 依赖项引起的。
您可以为每个调用指定自己的IDependencyResolver
,Configure
如下所示:
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
namespace SignalRSandbox
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration
{
Resolver = new DefaultDependencyResolver()
});
}
}
}
编辑:上述解决方案将使GlobalHost
无法使用,因为每个GlobalHost
属性都引用GlobalHost.DependencyResolver
SignalR不再使用。
可以通过解析 SignalR 实际使用IDependencyResolver
中的相应类型来解决此问题。例如,您可以执行以下操作,而不是使用 GlobalHost.ConnectionManager
:
public void Configuration(IAppBuilder app)
{
var resolver = new DefaultDependencyResolver();
var connectionManager = resolver.Resolve<IConnectionManager>();
var myHubContext = connectionManager.GetHubContext<MyHub>();
app.MapSignalR(new HubConfiguration
{
Resolver = resolver
});
}
或者,由于不会同时运行多个 SignalR 终结点,因此可以在调用 MapSignalR
之前重置GlobalHost.DependencyResolver
。然后你可以继续使用Globalhost
:
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver = new DefaultDependencyResolver();
// GlobalHost.ConnectionManager now references the IConnectionManager
// provided by the DefaultDependencyResolver instantiated in the line above.
var myHubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
// SignalR will use GlobalHost.DependencyResolver by default
app.MapSignalR();
}