3

以下のようなメソッドがあります

public List<Rajnikanth> GetRajnis()
{
    string username = Utility.Helpers.GetLoggedInUserName();
    return _service.GetRajni(username);
}   

Utility.Helper は静的クラスであり、public static class Helpers {

public static String GetLoggedInUserName()
{
    string username = "";
    if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
    {
        username = ((System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity).Ticket.Name;
    }
    return username;

}

}

テストしたい: GetRajnis()

モックしたい: GetLoggedInUserName()

したがって、私のテスト方法は次のようになります...

[TestMethod]
public void TestGetRajnis()
{
    SomeController s = new SomeController(new SomeService());
    var data = s.GetRajnis();
    Assert.IsNotNull(data);
}

静的メソッド GetLoggedInUserName() をモックするにはどうすればよいですか?

4

2 に答える 2

4

最も簡単なアプローチ: 戻り値をオーバーライドする

戻り値をモックする場合、これは非常に簡単です。クラスを変更しUtility.Helperて、 というプロパティを含めることができますOverrideLoggedInUserName。誰かが を呼び出したときGetLogedInUserName()に、オーバーライド プロパティが設定されている場合はそれが返されます。それ以外の場合は、HttpContext から値を取得する通常のコードを使用して戻り値を取得します。

public static class Helper
{
    // Set this value to override the return value of GetLoggedInUserName().
    public static string OverrideLoggedInUserName { get; set; };

    public static string GetLoggedInUserName()
    {
        // Return mocked value if one is specified.
        if ( !string.IsNullOrEmpty( OverrideLoggedInUserName ) )
            return OverrideLoggedInUserName;

        // Normal implementation.
        string username = "";
        if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
        {
            username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
        }
        return username;
    }
}

これにより、技術的にはモックではなくスタブである戻り値を効果的にオーバーライドできます( Martin Fowler による優れた記事Mocks Aren't Stubsによると) 。これにより、戻り値をスタブ化できますが、メソッドが呼び出されたかどうかをアサートすることはできません。とにかく、戻り値を操作したいだけであれば、これはうまくいきます。

これをテストで使用する方法を次に示します。

[ TestMethod ]
public void TestGetRajnis()
{
    // Set logged in user name to be "Bob".
    Helper.OverrideLoggedInUserName = "Bob";

    SomeController s = new SomeController( new SomeService() );
    var data = s.GetRajnis();

    // Any assertions...
}

この設計には 1 つの欠点があります。これは静的クラスであるため、オーバーライド値を設定すると、設定を解除するまで設定されたままになります。そのため、null にリセットすることを忘れないでください。

より良いアプローチ: 依存関係を注入する

ログインしているユーザー名を取得するクラスを作成し、それを のコンストラクターに渡す方法をお勧めしますSomeControllerこれを依存性注入と呼びます。このようにして、モック化されたインスタンスをテスト用に注入できますが、テストしていないときは (HttpContext からユーザーを取得する) 実際のインスタンスを渡します。これは、よりクリーンで明確なアプローチです。さらに、このアプローチを処理するように特別に設計されているため、使用しているモック フレームワークのすべての機能を活用できます。これがどのように見えるかです。

// Define interface to get the logged in user name.
public interface ILoggedInUserInfo
{
    string GetLoggedInUserName();
}

// Implementation that gets logged in user name from HttpContext. 
// This class will be used in production code.
public class LoggedInUserInfo : ILoggedInUserInfo
{
    public string GetLoggedInUserName()
    {
        // This is the same code you had in your example.
        string username = "";
        if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
        {
            username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
        }
        return username;
    }
}

// This controller uses the ILoggedInUserInfo interface 
// to get the logged in user name.
public class SomeController
{
    private SomeService _service;
    private ILoggedInUserInfo _userInfo;

    // Constructor allows you inject an object that tells it 
    // how to get the logged in user info.
    public SomeController( SomeService service, ILoggedInUserInfo userInfo )
    {
        _service = service;
        _userInfo = userInfo;
    }

    public List< Rajnikanth > GetRajnis()
    {
        // Use the injected object to get the logged in user name.
        string username = _userInfo.GetLoggedInUserName();
        return _service.GetRajni( username );
    }
}

これは、Rhino Mocks を使用してスタブ オブジェクトをコントローラーに挿入するテストです。

[ TestMethod ]
public void TestGetRajnis()
{
    // Create a stub that returns "Bob" as the current logged in user name.
    // This code uses Rhino Mocks mocking framework...
    var userInfo = MockRepository.GenerateStub< ILoggedInUserInfo >();
    userInfo.Stub( x => x.GetLoggedInUserName() ).Return( "Bob" );

    SomeController s = new SomeController( new SomeService(), userInfo );
    var data = s.GetRajnis();

    // Any assertions...
}

ここでの欠点は、Helper.GetLoggedInUserName()静的ではないため、コードのどこからでも呼び出すことができないことです。ただし、テストを終了するたびにスタブ化されたユーザー名をリセットする必要はなくなりました。静的ではないため、自動的にリセットされます。次のテスト用に再作成し、新しい戻り値を設定するだけです。

これが役立つことを願っています。

于 2012-10-08T18:23:58.967 に答える
2

テスト容易性を求める場合は、静的クラスを取り除きます。現時点での簡単な修正は、静的クラスのラッパーを作成することです。TypeMock のようなもの、または同等に強力なものを使用しない限り、静的クラスのロジックを変更することはできません。また、私はそれを提案しません。静的クラスをスタブする必要がある場合は、おそらく静的クラスであってはなりません。

public class StaticWrapper
{
    public virtual String GetLoggedInUserName()
    {
        Utility.Helpers.GetLoggedInUserName();
    }
}
于 2012-10-08T14:04:29.877 に答える