新测试导致旧测试中断,我做错了吗?

时间:2011-10-12 20:37:36

标签: unit-testing c#-4.0 tdd mstest

我有一个单元测试,测试是否没有提供婴儿的名字,然后不保存婴儿,另一个如果提供了第一个名字,那么应该调用保存。我做了红色/绿色/重构,它通过了。我添加了新的测试,为姓做同样的事情。现在,名字的测试失败了,因为没有提供姓氏。我在下面提供了测试代码。我想知道我是否会以错误的方式解决这个问题,或者我只是希望纠正破损的测试?

此外,验证器接口是必需的,因为有效的更改取决于使用该软件的客户端,否则我会将这些检查编码到Baby类本身。

更新:根据我已经收到的一些回复,似乎我的方式错了。我应该做什么,以便不会出现这种情况?

    [TestMethod]
    public void baby_is_not_saved_if_validation_fails() {
        // arange
        var validator = new Mock<IValidator<Baby>>();
        var output = new ValidationCollection();
        validator.Setup(v => v.IsValid(It.IsAny<Baby>(), out output)).Returns(false);
        var unitOfWork = GetMock();
        // act
        var b = new Baby();
        var svc = new BabyService(validator.Object, unitOfWork.Object);
        svc.AddNewBaby(b);
        // assert
        unitOfWork.Verify(u => u.SaveChanges(), Times.Never());
    }

    [TestMethod]
    public void baby_is_saved_if_validation_passes() {
        // arange
        var validator = new Mock<IValidator<Baby>>();
        var output = new ValidationCollection();
        validator.Setup(v => v.IsValid(It.IsAny<Baby>(), out output)).Returns(true);
        var unitOfWork = GetMock();
        // act
        var b = new Baby();
        var svc = new BabyService(validator.Object, unitOfWork.Object);
        svc.AddNewBaby(b);
        // assert
        unitOfWork.Verify(u => u.SaveChanges(), Times.Once());
    }

    [TestMethod]
    public void if_first_name_is_not_supplied_baby_is_not_added() {
        // arrange
        var validator = new DefaultBabyValidator();
        var unitOfWork = GetMock();
        // act
        var b = new Baby();
        var svc = new BabyService(validator, unitOfWork.Object);
        svc.AddNewBaby(b);
        // assert
        unitOfWork.Verify(u => u.SaveChanges(), Times.Never());
    }

    Mock<IHealthUnitOfWork> GetMock() {
        var uow = new Mock<IHealthUnitOfWork>();
        var dbSet = new Mock<IDbSet<Baby>>();
        dbSet.Setup(db => db.Add(It.IsAny<Baby>())).Returns(new Baby());
        uow.Setup(u => u.SaveChanges()).Verifiable();
        uow.SetupGet(u => u.Babies).Returns(dbSet.Object);

        return uow;
    }

    [TestMethod]
    public void if_first_name_is_supplied_baby_is_added() {
        // arrange
        var validator = new DefaultBabyValidator();
        var unitOfWork = GetMock();
        // act
        var b = new Baby { FirstName = "Charles" };
        var svc = new BabyService(validator, unitOfWork.Object);
        svc.AddNewBaby(b);
        // assert
        unitOfWork.Verify(u => u.SaveChanges(), Times.Once());
    }

    [TestMethod]
    public void if_last_name_is_not_supplied_baby_is_not_added() {
        // arrange
        var validator = new DefaultBabyValidator();
        var unitOfWork = GetMock();
        // act
        var b = new Baby { FirstName = "Charles" };
        var svc = new BabyService(validator, unitOfWork.Object);
        svc.AddNewBaby(b);
        // assert
        unitOfWork.Verify(u => u.SaveChanges(), Times.Never());
    }
}

2 个答案:

答案 0 :(得分:2)

您的问题是您的测试代表了相互矛盾的要求。如果提供了第一个名字而且姓氏不是,那么if_first_name_is_supplied_baby_is_added()表示应该保存宝宝,但是if_last_name_is_not_supplied_baby_is_not_added()表示宝宝不应该保存。

答案 1 :(得分:0)

问题在于,名字的原始测试当时没有姓氏的规格。当您的规格发生变化时,您的测试可能/将会发生变化。我已经重构了我的测试,因此我不需要在Baby类中发现新字段时更改以前的测试。我现在有一种测试方法可以确定有效婴儿的真实状况。现在,必要的现场测试检查以下内容:

  • 如果必填字段无效
    • IsValid必须返回false
    • 预期的异常必须存在于返回的集合中(AggregateException.InnerExceptions)
  • 如果必填字段有效
    • 仅检查预期的异常不存在

现在我只需要在发现新字段时更新一个测试(所有字段都是有效的测试)。

[TestMethod] // only update this one as new fields are discovered
public void when_baby_is_valid_validation_returns_true() {
    var validator = new DefaultBabyValidator();

    // valid baby goes here
    var baby = new Baby();

    AggregateException fail;
    Assert.IsTrue(validator.IsValid(baby, out fail));
}

[TestMethod]
public void validation_returns_exception_if_first_name_is_null() {
    var validator = new DefaultBabyValidator();
    AggregateException results;
    var isValid = validator.IsValid(new Baby(), out results);
    var expected = results.InnerExceptions
                    .OfType<ArgumentException>()
                    .SingleOrDefault(ex => ex.ParamName == "FirstName");
    Assert.IsFalse(isValid); // should always return false if this condition is met
    Assert.IsNotNull(expected); // should also contain the expected exception
}

[TestMethod]
public void validation_does_not_return_exception_if_first_name_is_valid() {
    var validator = new DefaultBabyValidator();
    AggregateException results;
    validator.IsValid(new Baby { FirstName = "Charles" }, out results);
    var expected = results.InnerExceptions
        .OfType<ArgumentException>()
        .SingleOrDefault(ex => ex.ParamName == "FirstName");
    Assert.IsNull(expected); // exception should not exsist. 
                             // We don't care if it returns true.
                             // There is only one case where IsValid should
                             // return true.
}