有什么方法可以测试类似的属性吗?
本文关键字:属性 测试类 什么 方法 | 更新日期: 2023-09-27 17:53:22
让我们假设我有一个具有类似属性的类:
public string First { get; set; }
public string Second { get; set; }
public string Third { get; set; }
我想在我的测试中以同样的方式测试它们…所以我写:
[Test]
public void TestFirst()
{
// Asserting strings here
}
是否有办法避免创建三个测试(一个用于第一,一个用于第二,一个用于第三)?
我正在寻找类似[Values(First, Second, Third)]
的东西,所以我可以写一个测试,将迭代属性。
干杯,提前感谢:)
这个怎么样:
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
var obj = new MyClass();
obj.First = "some value";
obj.Second = "some value";
obj.Third = "some value";
AssertPropertyValues(obj, "some value", x => x.First, x => x.Second, x => x.Third);
}
private void AssertPropertyValues<T, TProp>(T obj, TProp expectedValue, params Func<T, TProp>[] properties)
{
foreach (var property in properties)
{
TProp actualValue = property(obj);
Assert.AreEqual(expectedValue, actualValue);
}
}
}
您应该能够为此目的使用表达式树。使用表达式的MSDN文档。我已经创建了以下辅助方法,用于从任意对象obj
获取名为propertyName
的类型T
属性:
public T InvokePropertyExpression<T>(object obj, string propertyName)
{
return Expression.Lambda<Func<T>>(Expression.Property(
Expression.Constant(obj), propertyName)).Compile()();
}
在我的单元测试中使用这个助手方法,我现在可以根据其名称访问相关属性,例如:
[Test, Sequential]
public void Tests([Values("First", "Second", "Third")] string propertyName,
[Values("hello", "again", "you")] string expected)
{
var obj = new SomeClass
{ First = "hello", Second = "again", Third = "you" };
var actual = InvokePropertyExpression<string>(obj, propertyName);
Assert.AreEqual(expected, actual);
}
使用NUnit.Framework.Constraints.Constraint
有很多表达这类断言的可能性。此外,您可以使用ValuesSoueceAttribute
或TestCaseSourceAttribute
来描述测试的更多输入,而不是使用ValuesAttribute
或TestCaseAttribute
描述测试输入
让我们使用TestCaseSourceAttribute
public IEnumerable TestCasesSourcesAllProperties
{
get
{
yield return new TestCaseData(
new Tuple<string, string>[] {
Tuple.Create("First", "foo"),
Tuple.Create("Second", "bar"),
Tuple.Create("Third", "boo") } as object)
.SetDescription("Test all properties using Constraint expression");
}
}
在单个测试中构建约束
现在我们可以在一个测试
中为所有三个属性构建约束// get test parameters from TestCasesSourcesAllProperties
[TestCaseSource("TestCasesSourcesAllProperties")]
public void ClassUnderTest_CheckAllProperty_ExpectValues(Tuple<string, string>[] propertiesNamesWithValues)
{
// Arrange
ClassUnderTest cut = null;
// Act: perform actual test, here is only assignment
cut = new ClassUnderTest { First = "foo", Second = "bar", Third = "boo" };
// Assert
// check that class-under-test is not null
NUnit.Framework.Constraints.Constraint expression = Is.Not.Null;
foreach(var property in propertiesNamesWithValues)
{
// add constraint for every property one by one
expression = expression.And.Property(property.Item1).EqualTo(property.Item2);
}
Assert.That(cut, expression);
}
下面是一个完整的例子
缺点配置逻辑,即foreach
,在测试逻辑
这很容易做到,但我怀疑这是否值得。
How To——上面的许多答案都有效,但这似乎是最简单的,假设您正在测试一个新创建的对象…
[TestCase("First", "foo"]
[TestCase("Second", 42]
[TestCase("Third", 3.14]
public void MyTest(string name, object expected)
{
Assert.That(new MyClass(), Has.Property(name).EqualTo(expected));
}
然而,在测试中放置三个独立的断言似乎更容易阅读…
[Test]
public void MyTest()
{
var testObject = new MyClass();
Assert.That(testObject, Has.Property("First").EqualTo("foo"));
Assert.That(testObject, Has.Property("Second").EqualTo(42));
Assert.That(testObject, Has.Property("Third").EqualTo(3.14));
}
当然,这假设三个断言都是测试一个东西的一部分,比如defaultconstructorinitializesmyclasscorrect。如果这不是您要测试的内容,那么三个测试更有意义,即使它需要更多的输入。一种确定的方法是看看您是否能够为测试想出一个合理的名称。
查理您可以在测试方法的参数上使用Values
属性:
[Test]
public void MyTest([Values("A","B")] string s)
{
...
}
但是,这只适用于字符串常量(即不是属性值)。
我猜你可以使用反射从给定值中获得属性的值,例如
[Test]
public void MyTest([Values("A","B")] string propName)
{
var myClass = new MyClass();
var value = myClass.GetType().GetProperty(propName).GetValue(myClass, null);
// test value
}
但这并不是最干净的解决方案。也许你可以写一个测试,调用一个方法来测试每个属性。
[Test]
public void MyTest()
{
var myClass = new MyClass();
MyPropertyTest(myClass.First);
MyPropertyTest(myClass.Second);
MyPropertyTest(myClass.Third);
}
public void MyPropertyTest(string value)
{
// Assert on string
}
然而,最好避免这种测试方式,因为单元测试应该做的就是测试代码的一个单元。如果每个测试都被正确地命名,它可以用来保存您所期望的记录,并且可以很容易地在将来添加。
您可以编写参数化测试并将属性访问器作为参数传递:
看到的例子:假设你的类有3个属性:
public class MyClass
{
public string First { get; set; }
public string Second { get; set; }
public string Third { get; set; }
}
那么测试可能如下:
[TestFixture]
public class MyTest
{
private TestCaseData[] propertyCases = new[]
{
new TestCaseData(
"First",
(Func<MyClass, string>) (obj => obj.First),
(Action<MyClass, string>) ((obj, newVal) => obj.First = newVal)),
new TestCaseData(
"Second",
(Func<MyClass, string>) (obj => obj.Second),
(Action<MyClass, string>) ((obj, newVal) => obj.Second = newVal)),
new TestCaseData(
"Third",
(Func<MyClass, string>) (obj => obj.Third),
(Action<MyClass, string>) ((obj, newVal) => obj.Third = newVal))
};
[Test]
[TestCaseSource("propertyCases")]
public void Test(string description, Func<MyClass, string> getter, Action<MyClass, string> setter)
{
var obj = new MyClass();
setter(obj, "42");
var actual = getter(obj);
Assert.That(actual, Is.EqualTo("42"));
}
}
一些笔记:
1. 未使用的字符串描述作为1-st参数传递,以区分通过NUnit测试运行器UI或通过Resharper运行的测试用例。
2. 这两个案例是独立的,即使First
属性的测试失败,其他两个测试也将运行。
3.可以通过NUnit测试运行器UI或Resharper单独运行一个测试用例。
所以,你的测试是干净和干燥的:)
感谢大家的回答和帮助。我学到了很多东西。
这就是我最后所做的。我已经使用反射来获得所有的字符串属性,然后设置为一个值,检查值是否设置,设置为null,检查是否返回一个空字符串(逻辑在属性的getter)。
[Test]
public void Test_AllStringProperties()
{
// Linq query to get a list containing all string properties
var string_props= (from prop in bkvm.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
where
prop.PropertyType == typeof(string) &&
prop.CanWrite && prop.CanRead
select prop).ToList();
string_props.ForEach(p =>{
// Set value of property to a different string
string set_val = string.Format("Setting [{0}] to: '"Testing string'".", p.Name);
p.SetValue(bkvm, "Testing string", null);
Debug.WriteLine(set_val);
// Assert it was set correctly
Assert.AreEqual("Testing string", p.GetValue(bkvm, null));
// Set property to null
p.SetValue(bkvm,null,null);
set_val = string.Format("Setting [{0}] to null. Should yield an empty string.", p.Name);
Debug.WriteLine(set_val);
// Assert it returns an empty string.
Assert.AreEqual(string.Empty,p.GetValue(bkvm, null));
}
);
}
这样我就不需要担心是否有人添加了一个属性,因为它会被自动检查,而不需要我更新测试代码(正如你可能猜到的,不是每个人都更新或编写测试:)