测试删除方法而不调用Add方法

时间:2014-06-23 09:21:40

标签: c# unit-testing tdd

我正在编写一个管理Tag对象树的类的测试:

public class Tag
{
    public virtual int Id { get; set; }
    public virtual string Description{ get; set; }
    private IList<Tag> children = new List<Tag>();
    public virtual IEnumerable<Tag> Children
    {
        get {return children .ToArray();}
    }
    public void AddChildTag(Tag child)
    {
        children.Add(child);
    }
    public void RemoveChildTag(Tag child)
    {
        children.Remove(child);
    }
}

正如你所看到的,设置父属性的唯一模式是通过AddChildTag方法,这正是我想要的,我的问题是在单元测试中:因为每个测试都应该是原子的,我该如何测试RemoveChildTag方法?

我看到的唯一方法是调用add方法,然后调用remove,但是这样如果Add为某些错误,即使remove的测试也会失败,因此原子性会丢失。

怎么办呢?

修改
我从Tag对象中删除了父属性,因为我不再使用它 一些使用NUnit和FluentAssertion根据解决方案进行测试

    [Test]
    public void AddChildTagAddsChildren()
    {
        //Arrange
        Tag parent = new Tag();
        Tag child = new Tag();
        //Act
        parent.AddChildTag(child);
        //Assert
        parent.Children.Should().Contain(child);
    }
    [Test]
    public void RemoveChildTagRemovesAddedChildren()
    {
        //Arrange
        Tag parent = new Tag();
        Tag child = new Tag();
        parent.AddChildTag(child);
        //Act
        parent.RemoveChildTag(child);
        //Assert
        parent.Children.Should().NotContain(child);
    }
    [Test]
    public void RemoveChildTagThrowsNothingWhenNoChild()
    {
        //Arrange
        Tag parent= new Tag();
        Tag child= new Tag();
        //Act
        Action RemoveChild = () => parent.RemoveChildTag(child);
        //Assert
        RemoveChild.ShouldNotThrow();
    }

4 个答案:

答案 0 :(得分:21)

您的单元测试应该反映您班级的实际用例。您的消费者将如何使用RemoveChildTag方法?哪个更有意义?您使用对象集合的方式是什么?

var parent = new Tag();
// later
parent.RemoveChildTag(child);

......或

var parent = new Tag();
parent.AddChildTag(child);
// later
parent.RemoveChildTag(child);

您的消费者将删除之前添加的对象。这是您的用例,“删除删除以前添加的元素”(请注意,它还会生成出色的测试方法名称)。

AddRemove方法通常是互补的 - 你不能在没有其他方法的情况下测试。

答案 1 :(得分:14)

有一些方法可以测试Remove方法:

  1. 模拟 - 模拟您的数据结构,并调用remove,以便您可以测试调用正确的方法
  2. 继承 - 使children受到保护。您的测试类将继承自Tag类。现在您可以初始化children成员,以便我们测试删除
  3. 使用Add方法
  4. 我认为选项3是最好的,可以在单元测试中使用其他方法,如果Add有一些错误,超过1个测试将失败 - 您应该能够理解需要修复的内容。

    此外,每个单元测试都应测试一些基本行为,即使您之前需要做一些准备工作。 如果这些准备工作失败,则无法通过相关评论进行测试

    选项1 - 最适合您的代码到达第三方 - 其他服务器,文件系统&amp;更多。因为您不想在单元测试中进行这些调用 - 模拟响应。

    选项2 - 最好的我可以说,当你想要测试受保护/私有方法时,无需在代码所做的“途中”进行所有调用(公共方法使许多调用最终会调用您想要测试的方法),因为您只想测试特定的逻辑。当您的类具有您想要测试的状态时,也可以轻松使用此选项,而无需编写大量代码来使您的类处于此状态。

答案 2 :(得分:3)

您可以使用PrivateObject Class 安排您的测试对象

  

允许测试代码调用被测代码的方法和属性   因为它们不公开而无法访问。

修改

然后您可以通过PrivateObject.RealTypePrivateObject.Target属性

获得对包装对象的完全访问权限

修改

无论如何,每个破坏一个类的隔离和封装的系统都会使TDD中的单元测试的黑盒方法变得无用,应该避免作为毒药:)

答案 3 :(得分:2)

单元测试的常用方法是Arrange-Act-Assert范例。

我个人会对remove方法进行两次测试,一种是删除一个从未添加的子项,应该发生什么?是否应该抛出异常?

这看起来像是:

[Test]
public void RemoveChildTagThrowsExceptionWhenNoChildren()
{
    // Arrange
    var tag = new Tag();
    var tagToRemove = new Tag();

    // Act & Assert
    Expect(() => tag.RemoveChildTag(tagToRemove), Throws.ArgumentException);
}

然后,我会再次测试添加的孩子被移除时会发生什么:

[Test]
public void RemoveChildTagRemovesPreviouslyAddedChild()
{
    // Arrange
    var tag = new Tag();
    var childTag = new Tag();
    tag.AddChildTag(childTag);

    // Act
    tag.RemoveChildTag(childTag);

    // Assert
    Expect(tag.Children.Contains(childTag), Is.False);
}

值得注意的是,很多.NET Remove实现返回bo​​ol结果,表明是否实际执行了删除。请参阅here