C# 如何对自定义数据注释执行单元测试
我有以下简单的类数据注释来控制电话号码区域:C# 如何对自定义数据注释执行单元测试,c#,unit-testing,data-annotations,C#,Unit Testing,Data Annotations,我有以下简单的类数据注释来控制电话号码区域: public class PhoneAreaAttribute : ValidationAttribute, IClientValidatable { public const string ValidInitNumber = "0"; public const int MinLen = 2; public const int MaxLen = 4; public override bool IsValid(obje
public class PhoneAreaAttribute : ValidationAttribute, IClientValidatable
{
public const string ValidInitNumber = "0";
public const int MinLen = 2;
public const int MaxLen = 4;
public override bool IsValid(object value)
{
var area = (string)value;
if (string.IsNullOrWhiteSpace(area))
{
return true;
}
if (area.StartsWith(PhoneAreaAttribute.ValidInitNumber))
{
return false;
}
if (!Regex.IsMatch(area, @"^[\d]+$"))
{
return false;
}
if (!area.LengthBetween(PhoneAreaAttribute.MinLen, PhoneAreaAttribute.MaxLen))
{
return false;
}
return true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "phoneArea",
};
yield return rule;
}
}
公共类PhoneAreaAttribute:ValidationAttribute,IClientValidable
{
公用常量字符串ValidInitNumber=“0”;
公共常数int MinLen=2;
公共常数int MaxLen=4;
公共覆盖布尔值有效(对象值)
{
变量区域=(字符串)值;
if(string.IsNullOrWhiteSpace(区域))
{
返回true;
}
if(area.StartWith(PhoneAreaAttribute.ValidInitNumber))
{
返回false;
}
如果(!Regex.IsMatch(区域,@“^[\d]+$”)
{
返回false;
}
如果(!area.LengthBetween(PhoneAreaAttribute.MinLen,PhoneAreaAttribute.MaxLen))
{
返回false;
}
返回true;
}
公共IEnumerable GetClientValidationRules(ModelMetadata元数据、ControllerContext上下文)
{
var规则=新ModelClientValidationRule
{
ErrorMessage=FormatErrorMessage(metadata.GetDisplayName()),
ValidationType=“phoneArea”,
};
收益率-收益率规则;
}
}
我不知道这将是一个正确的单元测试这个类
谢谢。好的,基本上测试属性与测试任何常规类是一样的。我接受了您的类,并将其缩小了一点,以便可以运行它(您创建了一些扩展方法,我不想重新创建这些方法)。下面是这个类的定义
public class PhoneAreaAttribute : ValidationAttribute
{
public const string ValidInitNumber = "0";
public override bool IsValid(object value)
{
var area = (string)value;
if (string.IsNullOrEmpty(area))
{
return true;
}
if (area.StartsWith(PhoneAreaAttribute.ValidInitNumber))
{
return false;
}
return true;
}
}
事先注意:我的一些单元测试命名约定可能与您使用的不同(有一些)
现在我们将创建一个单元测试。我知道你已经有了一个测试项目,如果你没有,就创建一个。在这个测试项目中,您将创建一个新的单元测试(基本单元测试),我们将其命名为PhoneAreaAttributeTest
作为良好的实践,我创建了一个测试初始化器来创建所有共享的“资源”,在本例中是PhoneAreaAttribute
类的一个新实例。是的,您可以创建一个实例,就像您习惯于使用“常规”类一样(事实上,“常规”类和您的属性类之间没有太大区别)
现在我们已经准备好开始为这些方法编写测试了。基本上,您需要处理所有可能的场景。我将在这里向您展示我的(简化的)IsValid方法中可能出现的两种场景。首先,我将查看给定的对象参数是否可以大小写为字符串(这是第一个场景/TestMethod)。其次,我将查看是否正确处理了“IsNullOrEmpty”的路径(这是第二个场景/测试方法)
正如您所看到的,这只是一个常规的单元测试。这些只是最基本的。如果您还有问题,我还建议您阅读一些教程
以下是PhoneAreaAttributeTest
测试类:
[TestClass]
public class PhoneAreaAttributeTest
{
public PhoneAreaAttribute PhoneAreaAttribute { get; set; }
[TestInitialize]
public void PhoneAreaAttributeTest_TestInitialise()
{
PhoneAreaAttribute = new PhoneAreaAttribute();
}
[TestMethod]
[ExpectedException(typeof(InvalidCastException))]
public void PhoneAreaAttributeTest_IsValid_ThrowsInvalidCastException()
{
object objectToTest = new object();
PhoneAreaAttribute.IsValid(objectToTest);
}
[TestMethod]
public void PhoneAreaAttributeTest_IsValid_NullOrEmpty_True()
{
string nullToTest = null;
string emptoToTest = string.Empty;
var nullTestResult = PhoneAreaAttribute.IsValid(nullToTest);
var emptyTestResult = PhoneAreaAttribute.IsValid(emptoToTest);
Assert.IsTrue(nullTestResult, "Null Test should return true.");
Assert.IsTrue(emptyTestResult, "Empty Test should return true.");
}
}
当考虑如何“正确”测试这个类时,考虑以下内容:
是5李>的(CC)是有效的
- 该方法依赖于另外两种方法
和IsNullOrWhiteSpace
。我相信这两个都有一个额外的CC 2李>LengthBetween
- 有可能抛出一个
。这代表了另一个潜在的测试用例李>InvalidCastException
public class PhoneAreaAttributeTests
{
[Theory]
[InlineData("123", true)]
[InlineData(" ", true)]
[InlineData(null, true)]
public void IsValid_WithCorrectInput_ReturnsTrue(
object value, bool expected)
{
// Setup
var phoneAreaAttribute = CreatePhoneAreaAttribute();
// Exercise
var actual = phoneAreaAttribute.IsValid(value);
// Verify
actual.Should().Be(expected, "{0} should be valid input", value);
// Teardown
}
[Theory]
[InlineData("012", false)]
[InlineData("A12", false)]
[InlineData("1", false)]
[InlineData("12345", false)]
public void IsValid_WithIncorrectInput_ReturnsFalse(
object value, bool expected)
{
// Setup
var phoneAreaAttribute = CreatePhoneAreaAttribute();
// Exercise
var actual = phoneAreaAttribute.IsValid(value);
// Verify
actual.Should().Be(expected, "{0} should be invalid input", value);
// Teardown
}
[Fact]
public void IsValid_UsingNonStringInput_ThrowsExcpetion()
{
// Setup
var phoneAreaAttribute = CreatePhoneAreaAttribute();
const int input = 123;
// Exercise
// Verify
Assert.Throws<InvalidCastException>(
() => phoneAreaAttribute.IsValid(input));
// Teardown
}
// Simple factory method so that if we change the
// constructor, we don't have to change all our
// tests reliant on this object.
public PhoneAreaAttribute CreatePhoneAreaAttribute()
{
return new PhoneAreaAttribute();
}
}
公共类PhoneAreaAttributeTests
{
[理论]
[在线数据(“123”,真实)]
[InlineData(“,true)]
[InlineData(null,true)]
public void有效,输入正确返回(
对象值(预期为布尔值)
{
//设置
var phoneAreaAttribute=CreatePhoneAreaAttribute();
//练习
var-actual=phoneAreaAttribute.IsValid(值);
//核实
actual.Should().Be(应为“{0}应为有效输入”,值);
//拆卸
}
[理论]
[InlineData(“012”,假)]
[在线数据(“A12”,假)]
[在线数据(“1”,假)]
[在线数据(“12345”,假)]
public void有效,输入不正确,返回值无效(
对象值(预期为布尔值)
{
//设置
var phoneAreaAttribute=CreatePhoneAreaAttribute();
//练习
var-actual=phoneAreaAttribute.IsValid(值);
//核实
actual.Should().Be(应为“{0}应为无效输入”值);
//拆卸
}
[事实]
public void有效\u使用非字符串输入\u throwException()
{
//设置
var phoneAreaAttribute=CreatePhoneAreaAttribute();
常量int输入=123;
//练习
//核实
断言。抛出(
()=>phoneAreaAttribute.IsValid(输入));
//拆卸
}
//简单的工厂方法,如果我们改变
//构造函数,我们不必改变所有的
//依赖于此对象的测试。
公共PhoneAreaAttribute CreatePhoneAreaAttribute()
{
返回新的PhoneAreaAttribute();
}
}
*我喜欢使用流畅的断言,在这种情况下它会有所帮助,因为我们可以指定一条消息,让我们知道断言失败时,哪个是失败的断言。这些数据驱动的测试很好,因为它们可以通过将各种排列组合在一起,减少我们需要编写的类似测试方法的数量。当我们这样做时,最好避免使用前面解释的自定义消息。顺便说一下,流畅的断言可以与许多测试框架一起使用。像对其他类进行单元测试一样对其进行单元测试?或者你还有其他问题吗?@Styxxy我只需要为这个类创建一个“单元测试”。我个人总是为每个类创建一个单独的单元测试。如果没有单元测试