web-dev-qa-db-ja.com

ユニットテストHttpContext.Current.CacheまたはC#の他のサーバー側メソッド?

HttpContext.Current.Cache class を使用するクラスの単体テストを作成すると、NUnitを使用するとエラーが発生します。機能は基本的です。アイテムがキャッシュにあるかどうかを確認し、ない場合は作成して次の場所に配置します。

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

単体テストからこれを呼び出すと、最初のNullReferenceException行に遭遇すると、Cacheで失敗します。 Javaでは、サーバー側のコードをテストするために Cactus を使用します。 C#コードに使用できる同様のツールはありますか? これSO質問 モックフレームワークについて言及しています-これがこれらのメソッドをテストできる唯一の方法ですか?C#のテストを実行するための同様のツールはありますか?

また、単体テスト専用のコードを記述したくないため、Cacheがnullであるかどうかは確認せず、サーバーで実行すると常に有効になると想定しています。これは有効ですか、それともキャッシュの周りにnullチェックを追加する必要がありますか?

33
Tai Squared

これを行う方法は、HttpContextまたは他の同様のクラスの直接使用を避け、それらをモックに置き換えることです。結局のところ、HttpContextが正しく機能することをテストしようとしているのではなく(Microsoftの仕事です)、メソッドが必要なときに呼び出されたことをテストしようとしているだけです。

手順(たくさんのブログを掘り下げずにテクニックを知りたいだけの場合):

  1. キャッシュで使用するメソッド(おそらくGetItem、SetItem、ExpireItemなど)を説明するインターフェイスを作成します。それをICacheまたはあなたが好きなものと呼んでください

  2. そのインターフェイスを実装するクラスを作成し、メソッドを実際のHttpContextに渡します

  3. 同じインターフェースを実装し、モックキャッシュのように機能するクラスを作成します。オブジェクトの保存に関心がある場合は、辞書などを使用できます

  4. 元のコードを変更して、HttpContextをまったく使用せず、代わりにICacheのみを使用するようにします。次に、コードはICacheのインスタンスを取得する必要があります。インスタンスをクラスコンストラクターに渡すか(これが依存性注入が実際に行うすべてです)、またはグローバル変数に貼り付けることができます。

  5. 本番アプリでは、ICacheを実際のHttpContext-Backed-Cacheに設定し、単体テストでは、ICacheをモックキャッシュに設定します。

  6. 利益!

42
Orion Edwards

インターフェースを使用するのが最善の選択肢であるという他の人たちの意見に同意しますが、既存のシステムを変更することが不可能な場合もあります。これが私のプロジェクトの1つからマッシュアップしたばかりのコードで、あなたが探している結果が得られるはずです。これは、きれいなソリューションや優れたソリューションから最も遠いものですが、実際にコードを変更できない場合は、作業を完了する必要があります。

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

.NET 3.5を使用している場合は、アプリケーションでSystem.Web.Abstractionsを使用できます。

Justin Etheredgeには、HttpContext(キャッシュクラスを含む)をモックする方法についてのすばらしい post があります。

Justinの例から、HttpContextFactory.GetHttpContextを使用してHttpContextBaseをコントローラーに渡します。それらをモックするときは、キャッシュオブジェクトを呼び出すためのモックを作成するだけです。

6
Steve Wright

単体テストでキャッシュを具体的に処理するのに役立つ新しいアプローチがあります。

Microsoftの新しいMemoryCache.Defaultアプローチを使用することをお勧めします。 .NET Framework 4.0以降を使用し、System.Runtime.Cachingへの参照を含める必要があります。

こちらの記事を参照してください-> http://msdn.Microsoft.com/en-us/library/dd997357(v = vs.100).aspx

MemoryCache.Defaultは、Webアプリケーションと非Webアプリケーションの両方で機能します。つまり、Webアプリを更新して、HttpContext.Current.Cacheへの参照を削除し、それらをMemoryCache.Defaultへの参照に置き換えるという考え方です。後で、これらの同じメソッドを単体テストすることを決定して実行すると、キャッシュオブジェクトは引き続き使用可能であり、nullにはなりません。 (HttpContextに依存していないためです。)

この方法では、必ずしもキャッシュコンポーネントをモックする必要はありません。

5
ClearCloud8

一般的なコンセンサスは、単体テスト内からHttpContextに関連するものを駆動することは完全に悪夢であり、可能であれば避けるべきであるということのようです。

あなたはモックに関して正しい道を進んでいると思います。私はRhinoMocksが好きです( http://ayende.com/projects/rhino-mocks.aspx )。

まだ試していませんが、MoQの良いところもいくつか読んでいます( http://code.google.com/p/moq )。

ユニットテスト可能なWebUIをC#で本当に作成したい場合、人々が向かっているように見える方法は、WebFormsではなくMVCフレームワーク( http://www.asp.net/mvc )を使用することです。 ..

2
Antony Perkov

System.Web.Abstractions.dllでHttpContextBaseクラスを使用できます。これは、.NET3.5の新しいdllです。

以下のリンクで使用方法の例を見つけることができます。

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

2
Vadim

これはおそらくあなたの通りです... Phil Haackは、Rhinoモックの助けを借りて、asp mvcでhttpcontextをモックする方法を披露しますが、Webフォームに適用できると思います。

クリック!!

お役に立てれば。

1
WestDiscGolf

キャッシュのテストを気にしない場合は、以下を実行できます。

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

また、あなたは以下のようにmoqすることができます

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

誰もがここで言ったように、HTTPContextには問題があります。現在、 Typemock は、ラッパーや抽象化なしで直接偽造できる唯一のフレームワークです。

0
Ron_w

キャッシュオブジェクトは.NETFrameworkの封印された領域であるため、モックを作成するのは困難です。私は通常、キャッシュマネージャーオブジェクトを受け入れるキャッシュラッパークラスを構築することでこれを回避します。テストには、モックキャッシュマネージャーを使用します。本番環境では、実際にHttpRuntime.Cacheにアクセスするキャッシュマネージャーを使用します。

基本的に、私は自分でキャッシュを抽象化します。

0
Chris

MVC 3とMOQを使用している人の例:

私のコントローラーメソッドには次の行があります。

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

そのため、HttpContext.Cacheを設定していないため、単体テストは失敗します。

私の単体テストでは、次のように調整します。

 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

試すことができます...

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

これらのプログラミングの質問はすべて、インターフェイスを2回実装するインターフェイスベースのプログラミングモデルを求めています。 1つは実際のコード用で、もう1つはモックアップ用です。

次に、インスタンス化が次の問題になります。そのために使用できるいくつかのデザインパターンがあります。たとえば、有名なGangOfFour作成パターン( [〜#〜] gof [〜#〜] )または依存性注入パターンを参照してください。

ASP.Net MVCは、実際にはこのインターフェイスベースのアプローチを使用しているため、単体テストにはるかに適しています。

0
Rine