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()时,如果设置了 override 属性,则返回,否则使用正常的从 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...
}

这种设计确实有一个缺点。因为它是一个静态类,如果您设置了覆盖值,它会一直保持设置,直到您取消设置它。因此,您必须记住将其重新设置为空。

更好的方法:注入依赖

更好的方法可能是创建一个检索登录用户名的类,并将其传递给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 回答