wake-up-neo.net

Komponententest HttpContext.Current.Cache oder andere serverseitige Methoden in C #?

Beim Erstellen eines Komponententests für eine Klasse, die HttpContext.Current.Cache class verwendet, wird bei der Verwendung von NUnit eine Fehlermeldung angezeigt. Die Funktionalität ist einfach: Überprüfen Sie, ob sich ein Element im Cache befindet. Wenn nicht, erstellen Sie es und legen Sie es ab:

if (HttpContext.Current.Cache["Some_Key"] == null) {
    myObject = new Object();
    HttpContext.Current.Cache.Insert("Some_Key", myObject);
}
else {
    myObject = HttpContext.Current.Cache.Get("Some_Key");
}

Wenn dies aus einem Unit-Test aufgerufen wird, schlägt es mit at NullReferenceException fehl, wenn die erste Cache-Zeile angetroffen wird. In Java würde ich Cactus verwenden, um serverseitigen Code zu testen. Gibt es ein ähnliches Tool, das ich für C # -Code verwenden kann? Diese SO Frage erwähnt Mock-Frameworks - ist dies die einzige Möglichkeit, diese Methoden zu testen? Gibt es ein ähnliches Tool zum Ausführen von Tests für C #?

Außerdem überprüfe ich nicht, ob die Cache null ist, da ich keinen Code speziell für den Komponententest schreiben möchte und davon ausgehe, dass er immer gültig ist, wenn er auf einem Server ausgeführt wird. Ist dies gültig, oder sollte ich den Cache mit Nullprüfungen versehen?

33
Tai Squared

Um dies zu tun, vermeiden Sie die direkte Verwendung von HttpContext oder anderen ähnlichen Klassen und ersetzen Sie sie durch Mocks. Schließlich versuchen Sie nicht, die ordnungsgemäße Funktion von HttpContext zu testen (das ist die Aufgabe von Microsoft), sondern nur zu testen, ob die Methoden aufgerufen wurden, wenn sie sollten.

Schritte (Für den Fall, dass Sie nur die Technik kennen lernen möchten, ohne jede Menge Blogs zu durchsuchen):

  1. Erstellen Sie eine Schnittstelle, die die Methoden beschreibt, die Sie in Ihrem Caching verwenden möchten (wahrscheinlich Dinge wie GetItem, SetItem, ExpireItem). Nennen Sie es ICache oder was auch immer Sie mögen

  2. Erstellen Sie eine Klasse, die diese Schnittstelle implementiert und Methoden an den realen HttpContext übergibt

  3. Erstellen Sie eine Klasse, die dieselbe Schnittstelle implementiert und nur wie ein Scheincache fungiert. Es kann ein Wörterbuch oder etwas anderes verwenden, wenn Sie Objekte speichern möchten

  4. Ändern Sie den ursprünglichen Code so, dass der HTTP-Kontext überhaupt nicht verwendet wird und stattdessen immer nur ein ICache verwendet wird. Der Code muss dann eine Instanz des ICache abrufen - Sie können entweder eine Instanz in Ihrem Klassenkonstruktor übergeben (das ist alles, was die Abhängigkeitsinjektion wirklich ist) oder sie in eine globale Variable einfügen.

  5. Stellen Sie in Ihrer Produktions-App den ICache als Ihren echten HttpContext-Backed-Cache ein und stellen Sie den ICache in Ihren Unit-Tests als Schein-Cache ein.

  6. Profitieren!

42
Orion Edwards

Ich stimme den anderen zu, dass die Verwendung einer Schnittstelle die beste Option ist, aber manchmal ist es einfach nicht möglich, ein vorhandenes System zu ändern. Hier ist ein Code, den ich gerade aus einem meiner Projekte zusammengestellt habe und der Ihnen die gewünschten Ergebnisse liefern soll. Es ist die am weitesten entfernte Sache von einer hübschen oder großartigen Lösung, aber wenn Sie Ihren Code wirklich nicht ändern können, sollte es die Arbeit erledigen.

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

[TestFixture]
public class HttpContextCreation
{
    [Test]
    public void TestCache()
    {
        var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
        var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
        SetPrivateInstanceFieldValue(result, "m_HostContext", context);

        Assert.That(HttpContext.Current.Cache["val"], Is.Null);

        HttpContext.Current.Cache["val"] = "testValue";
        Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue"));
    }

    private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
    {
        var sb = new StringBuilder();
        var sw = new StringWriter(sb);
        var hres = new HttpResponse(sw);
        var hreq = new HttpRequest(fileName, url, queryString);
        var httpc = new HttpContext(hreq, hres);
        return httpc;
    }

    private static object RunInstanceMethod(object source, string method, object[] objParams)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        var type = source.GetType();
        var m = type.GetMethod(method, flags);
        if (m == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
        }

        var objRet = m.Invoke(source, objParams);
        return objRet;
    }

    public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
    {
        var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null)
        {
            throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
        }

        field.SetValue(source, value);
    }
}
29
Brian Surowiec
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
16
Shawn Miller

Wenn Sie .NET 3.5 verwenden, können Sie System.Web.Abstractions in Ihrer Anwendung verwenden.

Justin Etheredge hat eine großartige post Anleitung zum Verspotten von HttpContext (der die Cache-Klasse enthält).

In Justins Beispiel übergebe ich meinen Controllern eine HttpContextBase mithilfe von HttpContextFactory.GetHttpContext. Wenn ich sie verspotte, erstelle ich einfach einen Mock, um das Cache-Objekt aufzurufen.

6
Steve Wright

Es gibt einen neueren Ansatz, um bei Unit-Tests speziell mit dem Cache umzugehen.

Ich würde empfehlen, den neuen MemoryCache.Default -Ansatz von Microsoft zu verwenden. Sie müssen .NET Framework 4.0 oder höher verwenden und einen Verweis auf System.Runtime.Caching einfügen.

Siehe Artikel hier -> http://msdn.Microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default funktioniert sowohl für Webanwendungen als auch für Nicht-Webanwendungen. Daher sollten Sie Ihre Webanwendung aktualisieren, um Verweise auf HttpContext.Current.Cache zu entfernen und durch Verweise auf MemoryCache.Default zu ersetzen. Wenn Sie später die gleichen Methoden ausführen, ist das Cache-Objekt immer noch verfügbar und wird nicht null sein. (Weil es nicht auf einen HttpContext angewiesen ist.)

Auf diese Weise müssen Sie die Cache-Komponente nicht unbedingt verspotten.

5
ClearCloud8

Allgemeiner Konsens scheint zu sein, dass das Fahren von HttpContext innerhalb eines Komponententests ein Albtraum ist und nach Möglichkeit vermieden werden sollte.

Ich denke, Sie sind auf dem richtigen Weg, was das Verspotten betrifft. Ich mag RhinoMocks ( http://ayende.com/projects/rhino-mocks.aspx ).

Ich habe auch einige gute Dinge über MoQ gelesen ( http://code.google.com/p/moq ), obwohl ich es noch nicht ausprobiert habe.

Wenn Sie wirklich Unit-testbare Web-Benutzeroberflächen in C # schreiben möchten, verwenden Sie anscheinend eher das MVC-Framework ( http://www.asp.net/mvc ) als WebForms ...

2
Antony Perkov

Sie können die HttpContextBase-Klasse in System.Web.Abstractions.dll verwenden. Es ist eine neue DLL in .NET 3.5.

Ein Beispiel für die Verwendung finden Sie unter dem folgenden Link.

http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/

2
Vadim

Das ist vielleicht Ihre Straße hinauf ... Phil Haack zeigt mit Hilfe von Rhino-Mocks, wie man httpcontext in asp mvc verspottet, aber ich könnte mir vorstellen, dass dies auf Webformulare angewendet werden kann.

Clicky !!

Hoffe das hilft.

1
WestDiscGolf

wenn Sie sich nicht dafür interessieren, den Cache zu testen, können Sie Folgendes tun:

[TestInitialize]
    public void TestInit()
    {
      HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
    }

Auch sie können moq wie unten

var controllerContext = new Mock<ControllerContext>();
      controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
      controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
1
PUG

Wie jeder hier sagte, gibt es ein Problem mit HTTPContext, derzeit ist Typemock das einzige Framework, das es direkt ohne Wrapper oder Abstraktionen fälschen kann.

0
Ron_w

Das Cache-Objekt ist schwer zu verspotten, da es ein versiegelter Bereich des .NET-Frameworks ist. Normalerweise kann ich das umgehen, indem ich eine Cache-Wrapper-Klasse erstelle, die ein Cache-Manager-Objekt akzeptiert. Zum Testen verwende ich einen Schein-Cache-Manager. Für die Produktion verwende ich einen Cache-Manager, der tatsächlich auf HttpRuntime.Cache zugreift.

Grundsätzlich abstrahiere ich den Cache selbst.

0
Chris

Ein Beispiel für Benutzer von MVC 3 und MOQ:

Meine Controller-Methode hat die folgende Zeile:

model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList] 
as Dictionary<int, string>);

Daher schlägt jeder Komponententest fehl, da ich HttpContext.Cache nicht einrichte.

In meinem Unit-Test ordne ich Folgendes an:

 HttpRuntime.Cache[Constants.C_CustomerTitleList] = new Dictionary<int, string>();

 var mockRequest = new Mock<HttpRequestBase>();
 mockRequest.SetupGet(m => m.Url).Returns(new Uri("http://localhost"));

 var context = new Mock<HttpContextBase>(MockBehavior.Strict);
 context.SetupGet(x => x.Request).Returns(mockRequest.Object);
 context.SetupGet(x => x.Cache).Returns(HttpRuntime.Cache);

 var controllerContext = new Mock<ControllerContext>();
 controllerContext.SetupGet(x => x.HttpContext).Returns(context.Object);

 customerController.ControllerContext = controllerContext.Object;
0
Duncan

Könnte versuchen...

 Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake();
 var fakeSession = HttpContext.Current.Session;
 Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");

Alle diese Programmierfragen erfordern ein schnittstellenbasiertes Programmiermodell, in dem Sie die Schnittstelle zweimal implementieren. Eine für den realen Code und eine für das Modell.

Instantiierung ist dann das nächste Problem. Es gibt verschiedene Designmuster, die dafür verwendet werden können. Siehe zum Beispiel die berühmten GangOfFour Creational-Muster ( GOF ) oder die Dependency Injection-Muster.

ASP.Net MVC verwendet diesen schnittstellenbasierten Ansatz und eignet sich daher viel besser für Unit-Tests.

0
Rine