如何比较通过 ID 链接的对象图?
Posted
技术标签:
【中文标题】如何比较通过 ID 链接的对象图?【英文标题】:How do I compare object graphs that are linked by means of IDs? 【发布时间】:2020-06-12 16:06:02 【问题描述】:Fluent Assertions 库通过Should().BeEquivalentTo
方法和相关方法非常强调其comparing object graphs 的功能。正如文档指出的那样:
Should().BeEquivalentTo
是一个非常强大的功能,也是 Fluent Assertions 的独特卖点之一。
通过阅读示例,我得到的印象是对象图必须通过对象引用来连接。
现在,我有很多我想检查其内容的对象图和树,但它们通常是从文件或数据库中加载的。 因此,连接仅通过跨对象匹配的 ID 属性值存在。
例如,想象一下这个简单的结构(这里以 JSON 格式编写)- 示例 A:
[
"Id": 1,
"Name": "Foo",
"NextId": 2
,
"Id": 2,
"Name": "Bar"
]
我想使用 Fluent Assertions 来检查此类对象图的结构。
也就是说,无论Id
和NextId
字段实际具有什么值,第一个对象中的NextId
必须与第二个对象中的Id
具有相同的值。
因此,任何具有两个不同 Id
值的两个对象的集合,其中一个对象的 NextId
值等于另一个对象的 Id
值,都可以。
示例 B:
[
"Key": 5,
"LinkedTo": [3]
,
"Key": 10,
"LinkedTo": []
,
"Key": 3,
"LinkedTo": [5]
]
在这里,具有三个不同Key
值的三个对象的任何集合,其中两个对象在其LinkedTo
属性中引用彼此的键,而其余对象在其LinkedTo
属性中具有一个空数组应该匹配。
示例 C:
[
"Id": 1,
"Owner": 4
,
"Id": 2,
"Owner": 4
,
"Id": 3,
"Owner": 4
,
"Id": 4
]
在这里,如果其中三个对象的 Owner
属性的值与其余对象的 Id
属性匹配,则具有三个不同 Id
值的任何四个对象的集合都将匹配。
这可以通过BeEquivalentTo
以某种方式完成吗?
请注意,我确实不想在应用任何断言之前遍历我的对象并添加实际的对象引用,因为该操作本身可能会引入其自身的错误。
编辑:根据要求,以下是上述示例的几个匹配和不匹配图表:
示例 A:
匹配:
[
"Id": 5,
"Name": "Foo",
"NextId": -8
,
"Id": -8,
"Name": "Bar"
]
匹配:
[
"Id": 200,
"Name": "Bar"
,
"Id": 30,
"Name": "Foo",
"NextId": 200
]
不匹配:
[
"Id": 3,
"Name": "Bar",
"NextId": 6
,
"Id": 6,
"Name": "Foo"
]
示例 B:
匹配:
[
"Key": 20,
"LinkedTo": [7]
,
"Key": 8,
"LinkedTo": []
,
"Key": 7,
"LinkedTo": [20]
]
匹配:
[
"Key": 9,
"LinkedTo": [100]
,
"Key": 100,
"LinkedTo": [9]
,
"Key": 3,
"LinkedTo": []
]
不匹配:
[
"Key": 5,
"LinkedTo": [10]
,
"Key": 10,
"LinkedTo": []
,
"Key": 3,
"LinkedTo": [5]
]
示例 C:
匹配:
[
"Id": 1
,
"Id": 2,
"Owner": 1
,
"Id": 3,
"Owner": 1
,
"Id": 4,
"Owner": 1
]
匹配:
[
"Id": 10,
"Owner": 20
,
"Id": 30,
"Owner": 20
,
"Id": 20
,
"Id": 40,
"Owner": 20
]
不匹配:
[
"Id": 8,
"Owner": 2
,
"Id": 12,
"Owner": 8
,
"Id": 54,
"Owner": 2
,
"Id": 2
]
【问题讨论】:
您想要断言您的数据究竟是什么?BeEquivalentTo
将断言集合具有相同数量的元素,并且A
中的每个元素在B
中具有匹配项,其中所有属性都具有等效值(递归地使用相同的等效逻辑)。测试是否在您期望它通过的地方失败?你能分享一个完整的代码测试吗?
@JamesFaix:我不知道如何为此编写测试代码。我想断言第一个元素中的 NextId
与第二个元素中的 Id
具有相同的值——无论该值是 2、5 还是 369。我想等价逻辑会以某种方式包括之间的映射来自我预期数据的 ID 和在实际数据中找到的 ID。
BeEquivalentTo
最常用于断言 subject,例如针对预期值的函数返回值。您是否尝试断言有关您的代码的一些不变量?您能否提供一个应该匹配的主题+期望对,而另一对应该匹配失败?
@JonasNyrup:完成。 (更详细地说:对于 expected 图的三个示例中的每一个,我添加了两个匹配的 subjects 和一个匹配失败的 subject .)
【参考方案1】:
据我了解您的问题,您有一个对象列表,并希望断言每个连续对的 NextId
和 Id
匹配。
class MyClass
public MyClass(int id, int nextId, string name)
Id = id;
NextId = nextId;
Name = name;
public int Id get; set;
public int NextId get; set;
public string Name get; set;
Fluent Assertions 不知道连续性,相反,我们可以创建两个列表,使得列表中的每个匹配对对应于原始列表中的连续项。 我们现在可以比较这两个列表,看看列表中的每一对是否具有所需的关系。
要比较NextId
和Id
的两个列表,我们需要指示Fluent Assertions 如何比较MyClass
的两个实例。
对于BeEquivalentTo
,您可以使用Using<T>()
+ WhenTypeIs<T>()
组合指定如何与给定类型的实例进行比较。
请注意,默认情况下会尝试以任意顺序匹配这两个列表,但通过指定 WithStrictOrdering()
它将是成对比较。
[TestMethod]
public void Lists_BeEquivalentTo()
var objects = new[]
new MyClass(1, 2, "Foo"),
new MyClass(2, 3, "Bar"),
new MyClass(3, 4, "Baz")
;
objects.Take(objects.Length - 1).Should().BeEquivalentTo(objects.Skip(1), opt => opt
.WithStrictOrdering()
.Using<MyClass>(e => e.Subject.NextId.Should().Be(e.Expectation.Id))
.WhenTypeIs<MyClass>());
如果比较就像比较两个整数一样简单,也可以使用Equal()
断言,它只接受两个列表和一个指定两个元素何时相等的谓词。
[TestMethod]
public void Lists_Equal()
var objects = new[]
new MyClass(1, 2, "Foo"),
new MyClass(2, 3, "Bar"),
new MyClass(3, 4, "Baz")
;
objects.Take(objects.Length - 1).Should().Equal(objects.Skip(1),
(a, b) => a.NextId == b.Id);
【讨论】:
“并且想要断言每个连续对的 NextId 和 Id 匹配” - 不,抱歉,这根本不是我想要的。我希望比较两个对象图,其中的边不是由 .NET 对象引用表示,而是由匹配的 ID 值表示。我会尽量澄清我的问题。 我提供了一些更多示例性的对象图进行说明。以上是关于如何比较通过 ID 链接的对象图?的主要内容,如果未能解决你的问题,请参考以下文章