C# 如何测试通过公共方法修改的私有字段

C# 如何测试通过公共方法修改的私有字段,c#,.net,unit-testing,C#,.net,Unit Testing,有谁能给我推荐一种方法来测试通过公共方法修改的类中的私有字段吗。我读过很多人的评论,他们建议不建议测试私有成员,因为它们是实现的内部成员,但是这个场景似乎与大多数其他答案不同 我在考虑保护私有字段并创建一个测试子类来公开该字段,但是如果我不能修改类的内部内容呢 在下面的示例代码中,要测试的私有成员将是_values,它是一个只写的集合,通过AddValue()接收新值 公共类示例 { 私有字典(private Dictionary)值;; 创建私有日期时间; 公共样本() { _值=新字典();

有谁能给我推荐一种方法来测试通过公共方法修改的类中的私有字段吗。我读过很多人的评论,他们建议不建议测试私有成员,因为它们是实现的内部成员,但是这个场景似乎与大多数其他答案不同

我在考虑保护私有字段并创建一个测试子类来公开该字段,但是如果我不能修改类的内部内容呢

在下面的示例代码中,要测试的私有成员将是_values,它是一个只写的集合,通过AddValue()接收新值

公共类示例
{
私有字典(private Dictionary)值;;
创建私有日期时间;
公共样本()
{
_值=新字典();
_created=DateTime.Now;
}
public void AddValue(字符串键、字符串值)
{
_值。添加(键、值);
}
}

您仍然不需要测试私有变量。您可以测试您的接口。在这种情况下,我可能会添加一个
Count
属性,并将其用于某些测试(或类似的测试)

如果您想验证
\u值
在调用
Sample之后是否包含您期望的值,则可以使用该属性进行更改

private Dictionary<string, string> _values;
private Dictionary\u值;
将来

内部字典\u值;
然后为您的项目向AssemblyInfo.cs添加一个,引用您的测试项目。这将向您的测试项目公开_values字段


这远非理想,因为您将“测试”内部实现细节,但如果是这样或什么都没有,那么这就是一个起点

我认为单元测试的思想是通过对象的公共表面而不是内部实现来测试对象的行为。换句话说,您不应该在测试中访问
\u值
;相反,您应该调用public
AddValue()
方法来添加一些键/值,然后调用其他一些公共方法,例如
GetKeys()
或其他方法来再次获取信息。然后,您的单元测试应该根据输入的信息测试该信息是否正确


这样做的主要原因是单元测试不应该对实现细节做出任何假设。想象一下,无论出于何种原因,您需要稍后将
词典
更改为
分类词典
,或者突然需要两个单独的词典。您不需要对单元测试进行任何更改就可以工作;您应该能够更改内部实现,然后使用您已经获得的相同单元测试来验证其正确性。

正如Rob指出的,不建议您访问私有字段,但是如果您迫不及待地必须并且无法实际测试分配给该字段的值,您可以这样做:

        Type sampleType = sampleInstance.GetType();
        FieldInfo fieldInfo = sampleType.GetField("_values", BindingFlags.Instance, BindingFlags.NonPublic);
        Dictionary<string, string> info = fieldInfo.GetValue(sampleType);
Type sampleType=sampleInstance.GetType();
FieldInfo FieldInfo=sampleType.GetField(“\u值”,BindingFlags.Instance,BindingFlags.NonPublic);
字典信息=fieldInfo.GetValue(sampleType);

我认为这是一个依赖注入可能会有所帮助的例子

这里发生的事情是,您希望通过调用
AddValue
来测试内部对象(dictionary
\u values
)是否正确更新。您可以通过向被测试的类中注入一个模拟字典来实现这一点

可以这样做,例如如下所示。首先,您必须稍微更改
示例
类:

public class Sample
{
    private IDictionary<string, string> _values = new Dictionary<string, string>();

    protected virtual IDictionary<string, string> GetDictionary()
    {
        return this._values;
    }

    public void AddValue(string key, string value)
    {
        GetDictionary().Add(key, value);
        //    ^^^
        // notice this!
    }
}
在测试设置中,您现在可以测试这个
SampleTest
类,而不是
Sample
类。这应该可以,因为派生类是相同的,只是它允许您指定它将在内部使用的字典。检查
AddValue
的单元测试现在可以如下所示:

[Test]
public void AddValue_addSomething_DictionaryHasOneAdditionalEntry()
{
    var mockDictionary = new Dictionary<string, string>();
    var sample = new SampleTest(mockDictionary);
    var oldCount = mockDictionary.Count;

    sample.AddValue(...);

    Assert.AreEqual(oldCount + 1, mockDictionary.Count);
}
[测试]
public void AddValue\u addSomething\u dictionary作为附加条目()
{
var mockDictionary=newdictionary();
var样本=新样本测试(mockDictionary);
var oldCount=mockDictionary.Count;
样本.附加值(…);
arenequal(oldCount+1,mockDictionary.Count);
}


免责声明:我绝不是单元测试专家,因此我的示例可能有缺陷,甚至过于复杂。我的意图仅仅是证明,如果您以合理的可测试方式设计类,您可以测试类的内部属性,例如,通过允许依赖项注入的方式。

这个示例“有什么不同”吗?该类提供了哪些需要测试的功能?@Rex我不确定它是否不同,我认为它“似乎”不同。在这种情况下,内部元件只是一个存储区域。我想测试公共方法是否正常工作,但是唯一的方法是检查私有字段。(真实的类在AddValue方法中会有更复杂的逻辑)有趣的想法Rob,令人恶心,但就像你说的,这是一个起点,也许我想得太多了。添加一个简单的Count属性确实可以很好地工作。不确定您为什么被否决,但内部字典不能是公共的,它需要是私有的,并且只写。@WDuffy:我的回答中没有任何内容表明它是公共的。不清楚你所说的“只写”是什么意思。如果你真的只给字典写信,再也不读字典,那么它就没有用了,你可以把它完全删除。我会这样做的。
public class Sample
{
    private IDictionary<string, string> _values = new Dictionary<string, string>();

    protected virtual IDictionary<string, string> GetDictionary()
    {
        return this._values;
    }

    public void AddValue(string key, string value)
    {
        GetDictionary().Add(key, value);
        //    ^^^
        // notice this!
    }
}
// this derived class is only needed in your test project:
internal class SampleTest : Sample
{
    public SampleTest(IDictionary<string, string> dictionaryToUse)
    {
        this._dictionaryToUse = dictionaryToUse;
    }

    private IDictionary<string, string> _dictionaryToUse;

    protected override IDictionary<string, string> GetDictionary()
    {
        return this._dictionaryToUse;
    }
}
[Test]
public void AddValue_addSomething_DictionaryHasOneAdditionalEntry()
{
    var mockDictionary = new Dictionary<string, string>();
    var sample = new SampleTest(mockDictionary);
    var oldCount = mockDictionary.Count;

    sample.AddValue(...);

    Assert.AreEqual(oldCount + 1, mockDictionary.Count);
}