如何对使用 OWIN、SignalR 和硒/量角器的 Web 应用程序进行 E2E 测试 - 我只能获得第一个测试才能通

本文关键字:测试 E2E 第一个 应用程序 OWIN SignalR Web 量角器 和硒 | 更新日期: 2023-09-27 18:20:34

我正在尝试执行E2E测试,在每个测试中我创建一个新网站并回滚数据库。 我正在使用 SignalR 将数据推送到客户端。 我的问题是,当我一次运行所有测试时,只有我的第一个测试通过了,即使每个测试都单独通过。如何连续运行一堆测试,在每次运行之间清理 SignalR/Owin? 我找不到任何 SignalR + E2E 测试的示例,尽管官方文档确实显示了单元测试。

虽然有一些推断和日志记录,但我认为这是 SignalR 没有正确处理和/或初始化。 这是我为确认这是问题所做的:

  1. 打开Visual Studio 2013。
  2. 创建了一个使用Wewb API的Web应用程序,名为"SignalRSandbox"。
  3. 托管 NuGet 包 ->已安装Microsoft.AspNet.Signalr
  4. 托管 NuGet 包 -> 已安装angularjs(我的网站使用角度;为了使内容尽可能接近我的真实环境,我使用的是量角器而不是硒(
  5. 添加了一个名为 StartUp 的类:
  6. 添加了一个名为 ChatHub 的类:
  7. 添加了一个名为 index 的 html 文件:
  8. 创建了一个名为"SignalRSandbox.Tests"的新类库项目。
  9. 添加了对网站项目的引用
  10. 托管 NuGet 包 ->已安装xunit
  11. 托管 NuGet 包 ->已安装Protractor
  12. 托管 NuGet 包 ->已安装Selenium.WebDriver.ChromeDriver
  13. 托管 NuGet 包 ->已安装Microsoft.Owin.Hosting
  14. 托管 NuGet 包 ->已安装Microsoft.Owin.Host.HttpListener
  15. 托管 NuGet 包 ->已安装Microsoft.Owin.StaticFiles
  16. 添加了一个名为 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>:&nbsp;&nbsp;' + 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();
            }
        }
    }
}

如何对使用 OWIN、SignalR 和硒/量角器的 Web 应用程序进行 E2E 测试 - 我只能获得第一个测试才能通

如果要在同一 AppDomain 中多次调用MapSignalR,则应每次传入一个新DefaultDependencyResolver。如果你不把自己的IDependencyResolver传递给MapSignalR,它将使用GlobalHost.DependencyResolver引用的那个。

你看到的测试失败可能是由释放MyTest._site时释放的 SignalR 依赖项引起的。

您可以为每个调用指定自己的IDependencyResolverConfigure如下所示:

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();
}