通过公共属性对存储库进行单元测试

时间:2015-11-09 12:41:38

标签: c# unit-testing

在当前的WPF应用程序中,我们需要在首次使用时检索用户对象,然后在用户使用应用程序时保留该用户对象。

我们计划用静态方法做到这一点。

public static class UserHelper
{
    public static User CurrentUser { get; set; }
}

但是,用户数据保存在数据库中。所以我们需要在这个助手类中启动我们的存储库副本来完成这项工作:

public static class UserHelper
{
    public static User CurrentUser { get; set; }
    private static IRepository _rep = new Repository();

    public static void SetUser(string username)
    {
        CurrentUser = _rep.GetUserByName(username);
    }
}

然而,这不是单元可测试的 - 您无法通过构造函数将存储库传递给静态类。因此,最简单的解决方案似乎是将_rep公开(并将其重命名为Rep) - 这样您仍然可以将其作为新的Repository()保留在类中,但在测试时我们可以做到这一点:

[TestMethod] 
public void Assert_SomethingAboutUsers
{
    Mock<IRepository> repMock = new Mock<IRepository>();
    repMock.Setup(z => z.GetUserByName(It.IsAny<string>())).Returns(new User());
    UserHelper.Rep = repMock.Object;

    // make assertions
}

但这对我来说闻起来很有趣。我觉得在静态类中存放存储库有点尴尬。

这是个坏主意吗?如果是这样,为什么?

2 个答案:

答案 0 :(得分:3)

我认为问题在于,在您的设计中,您对UserHelper的任务和功能的想法并不清楚。

如果你要描述一个UserHelper,你会说它只是为了从这个唯一的存储库中获取信息,或者你会说用户帮助器能够与(可能很小的)范围一起使用存储库?

假设UserHelper仅用于此唯一存储库。在这种情况下,如果您使用不同的存储库,那么您的设计将是一个不同的东西。因此,单元测试使用自己的存储库是不正确的,因为单元测试会测试设计中的不同内容。

另一方面,如果您说 UserHelper应该可以使用各种存储库,那么为什么您的类是静态的?如果您的设计是这样的,那么用户助手可能会使用不同的存储库,那么在一个会话期间是否会有更多的用户帮助者,或者只能有一个?

如果您认为您对用户帮助程序的想法可能同时存在多个用户帮助程序,那么您的用户帮助程序不是单例,它不应该是静态的。

如果您拥有一组有限的用户帮助程序并且事先知道可能创建了哪些用户帮助程序,请考虑使用软件用户可以说的工厂:给我一个用户帮助程序......例如,一个用户帮助程序使用此ID,或使用此名称,或者使用此存储库,或者对于此特定用户而言非常适合的UserHelper等。任何能够唯一标识所需用户帮助程序的内容都足够了。

  

Google for Factory Design Pattern

然而,根据您的描述,我认为在您的设计中,用户帮助应该支持各种存储库(至少是您的普通存储库和特殊的单元测试存储库),但在一个会话期间只有一个用户帮助程序,称为< strong>唯一的用户帮助。此用户助手旨在与给定的存储库一起使用,至少在订购之前不会这样做。

每当你的设计描述类似类的唯一实例之类的东西时,人们往往会创建一个静态类,而实际上他们需要一个单例。

  

谷歌的单身设计模式。

MSDN about singleton

如果您认为在您的眼中,在您的程序的一个会话期间只有一个用户帮助程序,并且每个人都应该使用这个用户帮助程序对象,单例将如下所示:

public class UserHelper
{
    private static UserHelper theOneAndOnlyInstance = null;
    public static IRepository Repository {get; set;}

    // private constructor, so no one can create an instance
    private UserHelper() {}

    // the function to get the one and only instance
    public static UserHelper TheOneandOnlyUserHelper
    {
        get 
        {
            if (theOneAndOnlyInstance == null)
            {
                theOneAndOnlyInstance = new UserHelper();
            }
            return theOneAndOnlyInstance;
        }
    }
}

用法是:

public void InitializeProgram()
{
    UserHelper.Repository = GetRepository(...);
}

public void MyFunction()
{
    var userHelper = UserHelper.GetInstance()
    userHelper.SetUser(...);
}

public void MyOtherFunction()
{
    UserHelper.GetInstance().Function1();
}

请注意,GetInstance函数不是线程安全的。 Stackoverflow有几个关于如何使其线程安全的主题。

答案 1 :(得分:1)

问题是您的测试类与UserHelper具有静态依赖关系。静态实用程序类是单元测试的主要敌人。特别是当他们从数据库中读取繁重的工作时。

你有没有办法让UserHelper成为非静态类并将其注入依赖实例?这样您就可以创建UserHelper的模拟。

除此之外,UserHelper正在做两件事:它正在从数据库读取用户并保留User对象,以便将其提供给协作者。最好从UserHelper数据库中读取用户,并将其分配给CurrentUser属性。