2009-02-18 6 views
9

Ich verwende Cassini/WebServer.WebDev, um einige automatisierte Tests eines WebService mit NUnit auszuführen.Cassini/WebServer.WebDev, NUnit und AppDomainUnloadedException

Ich bin nichts Besonderes tun, nur

public class WebService{ 
    Microsoft.VisualStudio.WebHost.Server _server; 

    public void Start(){ 
    _server = new Microsoft.VisualStudio.WebHost.Server(_port, "/", _physicalPath); 
    } 

    public void Dispose() 
    { 
    if (_server != null) 
    { 
     _server.Stop(); 
     _server = null; 
    } 
    } 
} 
[TestFixture] 
public void TestFixture{ 
    [Test] 
    public void Test(){ 
    using(WebService webService = new WebService()){ 
     webService.Start(); 
     // actual test invoking the webservice 
    } 
    } 
} 

, aber wenn ich es mit nunit-console.exe laufen, bekomme ich die folgende Ausgabe:

NUnit version 2.5.0.9015 (Beta-2) 
Copyright (C) 2002-2008 Charlie Poole.\r\nCopyright (C) 2002-2004 James W. Newki 
rk, Michael C. Two, Alexei A. Vorontsov.\r\nCopyright (C) 2000-2002 Philip Craig 
.\r\nAll Rights Reserved. 

Runtime Environment - 
    OS Version: Microsoft Windows NT 6.0.6001 Service Pack 1 
    CLR Version: 2.0.50727.1434 (Net 2.0.50727.1434) 

ProcessModel: Default DomainUsage: Default 
Execution Runtime: net-2.0.50727.1434 
..... 
Tests run: 5, Errors: 0, Failures: 0, Inconclusive: 0 Time: 28,4538451 seconds 
    Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0 


Unhandled exceptions: 
1) TestCase1 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
2) TestCase2 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
3) TestCase3 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
4) TestCase4 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 

Wenn ich laufen nunit- Konsole unter dem Debugger, bekomme ich die folgende Ausgabe in der Debug-Konsole:

[...] 
The thread 0x1974 has exited with code 0 (0x0). 
############################################################################ 
##############     S U C C E S S    ################# 
############################################################################ 
Executed tests  : 5 
Ignored tests  : 0 
Failed tests   : 0 
Unhandled exceptions : 4 
Total time   : 25,7092944 seconds 
############################################################################ 
The thread 0x1bd4 has exited with code 0 (0x0). 
The thread 0x10f8 has exited with code 0 (0x0). 
The thread '<No Name>' (0x1a80) has exited with code 0 (0x0). 
A first chance exception of type 'System.AppDomainUnloadedException' occurred in System.Web.dll 
##### Unhandled Exception while running 
System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
    at System.Web.Hosting.ApplicationManager.HostingEnvironmentShutdownComplete(String appId, IApplicationHost appHost) 
    at System.Web.Hosting.HostingEnvironment.OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) 
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll 
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll 
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in System.Web.dll 
The thread 0x111c has exited with code 0 (0x0). 
The program '[0x1A64] nunit-console.exe: Managed' has exited with code -100 (0xffffff9c). 

Haben Sie irgendwelche Ideen, was könnte b Das verursacht dies?

+0

Haben Sie jemals eine Lösung für dieses Problem gefunden? Ich habe ein ähnliches Problem. –

+0

Nicht eine/gute/Lösung, aber ich landete failonerror = "false" auf der NUnit-Task und Postprocessing der XML-Ausgabe mit einem separaten Tools, die die nicht behandelte AppDomainUnloadedException ignoriert. –

+0

Ich habe eine Beispiellösung hochgeladen, die das Problem mit dem entsprechenden NUnit-Fehler unter https://bugs.launchpad.net/nunitv2/+bug/423611 demonstriert. es wäre toll, wenn Sie das ausprobieren und die Gültigkeit überprüfen könnten (und Ihre Ergebnisse an Launchpad senden). –

Antwort

7

Ich hatte das gleiche Problem, aber nicht mit Cassini. Stattdessen hatte ich mein eigenes Webserver-Hosting basierend auf System.Net.HttpListener mit ASP.Net-Unterstützung durch System.Web.HttpRuntime in einer anderen Anwendungsdomäne, die über System.Web.Hosting.ApplicationHost.CreateApplicationHost() erstellt wurde. Dies ist im Wesentlichen die Art und Weise, wie Cassini funktioniert, außer dass Cassini auf der Socket-Ebene arbeitet und viele der von selbst bereitgestellten Funktionen implementiert.

Wie auch immer, um mein Problem zu lösen, musste ich System.Web.HttpRuntime.Close() anrufen, bevor NUnit meine Anwendungsdomäne entladen konnte. Ich tat dies, indem ich eine neue Close()-Methode in meiner Host-Proxy-Klasse ausstellte, die von der [TearDown] -Methode meiner [SetupFixture] -Klasse aufgerufen wurde, und diese Methode ruft System.Web.HttpRuntime.Close() auf.

Ich schaute auf die Cassini-Implementierung durch. NET-Reflektor und, obwohl es System.Web.HttpRuntime.ProcessRequest() verwendet, scheint es System.Web.HttpRuntime.Close() nirgendwo zu nennen.

Ich bin mir nicht ganz sicher, wie Sie die vorgefertigte Cassini Implementierung mit halten können (Microsoft.VisualStudio.WebHost.Server), wie Sie den System.Web.HttpRuntime.Close() Aufruf tritt innerhalb der Anwendungsdomäne von Cassini erstellt bekommen müssen ASP.Net hosten.

Als Referenz sind hier einige Teile meiner Arbeitseinheit Test mit integriertem Webhosting.

Meine Klasse WebServerHost ist eine sehr kleine Klasse, die das Marshalling von Anfragen in die von System.Web.Hosting.ApplicationHost.CreateApplicationHost() erstellte Anwendungsdomäne ermöglicht.

using System; 
using System.IO; 
using System.Web; 
using System.Web.Hosting; 

public class WebServerHost : 
    MarshalByRefObject 
{ 
    public void 
    Close() 
    { 
     HttpRuntime.Close(); 
    } 

    public void 
    ProcessRequest(WebServerContext context) 
    { 
     HttpRuntime.ProcessRequest(new WebServerRequest(context)); 
    } 
} 

Die WebServerContext Klasse ist einfach ein Wrapper um eine System.Net.HttpListenerContext-Instanz, die von System.MarshalByRefObject leitet Anrufe aus der neuen ASP.Net-Hosting-Domain zu ermöglichen, wieder in meine Domäne zu nennen.

using System; 
using System.Net; 

public class WebServerContext : 
    MarshalByRefObject 
{ 
    public 
    WebServerContext(HttpListenerContext context) 
    { 
     this.context = context; 
    } 

    // public methods and properties that forward to HttpListenerContext omitted 

    private HttpListenerContext 
    context; 
} 

Die WebServerRequest Klasse ist nur eine Implementierung der abstrakten System.Web.HttpWorkerRequest Klasse, die von der ASP.Net-Hosting-Domain über die WebServerContext Klasse zurück in meine Domain aufruft.

using System; 
using System.IO; 
using System.Web; 

class WebServerRequest : 
    HttpWorkerRequest 
{ 
    public 
    WebServerRequest(WebServerContext context) 
    { 
     this.context = context; 
    } 

    // implementation of HttpWorkerRequest methods omitted; they all just call 
    // methods and properties on context 

    private WebServerContext 
    context; 
} 

Die WebServer Klasse ist ein Controller zum Starten und Stoppen des Web-Servers. Beim Start wird die ASP.Net-Hosting-Domäne mit meiner Klasse WebServerHost als Proxy erstellt, um die Interaktion zu ermöglichen. Eine Instanz wird ebenfalls gestartet und ein separater Thread wird gestartet, um Verbindungen zu akzeptieren.Wenn Verbindungen hergestellt werden, wird ein Worker-Thread im Thread-Pool gestartet, um die Anfrage zu bearbeiten, wiederum über meine Klasse . Schließlich, wenn der Webserver angehalten wird, wird der Listener gestoppt, der Controller wartet darauf, dass die Thread akzeptierenden Verbindungen beendet werden, und dann wird der Listener geschlossen. Schließlich wird die HTTP-Laufzeit auch über einen Aufruf in die WebServerHost.Close()-Methode geschlossen.

using System; 
using System.IO; 
using System.Net; 
using System.Reflection; 
using System.Threading; 
using System.Web.Hosting; 

class WebServer 
{ 
    public static void 
    Start() 
    { 
     lock (typeof(WebServer)) 
     { 
      // do not start more than once 
      if (listener != null) 
       return; 

      // create web server host in new AppDomain 
      host = 
       (WebServerHost)ApplicationHost.CreateApplicationHost 
       (
        typeof(WebServerHost), 
        "/", 
        Path.GetTempPath() 
       ); 

      // start up the HTTP listener 
      listener = new HttpListener(); 
      listener.Prefixes.Add("http://*:8182/"); 
      listener.Start(); 

      acceptConnectionsThread = new Thread(acceptConnections); 
      acceptConnectionsThread.Start(); 
     } 
    } 

    public static void 
    Stop() 
    { 
     lock (typeof(WebServer)) 
     { 
      if (listener == null) 
       return; 

      // stop listening; will cause HttpListenerException in thread blocked on GetContext() 
      listener.Stop(); 

      // wait connection acceptance thread to exit 
      acceptConnectionsThread.Join(); 
      acceptConnectionsThread = null; 

      // close listener 
      listener.Close(); 
      listener = null; 

      // close host 
      host.Close(); 
      host = null; 
     } 
    } 

    private static WebServerHost 
    host = null; 

    private static HttpListener 
    listener = null; 

    private static Thread 
    acceptConnectionsThread; 

    private static void 
    acceptConnections(object state) 
    { 
     while (listener.IsListening) 
     { 
      try 
      { 
       HttpListenerContext context = listener.GetContext(); 
       ThreadPool.QueueUserWorkItem(handleConnection, context); 
      } 
      catch (HttpListenerException e) 
      { 
       // this exception is ignored; it will be thrown when web server is stopped and at that time 
       // listening will be set to false which will end the loop and the thread 
      } 
     } 
    } 

    private static void 
    handleConnection(object state) 
    { 
     host.ProcessRequest(new WebServerContext((HttpListenerContext)state)); 
    } 
} 

Schließlich ist diese Initialization Klasse, mit dem NUnit markiert [SetupFixture] Attribut, verwendet wird, um den Web-Server zu starten, wenn die Unit-Tests gestartet werden, und schalten Sie ihn aus, wenn sie abgeschlossen sind.

using System; 
using NUnit.Framework; 

[SetUpFixture] 
public class Initialization 
{ 
    [SetUp] 
    public void 
    Setup() 
    { 
     // start the local web server 
     WebServer.Start(); 
    } 

    [TearDown] 
    public void 
    TearDown() 
    { 
     // stop the local web server 
     WebServer.Stop(); 
    } 
} 

Ich weiß, dass dies die Frage nicht genau beantworten, aber ich hoffe, dass Sie die Informationen nützlich finden.

+0

Ich frage mich, wie ein bestimmter Test aussehen würde? Wird der Textkörper in irgendeine Art von Execute-Aufruf eingeschlossen, um die tatsächliche Ausführung an die erstellte Anwendungsdomäne zu delegieren? –