fluentassertions中文文档

 

按示例编码

您可能已经注意到,这个开源项目的目的不仅是.NET领域中最好的断言框架,还要展示高质量的代码。我们大量实践测试驱动开发,TDD做出的承诺之一就是可以将单元测试视为API的文档。因此,尽管您可以自由地浏览这里的许多示例,但请考虑分析许多 单元测试

支持的测试框架

Fluent Assertions支持许多不同的单元测试框架。只需将对相应测试框架程序集的引用添加到单元测试项目中。Fluent Assertions将自动查找相应的程序集,并将其用于抛出特定于框架的异常。
如果由于某些未知原因,Fluent Assertions无法找到程序集,并且您在.NET 4.5或.NET Standard 2.0项目下运行,请尝试使用项目的app.config中的配置设置显式指定框架。如果找不到任何受支持的框架,它将回退到使用自定义 AssertFailedException  异常类。
<configuration>
<appSettings>
<!-- Supported values: nunit, xunit, mstest, mspec, mbunit and gallio -->
<add key="FluentAssertions.TestFramework" value="nunit"/>
</appSettings>
</configuration>
只需将NuGet包“FluentAssertions”添加到您的测试项目中即可。

主题识别

Fluent Assertions可以使用单元测试的C#代码来提取主题的名称,并在断言失败时使用它。例如考虑这个陈述:
string username = "dennis";
username.Should().Be("jonas");
这将抛出一个测试框架特定的异常,并带有以下消息:
 Expected username to be "jonas", but "dennis" differs near 'd' (index 0)  .`
这种方式的工作方式是Fluent Assertions将尝试遍历当前堆栈跟踪以查找行号和列号以及源文件的完整路径。由于它需要调试符号,因此需要您在调试模式下编译单元测试,即使在构建服务器上也是如此。此外,由于只有.NET Standard 2.0和完整的.NET Framework支持直接访问当前堆栈跟踪,因此主题标识仅适用于针对这些框架的平台。
 
现在,如果您构建了自己的直接使用Fluent Assertions的扩展,则可以告诉它在遍历堆栈跟踪时跳过该扩展代码。考虑例如客户断言:
public class CustomerAssertions
{
private readonly Customer customer;
public CustomerAssertions(Customer customer)
{
this.customer = customer;
}
[CustomAssertion]
public void BeActive(string because = "", params object[] becauseArgs)
{
customer.Active.Should().BeTrue(because, becauseArgs);
}
}
它的用法是:
myClient.Should().BeActive("because we don't work with old clients");
如果没有该 [CustomAssertion]  属性,Fluent Assertions将找到调用 Should().BeTrue()  该 customer  变量并将其视为被测试对象(SUT)的行。但是通过应用此属性,它将忽略此调用,而是通过查找调用 Should().BeActive()  并使用该 myClient  变量来查找SUT 。

基本断言

以下断言可用于所有类型的对象。
object theObject = null;
theObject.Should().BeNull("because the value is null");
theObject.Should().NotBeNull();
theObject = "whatever";
theObject.Should().BeOfType<string>("because a {0} is set", typeof(string));
theObject.Should().BeOfType(typeof(string), "because a {0} is set", typeof(string));
有时您可能希望首先断言某个对象属于某种类型 BeOfType  ,然后继续对将该对象强制转换为指定类型的结果进行其他断言。你可以通过将这些断言链接到 Which  属性上来做到这一点。
someObject.Should().BeOfType<Exception>()
.Which.Message.Should().Be("Other Message");
要声明两个对象是相等的(通过它们的实现 Object.Equals  ),请使用
string otherObject = "whatever";
theObject.Should().Be(otherObject, "because they have the same values");
theObject.Should().NotBe(otherObject);
如果要确保两个对象不仅在功能上相等,而是在内存中引用完全相同的对象,请使用以下两种方法。
theObject = otherObject;
theObject.Should().BeSameAs(otherObject);
theObject.Should().NotBeSameAs(otherObject);
一些通用断言的其他示例包括
var ex = new ArgumentException();
ex.Should().BeAssignableTo<Exception>("because it is an exception");
ex.Should().NotBeAssignableTo<DateTime>("because it is an exception");
var dummy = new Object();
dummy.Should().Match(d => (d.ToString() == "System.Object"));
dummy.Should().Match<string>(d => (d == "System.Object"));
dummy.Should().Match((string d) => (d == "System.Object"));
一些用户要求能够以流畅的方式轻松地将对象转发到其派生类之一。
customer.Animals.First().As<Human>().Height.Should().Be(178);
我们还增加了断言可以使用XML,二进制或数据协定格式化程序对对象进行序列化和反序列化的可能性。
theObject.Should().BeXmlSerializable();
theObject.Should().BeBinarySerializable();
theObject.Should().BeDataContractSerializable();
在内部, BeBinarySerializable  使用 Object图形比较 API,因此如果您需要从比较中排除某些属性(例如,因为它的后备字段是 [NonSerializable]  ,您可以这样做:
theObject.Should().BeBinarySerializable<MyClass>(
options => options.Excluding(s => s.SomeNonSerializableProperty));
Fluent Assertions对 [Flags]  基于枚举的特殊支持,允许您执行以下操作:
regexOptions.Should().HaveFlag(RegexOptions.Global);
regexOptions.Should().NotHaveFlag(RegexOptions.CaseInsensitive);

可空类型

short? theShort = null;
theShort.Should().NotHaveValue();
theShort.Should().BeNull();
int? theInt = 3;
theInt.Should().HaveValue();
theInt.Should().NotBeNull();
DateTime? theDate = null;
theDate.Should().NotHaveValue();
theDate.Should().BeNull();

布尔

bool theBoolean = false;
theBoolean.Should().BeFalse("it's set to false");
theBoolean = true;
theBoolean.Should().BeTrue();
theBoolean.Should().Be(otherBoolean);
显然,上述说法也行可能为空的布尔值,但如果你真的想确保一个布尔值,或者是 true  或 false  不 null  ,您可以使用这些方法。
theBoolean.Should().NotBeFalse();
theBoolean.Should().NotBeTrue();

字符串

要断言字符串是否为空,空或仅包含空格,您可以使用各种方法。
string theString = "";
theString.Should().NotBeNull();
theString.Should().BeNull();
theString.Should().BeEmpty();
theString.Should().NotBeEmpty("because the string is not empty");
theString.Should().HaveLength(0);
theString.Should().BeNullOrWhiteSpace(); // either null, empty or whitespace only
theString.Should().NotBeNullOrWhiteSpace();
显然,你会找到你期望的字符串断言的所有方法。
theString = "This is a String";
theString.Should().Be("This is a String");
theString.Should().NotBe("This is another String");
theString.Should().BeEquivalentTo("THIS IS A STRING");
theString.Should().BeOneOf(
"That is a String",
"This is a String",
);
theString.Should().Contain("is a");
theString.Should().ContainAll("should", "contain", "all", "of", "these");
theString.Should().ContainAny("any", "of", "these", "will", "do");
theString.Should().NotContain("is a");
theString.Should().NotContainAll("can", "contain", "some", "but", "not", "all");
theString.Should().NotContainAny("can't", "contain", "any", "of", "these");
theString.Should().ContainEquivalentOf("WE DONT CARE ABOUT THE CASING");
theString.Should().NotContainEquivalentOf("HeRe ThE CaSiNg Is IgNoReD As WeLl");
theString.Should().StartWith("This");
theString.Should().NotStartWith("This");
theString.Should().StartWithEquivalent("this");
theString.Should().NotStartWithEquivalentOf("this");
theString.Should().EndWith("a String");
theString.Should().NotEndWith("a String");
theString.Should().EndWithEquivalent("a string");
theString.Should().NotEndWithEquivalentOf("a string");
我们甚至支持通配符。例如,如果您想断言某些电子邮件地址是正确的,请使用以下命令:
emailAddress.Should().Match("*@*.com");
homeAddress.Should().NotMatch("*@*.com");
如果输入字符串的大小写无关紧要,请使用:
emailAddress.Should().MatchEquivalentOf("*@*.COM");
emailAddress.Should().NotMatchEquivalentOf("*@*.COM");
如果通配符对你不够,你总是可以使用一些正则表达式魔法:
someString.Should().MatchRegex("h.*\\sworld.$");
subject.Should().NotMatchRegex(".*earth.*");

数字类型以及实现IComparable <T>的所有其他内容

int theInt = 5;
theInt.Should().BeGreaterOrEqualTo(5);
theInt.Should().BeGreaterOrEqualTo(3);
theInt.Should().BeGreaterThan(4);
theInt.Should().BeLessOrEqualTo(5);
theInt.Should().BeLessThan(6);
theInt.Should().BePositive();
theInt.Should().Be(5);
theInt.Should().NotBe(10);
theInt.Should().BeInRange(1, 10);
theInt.Should().NotBeInRange(6, 10);
theInt = 0;
//theInt.Should().BePositive(); => Expected positive value, but found 0
//theInt.Should().BeNegative(); => Expected negative value, but found 0
theInt = -8;
theInt.Should().BeNegative();
int? nullableInt = 3;
nullableInt.Should().Be(3);
double theDouble = 5.1;
theDouble.Should().BeGreaterThan(5);
byte theByte = 2;
theByte.Should().Be(2);
请注意, Should().Be()  并且 Should().NotBe()  不适用于浮动和双打。浮点变量可以继承不准确,永远不应该进行相等性比较。相反,要么使用 Should().BeInRange()  专门为浮点或 decimal  变量设计的方法或以下方法。
float value = 3.1415927F;
value.Should().BeApproximately(3.14F, 0.01F);
这将验证浮点值是否在3.139和3.141之间。
相反,要断言值与金额不同,您可以这样做。
float value = 3.5F;
value.Should().NotBeApproximately(2.5F, 0.5F);
这将验证float的值不在2.0和3.0之间。
要声明值与提供的值之一匹配,您可以执行此操作。
value.Should().BeOneOf(new[] { 3, 6});

日期和时间

为了断言 DateTime  或 DateTimeOffset  反对各种约束,FA提供了一系列方法,只要您使用扩展方法来表示日期和时间,就可以真正帮助保持断言的可读性。
var theDatetime = 1.March(2010).At(22, 15).AsLocal();
theDatetime.Should().Be(1.March(2010).At(22, 15));
theDatetime.Should().BeAfter(1.February(2010));
theDatetime.Should().BeBefore(2.March(2010));
theDatetime.Should().BeOnOrAfter(1.March(2010));
theDatetime.Should().BeOnOrBefore(1.March(2010));
theDatetime.Should().BeSameDateAs(1.March(2010).At(22, 16));
theDatetime.Should().BeIn(DateTimeKind.Local);
theDatetime.Should().NotBe(1.March(2010).At(22, 16));
theDatetime.Should().NotBeAfter(2.March(2010));
theDatetime.Should().NotBeBefore(1.February(2010));
theDatetime.Should().NotBeOnOrAfter(2.March(2010));
theDatetime.Should().NotBeOnOrBefore(1.February(2010));
theDatetime.Should().NotBeSameDateAs(2.March(2010));
theDatetime.Should().BeOneOf(
1.March(2010).At(21, 15),
1.March(2010).At(22, 15),
1.March(2010).At(23, 15)
);
请注意我们如何使用扩展方法 March  , At  以更人性化的形式表示日期。还有更多的像不像这些,包括 2000.Microseconds()  , 3.Nanoseconds  以及类似的方法 AsLocal  ,并 AsUtc  以之间转换。你甚至可以做相对计算 2.Hours().Before(DateTime.Now)  。
如果您只关心日期或时间的特定部分,请使用以下断言方法。
theDatetime.Should().HaveDay(1);
theDatetime.Should().HaveMonth(3);
theDatetime.Should().HaveYear(2010);
theDatetime.Should().HaveHour(22);
theDatetime.Should().HaveMinute(15);
theDatetime.Should().HaveSecond(0);
theDatetime.Should().NotHaveDay(2);
theDatetime.Should().NotHaveMonth(4);
theDatetime.Should().NotHaveYear(2011);
theDatetime.Should().NotHaveHour(23);
theDatetime.Should().NotHaveMinute(16);
theDatetime.Should().NotHaveSecond(1);
var theDatetimeOffset = 1.March(2010).AsUtc().ToDateTimeOffset(2.Hours());
theDatetimeOffset.Should().HaveOffset(2);
theDatetimeOffset.Should().NotHaveOffset(3);
我们添加了一整套方法来断言两个DateTime对象之间的差异与某个时间帧匹配。所有五种方法都支持Before和After扩展方法。
theDatetime.Should().BeLessThan(10.Minutes()).Before(otherDatetime); // Equivalent to <
theDatetime.Should().BeWithin(2.Hours()).After(otherDatetime); // Equivalent to <=
theDatetime.Should().BeMoreThan(1.Days()).Before(deadline); // Equivalent to >
theDatetime.Should().BeAtLeast(2.Days()).Before(deliveryDate); // Equivalent to >=
theDatetime.Should().BeExactly(24.Hours()).Before(appointment); // Equivalent to ==
要断言日期/时间是(非)在另一个日期/时间值的指定毫秒数内,您可以使用此方法。
theDatetime.Should().BeCloseTo(1.March(2010).At(22, 15), 2000); // 2000 milliseconds
theDatetime.Should().BeCloseTo(1.March(2010).At(22, 15)); // default is 20 milliseconds
theDatetime.Should().BeCloseTo(1.March(2010).At(22, 15), 2.Seconds());
theDatetime.Should().NotBeCloseTo(2.March(2010), 1.Hours());
如果数据库截断日期/时间值,这可能特别有用。

时间跨度

FA还支持一些 TimeSpan  直接应用于(可空)实例的专用方法:
var timeSpan = new TimeSpan(12, 59, 59);
timeSpan.Should().BePositive();
timeSpan.Should().BeNegative();
timeSpan.Should().Be(12.Hours());
timeSpan.Should().NotBe(1.Days());
timeSpan.Should().BeLessThan(someOtherTimeSpan);
timeSpan.Should().BeLessOrEqualTo(someOtherTimeSpan);
timeSpan.Should().BeGreaterThan(someOtherTimeSpan);
timeSpan.Should().BeGreaterOrEqualTo(someOtherTimeSpan);
同样的 日期和时间的断言 , BeCloseTo  并 NotBeCloseTo  也可用于时间跨度:
timeSpan.Should().BeCloseTo(new TimeSpan(13, 0, 0), 10.Ticks());
timeSpan.Should().NotBeCloseTo(new TimeSpan(14, 0, 0), 10.Ticks());

集合

.NET中的集合对象是如此通用,以至于它们上的断言数量需要相同级别的多功能性。大多数,如果不是全部,都是如此不言自明,我们只会在这里列出它们。
IEnumerable collection = new[] { 1, 2, 5, 8 };
collection.Should().NotBeEmpty()
.And.HaveCount(4)
.And.ContainInOrder(new[] { 2, 5 })
.And.ContainItemsAssignableTo<int>();
collection.Should().Equal(new List<int> { 1, 2, 5, 8 });
collection.Should().Equal(1, 2, 5, 8);
collection.Should().NotEqual(8, 2, 3, 5);
collection.Should().BeEquivalentTo(8, 2, 1, 5);
collection.Should().NotBeEquivalentTo(new[] {8, 2, 3, 5});
collection.Should().HaveCount(c => c > 3)
.And.OnlyHaveUniqueItems();
collection.Should().HaveCountGreaterThan(3);
collection.Should().HaveCountGreaterOrEqualTo(4);
collection.Should().HaveCountLessOrEqualTo(4);
collection.Should().HaveCountLessThan(5);
collection.Should().NotHaveCount(3);
collection.Should().HaveSameCount(new[] { 6, 2, 0, 5 });
collection.Should().NotHaveSameCount(new[] { 6, 2, 0 });
collection.Should().StartWith(1);
collection.Should().StartWith(new[] { 1, 2 });
collection.Should().EndWith(8);
collection.Should().EndWith(new[] { 5, 8 });
collection.Should().BeSubsetOf(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, });
collection.Should().ContainSingle();
collection.Should().ContainSingle(x => x > 3);
collection.Should().Contain(8)
.And.HaveElementAt(2, 5)
.And.NotBeSubsetOf(new[] {11, 56});
collection.Should().Contain(x => x > 3);
collection.Should().Contain(collection, "", 5, 6); // It should contain the original items, plus 5 and 6.
collection.Should().OnlyContain(x => x < 10);
collection.Should().ContainItemsAssignableTo<int>();
collection.Should().ContainInOrder(new[] { 1, 5, 8 });
collection.Should().NotContain(82);
collection.Should().NotContain(new[] { 82, 83 });
collection.Should().NotContainNulls();
collection.Should().NotContain(x => x > 10);
const int successor = 5;
const int predecessor = 5;
collection.Should().HaveElementPreceding(successor, element);
collection.Should().HaveElementSucceeding(predecessor, element);
collection.Should().BeEmpty();
collection.Should().BeNullOrEmpty();
collection.Should().NotBeNullOrEmpty();
IEnumerable otherCollection = new[] { 1, 2, 5, 8, 1 };
IEnumerable anotherCollection = new[] { 10, 20, 50, 80, 10 };
collection.Should().IntersectWith(otherCollection);
collection.Should().NotIntersectWith(anotherCollection);
collection.Should().BeInAscendingOrder();
collection.Should().BeInDescendingOrder();
collection.Should().NotBeAscendingInOrder();
collection.Should().NotBeDescendingInOrder();
最后两个方法可用于断言集合包含按升序或降序排列的项目。对于可能很好的简单类型,但对于更复杂的类型,它需要您实现 IComparable  ,这在所有情况下都没有多大意义。这就是为什么我们提供表达式的重载。
collection.Should().BeInAscendingOrder(x => x.SomeProperty);
collection.Should().BeInDescendingOrder(x => x.SomeProperty);
在对集合的投影进行断言时,失败消息将不那么具有描述性,因为它只知道投影值而不知道包含该属性的对象。
collection.Select(x => x.SomeProperty).Should().OnlyHaveUniqueItems();
因此,我们提供了两个重载,它使用表达式来选择属性。
collection.Should().OnlyHaveUniqueItems(x => x.SomeProperty);
collection.Should().NotContainNulls(x => x.SomeProperty);
特殊重载 Equal()  , StartWith  并 EndWith  使用lambda来检查两个集合,而不依赖于类型的Equals()方法。例如,考虑两个包含某种域实体的集合,这些域实体持久存储到数据库然后重新加载。由于实际的对象实例不同,如果要确保特定属性被正确保留,通常会执行以下操作:
persistedCustomers.Select(c => c.Name).Should().Equal(customers.Select(c => c.Name);
persistedCustomers.Select(c => c.Name).Should().StartWith(customers.Select(c => c.Name);
persistedCustomers.Select(c => c.Name).Should().EndWith(customers.Select(c => c.Name);
使用这些新的重载,您可以将它们重写为:
persistedCustomers.Should().Equal(customers, (c1, c2) => c1.Name == c2.Name);
persistedCustomers.Should().StartWith(customers, (c1, c2) => c1.Name == c2.Name);
persistedCustomers.Should().EndWith(customers, (c1, c2) => c1.Name == c2.Name);
您还可以对集合的所有元素执行断言:
IEnumerable<BaseType> collection = new BaseType[] { new DerivedType() };
collection.Should().AllBeAssignableTo<DerivedType>();
collection.Should().AllBeOfType<DerivedType>();
collection.Should().AllBeEquivalentTo(referenceObject);

字典

您也可以将Fluent Assertions应用于通用词典。当然,您可以断言任何字典为null或非null,并且为空或不为空。像这样:
Dictionary<int, string> dictionary;
dictionary.Should().BeNull();
dictionary = new Dictionary<int, string>();
dictionary.Should().NotBeNull();
dictionary.Should().BeEmpty();
dictionary.Add(1, "first element");
dictionary.Should().NotBeEmpty();
您还可以断言整个字典的相等性,其中将使用Equals实现验证密钥和值的相等性。像这样:
var dictionary1 = new Dictionary<int, string>
{
{ 1, "One" },
{ 2, "Two" }
};
var dictionary2 = new Dictionary<int, string>
{
{ 1, "One" },
{ 2, "Two" }
};
var dictionary3 = new Dictionary<int, string>
{
{ 3, "Three" },
};
dictionary1.Should().Equal(dictionary2);
dictionary1.Should().NotEqual(dictionary3);
或者您可以声明字典包含某个键或值:
dictionary.Should().ContainKey(1);
dictionary.Should().ContainKeys(1, 2);
dictionary.Should().NotContainKey(9);
dictionary.Should().NotContainKeys(9, 10);
dictionary.Should().ContainValue("One");
dictionary.Should().ContainValues("One", "Two");
dictionary.Should().NotContainValue("Nine");
dictionary.Should().NotContainValues("Nine", "Ten");
您还可以声明字典具有一定数量的项:
dictionary.Should().HaveCount(2);
dictionary.Should().NotHaveCount(3);
最后,您可以声明字典包含特定的键/值对:
KeyValuePair<int, string> item1 = new KeyValuePair<int, string>(1, "One");
KeyValuePair<int, string> item2 = new KeyValuePair<int, string>(2, "Two");
dictionary.Should().Contain(item1);
dictionary.Should().Contain(item1, item2);
dictionary.Should().Contain(2, "Two");
dictionary.Should().NotContain(item1);
dictionary.Should().NotContain(item1, item2);
dictionary.Should().NotContain(9, "Nine");
还支持链接附加断言。
dictionary.Should().ContainValue(myClass)
.Which.SomeProperty.Should().BeGreaterThan(0);

的GUID

你可以对Guids做的断言很简单。你可以断言他们与另一个Guid的平等,或者你可以断言Guid是空的。
Guid theGuid = Guid.NewGuid();
Guid sameGuid = theGuid;
Guid otherGuid = Guid.NewGuid();
theGuid.Should().Be(sameGuid);
theGuid.Should().NotBe(otherGuid);
theGuid.Should().NotBeEmpty();
Guid.Empty.Should().BeEmpty();

枚举

使用标准 Should().Be()  方法,使用.NET的 Enum.Equals()  实现来比较Enums 。这意味着Enums必须属于同一类型,并且具有相同的基础值。

例外

以下示例验证该 Foo()  方法是否抛出 InvalidOperationException  哪个 Message  属性具有特定值。
subject.Invoking(y => y.Foo("Hello"))
.Should().Throw<InvalidOperationException>()
.WithMessage("Hello is not allowed at this moment");
但是如果你像我一样喜欢arrange-act-assert语法,你也可以在你的行为部分使用一个动作。
Action act = () => subject.Foo2("Hello");
act.Should().Throw<InvalidOperationException>()
.WithInnerException<ArgumentException>()
.WithMessage("whatever");
请注意,该示例还验证该异常是否具有特定消息的特定内部异常。实际上,您甚至可以使用And属性检查异常实例的各个属性。
Action act = () => subject.Foo(null);
act.Should().Throw<ArgumentNullException>()
.And.ParamName.Should().Be("message");
执行相同操作的另一种方法是将一个或多个调用链接到该 Where()  方法:
Action act = () => subject.Foo(null);
act.Should().Throw<ArgumentNullException>().Where(e => e.Message.StartsWith("did"));
但是,我们发现测试子字符串的异常消息是如此常见,我们更改了默认行为 WithMessage  以支持通配符表达式并以不区分大小写的方式匹配。
Action act = () => subject.Foo(null);
act
.Should().Throw<ArgumentNullException>()
.WithMessage("?did*");
另一方面,您可能希望验证没有抛出异常。
Action act = () => subject.Foo("Hello");
act.Should().NotThrow();
我知道如果抛出异常,单元测试无论如何都会失败,但是这种语法会返回一个更清晰的异常描述,该异常被抛出并且更适合于AAA语法。
如果要验证是否未抛出特定异常,并且想要忽略其他异常,则可以使用重载来执行此操作:
Action act = () => subject.Foo("Hello");
act.Should().NotThrow<InvalidOperationException>();
如果您正在测试的方法返回一个 IEnumerable  或者 IEnumerable<T>  它使用 yield  关键字来构造该集合,那么只调用该方法将不会产生您期望的效果,因为在您实际迭代该集合之前不会进行实际工作。您可以使用 Enumerating()  扩展方法强制枚举这样的集合。
Func<IEnumerable<char>> func = () => obj.SomeMethodThatUsesYield("blah");
func.Enumerating().Should().Throw<ArgumentException>();
您必须使用该 Func<T>  类型而不是 Action<T>  那时。
该异常抛出API遵循相同的规则 try  ...  catch  ...建筑业开始。换句话说,如果您期望抛出(非)抛出某个异常,并且抛出更具体的异常,它仍然会满足该断言。所以抛出 ApplicationException  时的 Exception  预期不会失败的断言。但是,如果您真的想明确确切的异常类型,可以使用 ThrowExactly  和 WithInnerExceptionExactly  。
 
.NET 4.0及更高版本包括 AggregateException  通常由并行任务库或使用new  async  关键字运行的代码抛出的内容。以上所有内容也适用于聚合的异常,无论您是否在实际 AggregateException  或任何(嵌套)聚合异常上进行断言。
在讨论 async  关键字时,您还可以验证异步执行的方法是抛出还是不抛出异常:
Func<Task> act = async () => { await asyncObject.ThrowAsync<ArgumentException>(); };
await act.Should().ThrowAsync<InvalidOperationException>();
await act.Should().NotThrowAsync();
act.Should().Throw<InvalidOperationException>();
act.Should().NotThrow();
或者,您可以使用以下 Awaiting  方法:
Func<Task> act = () => asyncObject.Awaiting(async x => await x.ThrowAsync<ArgumentException>());
act.Should().Throw<ArgumentException>();
两者都给你相同的结果,所以这只是个人喜好的问题。

对象图比较

考虑该类 Order  及其电汇等效 OrderDto  (所谓的 DTO )。还假设订单具有一个或多个 Product  s和相关联的 Customer  。巧合的是, OrderDto  将有一个或多个 ProductDto  s和一个对应的 CustomerDto  。您可能希望确保 OrderDto  对象图中所有对象的所有公开成员都与对象图的同名成员匹配 Order  。
您可以使用以下命令断言两个对象图的结构相等 Should().BeEquivalentTo()  :
orderDto.Should().BeEquivalentTo(order);

递归

默认情况下,比较是递归的。为了避免无限递归,默认情况下,Fluent Assertions最多会递归10级,但如果要强制它尽可能深,请使用该 AllowingInfiniteRecursion  选项。另一方面,如果要禁用递归,只需使用此选项:
orderDto.Should().BeEquivalentTo(order, options =>
options.ExcludingNestedObjects());

价值类型

要确定Fluent Assertions是否应该重新出现在对象的属性或字段中,它需要了解哪些类型具有值语义以及哪些类型应被视为引用类型。默认行为是将所有覆盖的类型 Object.Equals  视为设计为具有值语义的对象。遗憾的是,匿名类型和元组也会覆盖此方法,但由于我们倾向于在等效性比较中经常使用它们,因此我们始终通过它们的属性对它们进行比较。
您可以通过使用单个断言的 ComparingByValue<T>  或 ComparingByMembers<T>  选项轻松覆盖它:
subject.Should().BeEquivalentTo(expected,
options => options.ComparingByValue<IPAddress>());
或者使用全局选项执行相同操作:
AssertionOptions.AssertEquivalencyUsing(options => options
.ComparingByValue<DirectoryInfo`());

自动转换

过去,Fluent Assertions会尝试将被测试者的财产价值转换为期望的相应财产的类型。但是很多人抱怨这种行为,其中表示日期和时间的字符串属性会与 DateTime  属性神奇地匹配。从5.0开始,此转换将不再发生。但是,您仍然可以使用 WithAutoConversion  或 WithAutoConversionFor  选项调整断言:
subject.Should().BeEquivalentTo(expectation, options => options
.WithAutoConversionFor(x => x.SelectedMemberPath.Contains("Birthdate")));

编译时类型与运行时类型

默认情况下,在递归比较期间选择要处理的成员时,Fluent Assertions会尊重对象或成员的声明(编译时)类型。也就是说,如果主题是a  OrderDto  但是它所分配的变量具有类型,则 Dto  只有在将对象与 order  变量进行比较时才会考虑后一类定义的成员。可以配置此行为,如果您愿意,可以选择使用运行时类型:
Dto orderDto = new OrderDto();
// Use runtime type information of orderDto
orderDto.Should().BeEquivalentTo(order, options =>
options.RespectingRuntimeTypes());
// Use declared type information of orderDto
orderDto.Should().BeEquivalentTo(order, options =>
options.RespectingDeclaredTypes());
此规则的一个例外是声明的类型 object  。由于 object  不公开任何属性,因此尊重声明的类型是没有意义的。因此,如果主题或成员的类型是 object  ,它将使用图中该节点的运行时类型。对于(多维)数组,这也可以更好地工作。

匹配成员

该 Order  对象的所有公共成员必须 OrderDto  具有相同的名称。如果缺少任何成员,则会抛出异常。但是,您可以自定义此行为。例如,如果您只想包含成员,则两个对象图都具有:
orderDto.Should().BeEquivalentTo(order, options =>
options.ExcludingMissingMembers());

选择会员

如果要使用以下 Excluding()  方法排除某些(可能深度嵌套的)个别成员:
orderDto.Should().BeEquivalentTo(order, options =>
options.Excluding(o => o.Customer.Name));
该 Excluding()  上将目标还需要一个lambda表达式,提供了更多的灵活性,决定排除哪些成员的选择方法:
orderDto.Should().BeEquivalentTo(order, options => options
.Excluding(ctx => ctx.SelectedMemberPath == "Level.Level.Text"));
也许是牵强附会,但您甚至可能决定通过索引排除特定嵌套对象上的成员。
orderDto.Should().BeEquivalentTo(order, options =>
options.Excluding(o => o.Products[1].Status));
当然, Excluding()  并 ExcludingMissingMembers()  可以结合起来。
您还可以采用不同的方法并明确告知Fluent Assertions要包含哪些成员。您可以直接指定属性表达式或使用作用于提供的谓词 ISubjectInfo  。
orderDto.Should().BeEquivalentTo(order, options => options
.Including(o => o.OrderNumber)
.Including(pi => pi.SelectedMemberPath.EndsWith("Date"));

包括属性和/或字段

您还可以更广泛地配置成员包含。除其他配置外,Fluent Assertions将包含所有 public  属性和字段。可以更改此行为:
// Include Fields
orderDto.Should().BeEquivalentTo(order, options => options
.IncludingFields();
// Include Properties
orderDto.Should().BeEquivalentTo(order, options => options
.IncludingProperties();
// Exclude Fields
orderDto.Should().BeEquivalentTo(order, options => options
.ExcludingFields();
// Exclude Properties
orderDto.Should().BeEquivalentTo(order, options => options
.ExcludingProperties();
此配置会影响成员的初始包含,并在任何 Exclude  s或其他 IMemberSelectionRule  s 之前发生。此配置也会影响匹配。例如,如果排除属性,则在查找预期对象的匹配时不会检查属性。

等效性比较行为

除了影响比较中包含的成员之外,还可以覆盖对特定成员执行的实际断言操作。
orderDto.Should().BeEquivalentTo(order, options => options
.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation, 1000))
.When(info => info.SelectedMemberPath.EndsWith("Date")));
如果您想为某种类型的所有成员执行此操作,可以像这样缩短上述调用。
orderDto.Should().BeEquivalentTo(order, options => options
.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation, 1000))
.WhenTypeIs<DateTime>();

枚举

默认情况下,按枚举的基础数值 Should().BeEquivalentTo()  比较 Enum  成员。 Enum  使用以下配置还可以选择仅按名称进行比较:
orderDto.Should().BeEquivalentTo(expectation, options => options.ComparingEnumsByName());

收藏和词典

考虑到我们的运行示例,您可以对 OrderDto  s 的集合使用以下内容:
orderDtos.Should().BeEquivalentTo(orders, options => options.Excluding(o => o.Customer.Name));
您还可以断言所有实例 OrderDto  在结构上都等于单个对象:
orderDtos.Should().AllBeEquivalentTo(singleOrder);

订购

默认情况下,Fluent Assertions将忽略集合中项目的顺序,无论集合是在对象图形的根目录还是隐藏在嵌套属性或字段中。如果订单很重要,您可以使用以下选项覆盖默认行为:
orderDto.Should().BeEquivalentTo(expectation, options => options.WithStrictOrdering());
您甚至可以告诉FA仅对特定集合或字典成员使用严格排序,类似于您排除某些成员的方式:
orderDto.Should().BeEquivalentTo(expectation, options => options.WithStrictOrderingFor(s => s.Products));
注意: 出于性能原因,将按照确切的顺序比较字节集合。

诊断

 Should().BeEquivalentTo  是一个非常强大的功能,也是Fluent Assertions的独特卖点之一。但有时它可能有点压倒性,特别是如果某些断言在意外情况下失败。为了帮助您了解Fluent Assertions如何比较两个(对象集合)对象图,失败消息将始终包含相关的配置设置:
Xunit.Sdk.XunitException
Expected item[0] to be 0x06, but found 0x01.
Expected item[1] to be 0x05, but found 0x02.
Expected item[2] to be 0x04, but found 0x03.
Expected item[3] to be 0x03, but found 0x04.
Expected item[4] to be 0x02, but found 0x05.
Expected item[5] to be 0x01, but found 0x06.
With configuration:
- Use declared types and members
- Compare enums by value
- Include all non-private properties
- Include all non-private fields
- Match member by name (or throw)
- Be strict about the order of items in byte arrays
但是,有时这还不够。对于需要了解更多但需要更多的情况,您可以添加该 WithTracing  选项。当添加到断言调用时,它会扩展上面的输出,如下所示:
With trace:
Structurally comparing System.Object[] and expectation System.Byte[] at root
{
Strictly comparing expectation 6 at root to item with index 0 in System.Object[]
{
Treating item[0] as a value type
}
Strictly comparing expectation 5 at root to item with index 1 in System.Object[]
{
Treating item[1] as a value type
}
Strictly comparing expectation 4 at root to item with index 2 in System.Object[]
{
Treating item[2] as a value type
}
Strictly comparing expectation 3 at root to item with index 3 in System.Object[]
{
Treating item[3] as a value type
}
Strictly comparing expectation 2 at root to item with index 4 in System.Object[]
{
Treating item[4] as a value type
}
Strictly comparing expectation 1 at root to item with index 5 in System.Object[]
{
Treating item[5] as a value type
}
}
默认情况下,仅当断言作为消息的一部分失败时才会显示跟踪。如果成功断言需要跟踪,您可以像这样捕获它:
object object1 = [...];
object object2 = [...];
var traceWriter = new StringBuilderTraceWriter();
object1.Should().BeEquivalentTo(object2, opts => opts.WithTracing(traceWriter));
string trace = traceWriter.ToString();
或者,您可以 ITraceWriter  为特殊目的编写自己的实现,例如写入文件。

全局配置

尽管结构等效API非常灵活,但您可能希望在全球范围内更改其中一些选项。这是静态类 AssertionOptions  发挥作用的地方。例如,要始终按名称比较枚举,请使用以下语句:
AssertionOptions.AssertEquivalencyUsing(options =>
options.ComparingEnumsByValue);
 Should().BeEquivalentTo  支持单个调用可用的所有选项,但某些特定于主题类型的重载除外(出于显而易见的原因)。

事件监控

Fluent Assertions有一组扩展,允许您验证对象是否引发了特定事件。在调用断言扩展之前,必须首先告诉Fluent Assertions您要监视对象:
var subject = new EditCustomerViewModel();
using (var monitoredSubject = subject.Monitor())
{
subject.Foo();
monitoredSubject.Should().Raise("NameChangedEvent");
}
请注意, subject  只要 using  块持续,Fluent Assertions将继续监视for 。
假设我们正在处理MVVM实现,您可能需要验证它是否 PropertyChanged  为特定属性引发了它的事件:
monitoredSubject
.Should().Raise("PropertyChanged")
.WithSender(subject)
.WithArgs<PropertyChangedEventArgs>(args => args.PropertyName == "SomeProperty");
请注意, WithSender()  验证所有出现的发送方参数都设置为指定的对象。  WithArgs()  只验证至少有一个匹配 EventArgs  对象。换句话说,事件监视仅适用于符合标准双参数sender / args .NET模式的事件。
由于验证 PropertyChanged  事件是如此常见,我已经包含了上述示例的专用快捷方式:
subject.Should().Raise().PropertyChangeFor(x => x.SomeProperty);
你也可以这样做; 声称没有提出特定事件。
subject.Should().NotRaisePropertyChangeFor(x => x.SomeProperty);
要么…
subject.Should().NotRaise("SomeOtherEvent");
还有一个通用版本 Monitor()  。它用于限制您要收听的事件。您可以通过提供定义事件的类型来实现。
var subject = new ClassWithManyEvents();
using (var monitor = subject.Monitor<IInterfaceWithFewEvents>();
{
}
 Monitor()  如果您希望使用监视动态生成的类的事件,则此通用版本也非常有用 System.Reflection.Emit  。由于事件是动态生成的并且在父类中不存在,因此非通用版本 Monitor()  将找不到事件。这样,您可以告诉事件监视器在生成的类中实现了哪个接口。
POCOClass subject = EmitViewModelFromPOCOClass();
using (var monitor = subject.Monitor<ISomeInterface>())
{
// POCO class doesn't have INotifyPropertyChanged implemented
monitor.Should().Raise("SomeEvent");
}
通过返回的对象 Monitor  公开一个命名的方法 GetEventRecorder  以及属性 MonitoredEvents  ,并 OccurredEvents  可以使用直接与显示器进行互动,比如建立自己的扩展。例如:
var eventSource = new ClassThatRaisesEventsItself();
using (var monitor = eventSource.Monitor<IEventRaisingInterface>())
{
EventMetadata[] metadata = monitor.MonitoredEvents;
metadata.Should().BeEquivalentTo(new[]
{
new
{
EventName = nameof(IEventRaisingInterface.InterfaceEvent),
HandlerType = typeof(EventHandler)
}
});
}

类型,方法和属性断言

我们在类型和类型的方法和属性上添加了许多断言。这些是相当技术性的断言,尽管我们喜欢将单元测试作为应用程序的功能规范读取,但我们仍然看到对类成员使用断言。例如,当您对类使用策略注入并要求其方法是虚拟的时。忘记将方法设为虚拟将避免策略注入机制为其创建代理,但您只会注意到运行时的后果。因此,创建一个在类上声明此类需求的单元测试会很有用。一些例子。
typeof(MyPresentationModel).Should().BeDecoratedWith<SomeAttribute>();
typeof(MyPresentationModel)
.Should().BeDecoratedWithOrInherit<SomeInheritedOrDirectlyDecoratedAttribute>();
typeof(MyPresentationModel).Should().NotBeDecoratedWith<SomeAttribute>();
typeof(MyPresentationModel)
.Should().NotBeDecoratedWithOrInherit<SomeInheritedOrDirectlyDecoratedAttribute>();
typeof(MyBaseClass).Should().BeAbstract();
typeof(InjectedClass).Should().NotBeStatic();
MethodInfo method = GetMethod();
method.Should().BeVirtual();
PropertyInfo property = GetSomeProperty();
property.Should().BeVirtual()
.And.BeDecoratedWith<SomeAttribute>();
您还可以使用 Methods()  或 Properties()  扩展方法和一些可选的过滤方法对特定类型的多个方法或属性执行断言。像这样:
typeof(MyPresentationModel).Methods()
.ThatArePublicOrInternal
.ThatReturnVoid
.Should()
.BeVirtual("because this is required to intercept exceptions")
.And.BeWritable()
.And.BeAsync();
typeof(MyController).Methods()
.ThatDoNotReturn<ActionResult>()
.ThatAreNotDecoratedWith<HttpPostAttribute>()
.Should().NotBeVirtual()
.And.NotBeAsync()
.And.NotReturnVoid()
.And.NotReturn<ActionResult>();
typeof(MyController).Methods()
.ThatReturn<ActionResult>()
.ThatAreDecoratedWith<HttpPostAttribute>()
.Should()
.BeDecoratedWith<ValidateAntiForgeryTokenAttribute>(
"because all Actions with HttpPost require ValidateAntiForgeryToken");
如果您还想断言属性具有特定属性值,请使用此语法。
typeWithAttribute.Should()
.BeDecoratedWith<DummyClassAttribute>(a => ((a.Name == "Unexpected") && a.IsEnabled));
您可以在适用于某些过滤器的程序集中断言所有类型的方法或属性,如下所示:
var types = typeof(ClassWithSomeAttribute).Assembly.Types()
.ThatAreDecoratedWith<SomeAttribute>()
.ThatImplement<ISomeInterface>()
.ThatAreInNamespace("Internal.Main.Test");
var properties = types.Properties().ThatArePublicOrInternal;
properties.Should().BeVirtual();
或者,您可以使用此更流畅的语法。
AllTypes.From(assembly)
.ThatAreDecoratedWith<SomeAttribute>()
.ThatImplement<ISomeInterface>()
.ThatDeriveFrom<IDisposable>()
.ThatAreUnderNamespace("Internal.Main.Test");
AllTypes.From(assembly)
.ThatAreNotDecoratedWith<SomeAttribute>()
.ThatDoNotImplement<ISomeInterface>()
.ThatDoNotDeriveFrom<IDisposable>()
.ThatAreNotUnderNamespace("Internal.Main")
.ThatAreNotInNamespace("Internal.Main.Test");
有这么多的可能性和专门的方法,这些例子都没有做好。 TypeAssertionSpecs.cs  有关更多示例,请查看源代码。

装配参考

如果您运行的是.NET 4.5或.NET Standard 2.0,则可以访问断言程序集的方法是否引用另一个程序集。这些通常用于在应用程序中强制执行层,例如,断言Web层不引用数据层。要声明引用,请使用以下语法:
assembly.Should().Reference(otherAssembly);
assembly.Should().NotReference(otherAssembly);

XML类

Fluent Assertions支持几个LINQ-to-XML类的断言:
xDocument.Should().HaveRoot("configuration");
xDocument.Should().HaveElement("settings");
xElement.Should().HaveValue("36");
xElement.Should().HaveAttribute("age", "36");
xElement.Should().HaveElement("address");
xElement.Should().HaveElementWithNamespace("address", "http://www.example.com/2012/test");
xElement.Should().HaveInnerText("some textanother textmore text");
最后两个断言也支持 XName  参数:
xElement.Should().HaveAttribute(XName.Get("age", "http://www.example.com/2012/test"), "36");
xElement.Should().HaveElement(XName.Get("address", "http://www.example.com/2012/test"));
xAttribute.Should().HaveValue("Amsterdam");
您还可以在这两个元素之间进行深度比较。
xDocument.Should().BeEquivalentTo(XDocument.Parse("<configuration><item>value</item></configuration>"));
xElement.Should().BeEquivalentTo(XElement.Parse("<item>value</item>"));
通过此语法可以在特定(根)元素之上链接其他断言。
xDocument.Should().HaveElement("child")
.Which.Should().BeOfType<XElement>()
.And.HaveAttribute("attr", "1");

执行时间处理时间

版本1.4中的新增功能是断言特定方法或操作的执行时间不超过预定义值的方法。要验证方法的执行时间,请使用以下语法:
public class SomePotentiallyVerySlowClass
{
public void ExpensiveMethod()
{
for (short i = 0; i < short.MaxValue; i++)
{
string tmp = " ";
if (!string.IsNullOrEmpty(tmp))
{
tmp += " ";
}
}
}
}
var subject = new SomePotentiallyVerySlowClass();
subject.ExecutionTimeOf(s => s.ExpensiveMethod()).Should().BeLessOrEqualTo(500.Milliseconds());
或者,要验证任意操作的执行时间,请使用以下语法:
Action someAction = () => Thread.Sleep(100);
someAction.ExecutionTime().Should().BeLessOrEqualTo(200.Milliseconds());
受支持的断言 ExecutionTime()  是为 TimeSpan  s 找到的断言的子集,即:
someAction.ExecutionTime().Should().BeLessOrEqualTo(200.Milliseconds());
someAction.ExecutionTime().Should().BeLessThan(200.Milliseconds());
someAction.ExecutionTime().Should().BeGreaterThan(100.Milliseconds());
someAction.ExecutionTime().Should().BeGreaterOrEqualTo(100.Milliseconds());
someAction.ExecutionTime().Should().BeCloseTo(150.Milliseconds(), 50.Milliseconds());

断言范围

您可以将多个断言批处理为一个, AssertionScope  以便FluentAssertions在范围结束时抛出一个异常,但所有失败。
例如
using (new AssertionScope())
{
5.Should().Be(10);
"Actual".Should().Be("Expected");
}
上面将批处理两个失败,并在处理 AssertionScope  显示两个错误时抛出异常。
例如,在dispose处抛出的异常包含:
Expected value to be 10, but found 5.
Expected string to be "Expected" with a length of 8, but "Actual" has a length of 6, differs near "Act" (index 0).
at........
有关更多信息,请查看单元测试中的 AssertionScopeSpecs.cs
 

转载于:https://www.cnblogs.com/gaoliangchao/p/10036836.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值