如何在 C# 中测试 setter?为什么?

how to test a setter in c# ?and why?

我正在开始使用 C#。我被要求做一个作业,其中包含为 setter 编写单元测试并检查其输出。我不遵循测试不包含任何逻辑的非常琐碎的东西的意思。这是示例(SetKeywords()是要测试的方法):

public struct Keyword
{
    private string keyword;
    private KeywordTypes type;

    public Keyword(string keyword, KeywordTypes Type = 
    KeywordTypes.String)
    {
        this.keyword = keyword;
        this.type = Type;  
    }

    public string GetString()
    {
        return this.keyword;
    }

    public KeywordTypes WhichType()
    {
        return this.type;
    }
}

public class ShopParser
{
    private Keyword[] keywords = new Keyword[0];

    public void **SetKeywords**(Keyword[] tags)
    {
        keywords = tags;
    }
}
    public Keyword[] GetKeywords()
    {
            return this.keywords;
    } 
    public static KeywordPair[] ExtractFromTaG(ShopParser parser, string 
    serializedInput)
    {
            var findings = new KeywordPair[0];
            foreach (var keyword in parser.GetKeywords())
            {
                var start = serializedInput.IndexOf(keyword.GetStart());

                // Check if keyword is in input string, if not continue 
                with next keyword.

                if (start <= -1) continue;

                var end = serializedInput.LastIndexOf(keyword.GetEnd());

                // Extract the thing between the tags. Tag excluded
                start += keyword.GetStart().Length;

                var substring = serializedInput.Substring(start, end - 
                start);

                // Add substring to result list

                var tmp = new KeywordPair[findings.Length + 1];
                var i = 0;
                for (; i < findings.Length; ++i)
                {
                    tmp[i] = findings[i];
                }
                tmp[i] = new KeywordPair(keyword, substring);
                findings = tmp;
            }
            return findings;
    }

}

没有复杂的代码并不意味着 class 的作者没有设计决定,应该通过单元测试来验证和保护。 IE。您为集合中的项目选择值类型的事实使得某些行为变得不可能并且有些微不足道 - 测试在那里澄清 class 正确实施 设计决策 并保护 class 的行为 以防将来修改。

集合类型属性的 setter 单元测试(与值类型 int 不同)实际上 非常重要 因为必须验证契约class 的定义和正确支持 - setter 是否复制一个集合或引用现有集合,它是深拷贝还是浅拷贝?正确地测试每个案例绝对不是一件容易的事。 (同样在较小程度上适用于所有引用类型属性,但在非集合情况下,对行为的期望通常与默认行为更一致)。

那么在编写测试之前你想做的是决定你的集合的行为属性 - 它是在设置时复制还是引用到原始的实时实例。如果集合是引用类型(不是问题中的情况),您还需要决定它是浅拷贝还是深拷贝(深拷贝不常见)。

做出决定后,编写测试进行验证就显得有些微不足道了。您添加以下测试:

  • 通过 getter 公开的集合是否具有与调用 setter 相同顺序的相同项目(适用于复制和引用方法)
  • 对集合使用 setter 并修改原始集合(以防数组更改集合中的项目)。验证 getter 公开的集合行为是否正确(匹配更新的一个以供实时参考或与复制的保持相同)
  • 如果使用非不可变引用类型的集合,请验证修改单个项目的行为是否符合预期(反映对非深层复制的修改或保持不变)
  • if collection just references original one tests may 缩短为仅检查原始值与 getter 返回值之间的引用相等性,但这样做会不是 document 预期的行为和限制将来修改的能力。

可能需要额外的测试来验证作为 getter 的结果返回的集合的行为是否符合 class 作者的设计 - 特别是如果对结果集合的修改反映在 class' 状态与否(getter 返回 shallow/deep 状态副本或直接公开内部状态,如问题所示)。

请注意,不鼓励使用 setter 集合属性 - 请参阅 CA2227: Collection properties should be read only。所以问题中的代码有点遵循建议,但更好的名称如 "AddKeywords"/"ReplaceKeywords" 会澄清行为而不是一般的 "set".

如何测试?

当您调用 SetKeywords 时,它应该会做一些事情。现在它设置了内部数组 keywords。所以你需要问自己的问题是你怎么能确定它做到了?好吧,你有一个 GetKeywords 方法,它 returns 内部数组,所以我们可以使用它来进行如下测试:

[TestClass]
public class ShopParserTests
{
    [TestMethod]
    public void SetKeyWords__WhenGivenAnArray__MustSetTheInternalArray()
    {
        // Arrange
        var k1 = new Keyword("One", KeywordTypes.String);
        var k2 = new Keyword("Two");
        var parser = new ShopParser();
        var keys = new Keyword[] { k1, k2 };

        // Act
        parser.SetKeywords(keys);

        // Assert
        Keyword[] keysReturned = parser.GetKeywords();
        Assert.AreEqual(keysReturned[0].GetString(), k1.GetString());
        Assert.AreEqual(keysReturned[0].WhichType(), k1.WhichType());
        Assert.AreEqual(keysReturned[1].GetString(), k2.GetString());
        Assert.AreEqual(keysReturned[1].WhichType(), k2.WhichType());

        // More tests
    }
}

一些建议

请记住,您可能需要根据您的要求编写更多测试。例如,如果用户这样做会怎样:

Keyword[] keysReturned = parser.GetKeywords();
keys[0] = new Keyword();

你想允许吗?

此外,在 C# 中,您的 类 可以简化并利用属性。所以你的 Keyword 和 ShopParser 类 应该这样写:

public struct Keyword
{
    public Keyword(string keyword, KeywordTypes type =
    KeywordTypes.String)
    {
        this.TheKeyword = keyword;
        this.KeyType= type;
    }

    public string TheKeyword { get; private set; }
    public KeywordTypes KeyType { get; private set; }
}

public class ShopParser
{
    public void SetKeywords(Keyword[] tags)
    {
        this.KeyWords = tags;
    }

    public Keyword[] KeyWords { get; private set; }
}