【问题标题】:How do I invoke a validation attribute for testing?如何调用验证属性进行测试?
【发布时间】:2011-03-18 16:21:49
【问题描述】:

我正在使用 DataAnnotations 中的 RegularExpressionAttribute 进行验证,并想测试我的正则表达式。有没有办法在单元测试中直接调用属性?

我希望能够做类似的事情:

public class Person
{
    [RegularExpression(@"^[0-9]{3}-[0-9]{3}-[0-9]{4}$")]
    public string PhoneNumber { get; set; }
}

然后在单元测试中:

[TestMethod]
public void PhoneNumberIsValid
{
    var dude = new Person();
    dude.PhoneNumber = "555-867-5309";

    Assert.IsTrue(dude.IsValid);
}

甚至

Assert.IsTrue(dude.PhoneNumber.IsValid);

【问题讨论】:

标签: c# validation unit-testing data-annotations dynamic-data


【解决方案1】:

我最终使用了来自 DataAnnotations 命名空间的静态 Validator 类。我的测试现在看起来像这样:

[TestMethod]
public void PhoneNumberIsValid()
{
    var dude = new Person();
    dude.PhoneNumber = "666-978-6410";

    var result = Validator.TryValidateObject(dude, new ValidationContext(dude, null, null), null, true);

    Assert.IsTrue(result);
}

【讨论】:

  • 这个解决方案的问题在于,它会针对您在该属性上拥有的所有验证器进行测试,而不仅仅是正则表达式,因此您不能很好地隔离您的测试用例。您是否考虑过将正则表达式存储在可全局访问的某个地方,无论是在资源文件中还是在 Web 配置中?由于此时您只是想验证您的正则表达式,因此您可以使用 Regex.Match() 单独测试模式。
  • 由于您只是尝试验证单个属性,您可能需要考虑使用Validator.TryValidateProperty 方法。这是假设您的 Person 课程实际上不仅仅是一个电话号码。
【解决方案2】:

刚刚新建了一个 RegularExpressionAttribute 对象。

var regularExpressionAttribute = new RegularExpressionAttribute("pattern");

Assert.IsTrue(regularExpressionAttribute.IsValid(objToTest));

【讨论】:

  • 这可能是我必须走的路,但我需要在测试中复制我的模式。我想避免这种情况。我将编辑帖子并举例说明我希望能够做什么。
  • @CobraGeek 我用静态常量文件解决了这个问题,见下文
【解决方案3】:

抱歉回复晚了。

我是新来的。如果您想在隔离中测试每个 ValidationAttribute,您可以继续进行下一种方式,例如:

    [Test]
    public void Test_the_State_value_IsRequired()
    {
        string value = "Finished";
        var propertyInfo = typeof(TimeoffTemporalIncapacityEntry).GetProperty("State");
        var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true).Cast<RequiredAttribute>().FirstOrDefault();
        Assert.IsTrue(attribute.IsValid(value));
    }

【讨论】:

    【解决方案4】:

    我使用了@Martin 的建议和一个静态常量文件,这使我可以避免在本地指定正则表达式字符串

    [TestMethod]
    public void Test_Regex_NationalinsuranceNumber()
    {
        var regularExpressionAttribute = new RegularExpressionAttribute(Constants.Regex_NationalInsuranceNumber_Validate);
    
        List<string> validNINumbers = new List<string>() { "TN311258F", "QQ123456A" };
        List<string> invalidNINumbers = new List<string>() { "cake", "1234", "TS184LZ" };
        validNINumbers.ForEach(p => Assert.IsTrue(regularExpressionAttribute.IsValid(p)));
        invalidNINumbers.ForEach(p => Assert.IsFalse(regularExpressionAttribute.IsValid(p)));
    }
    

    【讨论】:

      【解决方案5】:

      您可以使用此类来验证隔离中的任何 ValidationAttribute 类型: T = 包含属性的类类型, A = 类型验证属性

      例子:

      string stateValue = "Pendiente";
      ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute> validator =
          new ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute>();
      Assert.IsTrue(validator.ValidateValidationAttribute("State", stateValue));
      
      
      public class ValidationAttributeValidator<T,A>
      {
          public ValidationAttributeValidator() { }
      
          public bool ValidateValidationAttribute(string property, object value)
          {
              var propertyInfo = typeof(T).GetProperty(property);
              var validationAttributes = propertyInfo.GetCustomAttributes(true);
      
              if (validationAttributes == null)
              {
                  return false; 
              }
              List<ValidationAttribute> validationAttributeList = new List<ValidationAttribute>();
              foreach (object attribute in validationAttributes)
              {
                  if (attribute.GetType() == typeof(A))
                  {
                      validationAttributeList.Add((ValidationAttribute)attribute);
                  }
              }
              return(validationAttributeList.Exists(x => x.IsValid(value)));
          }
      }
      

      【讨论】:

        【解决方案6】:

        在@Evelio 的回答的基础上,我将提供一个关于如何对自定义验证器进行单元测试的答案,因为这似乎在互联网上的任何地方都没有阐明,这是搜索时出现的热门话题之一怎么做。

        @Evelio 的答案非常接近,但它可以做更多的解释。

        要测试您的验证,您需要有一个将验证属性附加到其成员数据的类。在这里,我使用了一个新的自定义验证器,它对我的​​名为 FeeTimeUnitValidator 的项目有意义。这个验证器接受一个范围和另一个属性作为输入。如果其他属性为零,则验证器附加到的属性无关紧要。但如果其他属性不为零,则该属性需要在范围内。 这是我用于测试的 MockClass:

            class MockClass
            {
                public decimal Fee { get; set; }
        
                [FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
                public int attributeUnderTest { get; set; }
        
                public int badOtherProperty { get; set; }
                [FeeTimeUnitValidator(otherPropertyName: "badOtherProperty", minValue: 1, maxValue: 12)]
                public int badAttributeUnderTest { get; set; }
        
                [FeeTimeUnitValidator(otherPropertyName: "NotFoundAttribute", minValue: 1, maxValue: 12)]
                public int nameNotFoundAttribute { get; set; }
            }
        

        注意属性验证:

        [FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
        

        这表示检查属性“费用”作为费用属性(即,它必须是非零),然后范围是 1 - 12。

        我在单元测试类中实例化类并使用 setup 方法设置它。由于该类上有三个属性具有验证器,因此我将属性名称传递给安装程序类。

            private MockClass classUnderTest;
            private ValidationContext context;
        
            FeeTimeUnitValidator setup(string attributeUnderTest)
            {
                classUnderTest = new MockClass();
                classUnderTest.Fee = 0;
                var propertyInfo = typeof(MockClass).GetProperty(attributeUnderTest);
                var validatorArray = propertyInfo.GetCustomAttributes(typeof(FeeTimeUnitValidator), true);
        
                Assert.AreEqual(1, validatorArray.Length);
                var validator = validatorArray[0];
                Assert.IsTrue(validator.GetType().Equals(typeof(FeeTimeUnitValidator)));
        
                context = new ValidationContext(classUnderTest, null, null);
        
                return (FeeTimeUnitValidator)validator;
            }
        

        有一些有趣的事情。我正在使用@Evelio 的方法从属性中提取验证器。这是在设置例程的第 3 行和第 4 行中完成的。然后,由于这是一个单元测试方法,我做了一些断言来确保我得到了我所期望的。当我将此模式转移到另一个验证器的另一个单元测试类时,这实际上遇到了一个问题。

        然后另一个关键是我创建了 ValidationContext(因为更复杂的验证器需要一个上下文来查找它们引用的其他属性——在我的例子中,我使用它来查找 Fee 属性)。当我研究如何对这些自定义验证器进行单元测试时,让我绊倒的是 ValidationContext。我找不到有关如何创建它们的任何信息。我相信属性验证的“上下文”是属性所在的类。这就是为什么我使用类实例作为第一个参数来创建验证上下文。然后,这为验证器提供了对类上其他属性的访问权限,因此您可以进行跨属性验证。

        现在我已经创建了上下文和指向验证器的指针,我可以跳入单元测试本身以确保验证器正常工作:

            [TestMethod]
            public void TestInRangeIsValidWhenFeeNonZero()
            {
                // Arrange
                var validator = setup("attributeUnderTest");
                classUnderTest.Fee = 10;
        
                // Act
                ValidationResult value12 = validator.GetValidationResult(12, context);
                ValidationResult value1 = validator.GetValidationResult(1, context);
                ValidationResult value5 = validator.GetValidationResult(5, context);
        
                // Assert
                Assert.AreEqual(ValidationResult.Success, value12);
                Assert.AreEqual(ValidationResult.Success, value1);
                Assert.AreEqual(ValidationResult.Success, value5);
            }
        

        如果我的验证器不需要上下文(即,它可以验证属性而不参考其他属性),那么我可以使用更简单的 IsValid() 接口,但如果验证器需要非空上下文,您必须像我在这里所做的那样使用 GetValidationResult() 方法。

        我希望这可以帮助其他可能正在编写验证器并且像我一样对单元测试充满虔诚的人。 :)

        Here is a good article on creating custom validators.

        【讨论】:

          【解决方案7】:

          扩展@CobraGeek 的回答和@Erik 的评论,您可以使用Validator.TryValidateProperty仅验证一个字段 而不是整个对象,如下所示:

          var results = new List<ValidationResult>();
          
          Person dude = new Person();
          
          System.ComponentModel.TypeDescriptor.AddProviderTransparent
          (new AssociatedMetadataTypeTypeDescriptionProvider(dude.GetType()), dude.GetType());
          
          dude.PhoneNumber = "555-867-5309";
          
          var vc = new ValidationContext(dude, null, null);
          
          vc.MemberName = "PhoneNumber";   
          
          bool result = Validator.TryValidateProperty(dude.PhoneNumber, vc, results);
          

          之后result 是表示验证成功的布尔值,如果为false,results 包含抛出错误的详细信息列表。

          【讨论】:

          • 谢谢,MemberName 位对我很重要。我收到一个异常说 Parameter 'propertyName' 不能为空,似乎 MemberName 是指的。
          【解决方案8】:
          // You can do something like this.
          [TestMethod]
          public void PhoneNumberIsValid
          {
              var propInfo = typeof(Person).GetProperty("PhoneNumber");
              var attr = propInfo.GetCustomAttributes(typeof(RegularExpressionAttribute), true);
          
              // Act Assert Positives
                  Assert.IsTrue(((RegularExpressionAttribute)attr [0]).IsValid("555-55-5555"));
          
                  // Act Assert Negative
                Assert.IsFalse(((RegularExpressionAttribute)attr[0]).IsValid("123654654654"));
          
                  }
          

          【讨论】:

            猜你喜欢
            • 2020-12-01
            • 1970-01-01
            • 1970-01-01
            • 2020-03-09
            • 1970-01-01
            • 1970-01-01
            • 2020-04-27
            • 2016-05-25
            • 2020-06-19
            相关资源
            最近更新 更多