设定私有财产的价值

时间:2013-01-06 22:53:11

标签: c# properties mocking

我正在尝试测试我的课程

public class Parser
{

    private static IDictionary<String, Regex> PhrasesToRegexp { get; set; }

    public static void InitPhrases(IList<String> Phrases, Boolean useDeclination )
    {
        throw new NotImplementedException();
    }

    ...

    public ParsingResults Find(String source)
    {
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(source);
        return new ParsingResults(FindUrls(doc), CountPhrases(doc));
    }



    private IList<String> FindUrls(HtmlDocument source)
    {
        return source.DocumentNode.SelectNodes("//a[@href]").
            Select(link => link.GetAttributeValue("href", "")).ToList();
    }

    private IDictionary<String, int> CountPhrases(HtmlDocument source)
    {
        IDictionary<String, int> results = new Dictionary<String, int>();
        foreach (String key in PhrasesToRegexp.Keys)
        {
            results.Add( key , 0 );
        }

        foreach (HtmlNode node in source.DocumentNode.SelectNodes("//p"))
        {
            foreach (String phrase in results.Keys)
            {
                results[phrase] += PhrasesToRegexp[phrase].Matches
                    (Regex.Replace(node.InnerText, @"<(.|\n)*?>", string.Empty)).Count;
            }
        }
        return results;
    }

}

问题是PhrasesToRegexp中的属性InitPhrases已初始化,我正在为方法Find尝试写单元测试。基本上我需要设置此私有属性PhrasesToRegexp的值。有没有办法做到这一点?我不是模拟专家,但我认为他们不会这样做,因为这个属性和测试方法都在同一个对象中。

5 个答案:

答案 0 :(得分:1)

你可以专门为单元测试添加一个新的构造函数,但我建议尽量减少对类的任何更改,使其可以进行单元测试。支持单元测试的专业化通常意味着您没有测试将在最终应用程序中运行的实际代码。你越专业,越难以确保真正的代码得到充分测试,并且更多的机会在被测试的代码中引入不必要的副作用。

相反,(如果可能的话)我尝试将该类用作客户端 - 如果您构造实例并将该方法作为客户端调用,则您不需要在私有状态下进行调用,并且您的单元测试将测试任何客户端代码将使用的确切内容。此外,如果您更改了类的内部工作,则您的测试更有可能保持有效/有效,因为没有特殊的单元测试途径,您可能忘记与代码更改保持同步。

如果您希望公开该属性并直接生成它,那么将其更改为internal并使用InternalsVisibleTo是一种标准方法,但它会冒犯我的封装感,因为它对每个人来说都是永久内部的。另一个程序员怎么知道你的意思是“内部测试”而不是“嘿,我们是好朋友,请尽可能多地参与我的内部状态”。如果我们想要进行单元测试时将它扔掉,那么私有的是什么?因此,另一种保持代码私有的方法是使用特殊的构建进行单元测试,它设置了一个#define,允许您公开要为测试访问的私有,同时将它们保留为正常构建中的私有。

一种方式是对财产本身的暴力(但这可能非常混乱):

#if UNIT_TEST
    public
#else
    private
#endif
int MyPrivateProperty { get; set; }

或者,更简洁的方法(但更多工作)是保持原始代码不受损害并添加访问方法,以最大限度地减少您无意中破坏/更改测试代码的可能性。

private int MyProperty { get; set; }

#if UNIT_TEST
    public int AccessMyProperty
    {
        get { return(MyProperty); }
        set { MyProperty = value; }
    }
#endif 

答案 1 :(得分:1)

在我的单元测试项目中,我创建了一个扩展原始类的MockClass,不会重写任何方法或属性。

  • 原始类:私有属性被提升为受保护。
  • 模拟类:创建一个设置受保护属性的公共方法。

使用这种方法,您不必将可见性更改为内部,这比受保护的更明显。当然,这也意味着如果开发人员想要访问您的财产,他可以通过扩展您的课程,就像您的单元测试一样。

public class Parser
{
      protected static IDictionary<String, Regex> PhrasesToRegexp { get; set; }
      ...
}

public class MockParser : Parser
{
     public MockParser() : base()
     {
     }

     public void AddPhraseToRegexp(String key, Regex value)
     {
         // Add it
         PhrasesToRegexp.Add(key, value);
     }

     public void CreatePhrasesToRegexp(IDictionary<String, Regex> newDict)
     {
         // Create a new Dictionary
         PhrasesToRegexp = newDict;
     }
}

注意:这不适用于静态或密封类

答案 2 :(得分:0)

这可能取决于您使用的模拟框架。我通常使用Moq或NMock2,在这些情况下,可行的选项是:

1)使属性内部而不是私有,这允许您在单元测试中设置它。如果您的单元测试位于单独的项目中,则可能必须使用InternalsVisibleTo属性。

2)为单元测试purpoeses创建一个单独的构造函数,接受PhrasesToRegexp

答案 3 :(得分:0)

1)您应该将private声明为internal,而不是[assembly: InternalsVisibleTo("MainFormUnitTests")]

2)在AssemblyInfo.cs中你应该添加程序集,从那里你的类将被访问如下

{{1}}

答案 4 :(得分:0)

对我而言,出现此问题是因为您在解析器中表达了两个不同的问题。

  1. 您的词组和正则表达式的快速参考缓存
  2. 解析逻辑
  3. 我建议将这两个问题分成两个不同的类别。此外,您可能希望解决在某些时候清除静态字典的内存(或使缓存无效)。

    如果你将缓存逻辑放在ICachePhrasesAndRegex接口后面,那么你可以轻松地模拟测试的依赖性。