目前只能测试正常属性的值,无法测试索引器值。 我需要编写一个看起来像这样的测试:
```c#
[测试夹具]
公共类测试类
{
类示例
{
私人字典
public string this[string key]
{
get { return dictionary[key]; }
set { dictionary[key] = value; }
}
}
[Test]
public void Test()
{
var obj = new Example
{
["TEST1"] = "1",
["TEST2"] = "2",
};
Assert.That(obj, Has.Property("Item", "TEST1").EqualTo("1").And.Property("Item", "TEST2").EqualTo("2"));
}
}
``
我喜欢这个主意。
C# 不支持命名索引属性,但 VB.NET 支持。 我认为语法.Property("Name", index1, ...)
应该保留给命名索引器,所以我不希望看到它用于默认索引器。 我希望能够通过不要求人们传入名称参数来尊重 C# 语言。
您如何看待Has.Item("TEST1").EqualTo("1")
或Has.Index("TEST1").EqualTo("1")
?
@jnm2
将“Item”或“Indexer”方法作为快捷方式很方便,但实际上可以在 C# 中更改索引器名称: https :
@Ch0senOne可以更改名称。 更清楚地说,我应该说,在 C# 中不可能创建非默认索引器。 例如,您不能声明两个索引器,也不能使用 C# 中的名称。
仔细考虑一下,恐怕Has.Item("value")
与Contains.Item
会混淆。 那么也许Has.Index("value")
?
所以我赞成Has.Index("IndexValue")
用于默认索引器,而Has.Property("PropertyName", indexParams)
用于非默认索引器。 Has.Property
最终也会为默认索引器工作,即使 C# 惯用的做法是使用Has.Index
。
或者如果我们想变得可爱,我们可以做Has.Index["IndexValue"]
和
Has.Property("PropertyName")[indexparams]
... 😁
实际上,我想知道我们是否可以使用索引器语法摆脱Has.Item["AtIndex"]
以避免与Contains.Item("ItemValue")
混淆......不知道我是怎么想的!
让我们看看其他@nunit/framework-team 成员的想法。
我们已经定义了使用 lambda 而不是字符串来处理属性的方向。 似乎时间可能会更好地花在这上面。 我在用手机,所以不能给你问题号。
@CharliePoole https://github.com/nunit/nunit/issues/26?
这对Has.Property
有意义。 我们可以这样做:
Has.Property(_ => _.PropertyName[index1, ...])
Has.Property(_ => _.PropertyName, index1, ...)
我仍然认为在需要非默认属性之前我们不应该对Has.Property
做任何事情,现在只需提供Has.Index(value1, ...)
或Has.Index[value1, ...]
或Has.Item[value1, ...]
更常见的默认索引器。
就是那个。 我同意你关于命名索引器的意见,无论如何都没有被要求。
@jnm2为了您的考虑,您也可以制作一个多键索引器。 例如
this[string key1, string key2]
我想知道这个问题是否有任何更新,或者当前在不继承 IEnumerable 的类型上测试索引器的方法。 我想我可以检查是否抛出了 KeyNotFoundException。 我忽略的其他任何东西都可以让我检查索引器中是否存在键?
@crush83是的,我的index1, ...
语法旨在表明我们无论做什么都应该考虑它们。
我不喜欢术语键,除非我们谈论的对象实际上是字典或键控集合。 这些是带有默认索引器的东西,但并非所有带有默认索引器的东西都有键。 (既然您正在使用这个术语,也许您会发现我们的Contains.Key
语法很有帮助?我们可以改进它吗?)
除了你在这里看到的之外,没有任何进展。 我们需要一个提案。 把一些东西放在那里,我目前最喜欢的是Has.Item(params object[] indexerArgs)
,不管它的名字是什么,它都会调用默认的索引器。
到目前为止,这会完成所有要求吗?
+1
@nunit/framework-team 您是否支持以下新 API?
namespace NUnit.Framework
{
public abstract class Has
{
public static ResolvableConstraintExpression Property(string name);
+ public static ResolvableConstraintExpression Item(params object[] indexerArgs);
}
}
namespace NUnit.Framework.Constraints
{
public class ConstraintExpression
{
public ResolvableConstraintExpression Property(string name);
+ public ResolvableConstraintExpression Item(params object[] indexerArgs);
}
}
在有理由将它添加到 API 之前,我可能会将IndexerConstraint
类留在内部。
听起来不错。
另一票赞成,我只是想将此功能用于控制台测试。 🙂
@jnm2这个问题还是团队想要做的吗?
我对这个问题进行了快速测验,因为我不明白到底希望支持的 API 应该是什么样子 - 是吗:
/// obj from the initial post example
Assert.That(obj, Has.Item("TEST1").EqualTo(1).And.Item("TEST2").EqualTo(2));
或者是:
Assert.That(obj, Has.Item("TEST1", 1).And.Item("TEST2", 2));
?
你的例子需要params object[]
所以选择选项 2。
假设选择。 2、是否需要数组就像只提供一个对象,断言键存在,2 个参数 - 键和值? 其他组合(或多或少的预期参数),失败?
@Poimen是的,我们将不胜感激!
该数组是必需的,因为索引器可以采用多个参数。 例如:
public string this[int x] => "First indexer";
public string this[string x] => "Second indexer";
public string this[int x, int y] => "Third indexer";
您将使用Has.Item(42).EqualTo("First indexer")
、 Has.Item("42").EqualTo("Second indexer")
和Has.Item(1, 2).EqualTo("Third indexer")
。
看,你每天都在学习新东西 - 我以为这是一个错字,哎呀:see_no_evil:我以前没有使用过多键索引器的乐趣......
好的,很酷,这周我会考虑把一些东西放在一起。
@jnm2我为此问题创建了一个 PR。
我担心的一个问题是代码产生的错误消息:
Has.Item(42)
错误消息有点“hacky”,但我不确定解决这个问题的最干净的方法是什么。 “黑客”是:
https://github.com/nunit/nunit/pull/3609/commits/96154ca6402c692cec5e0ae6947f9922e72799fe#diff -ceeee4b8399633f181b6bccd5a39eb32R72
@Poimen抱歉,不确定您的意思。 当前的消息是什么,您希望它是什么? 或者您正在寻找建议?
@jnm2出现的错误信息是:
Expected: indexer [System.Int32]
But was: "not found"
错误消息的形成方式StringifyArguments
函数有点“hacky”。
因此,如果有一种更简洁的方式来生成所需的消息输出,那么我表达得不好的问题更多。
@jnm2我想我已经解决了评论中的评论(感谢他们)。 标签上写着awaiting:contributor
。
我不确定我是否需要做更多的事情来让您知道我已经“完成”了:+1:
我认为我们应该尽可能接近Has.Property
找不到属性时显示的消息。 那么我们就应该更换property
与default indexer
,取而代之的属性名accepting arguments [42]
或类似的东西。 我倾向于认为我们应该使用现有的格式助手之一,如MsgUtils.FormatCollection
来显示参数值而不是列出参数类型。 例如,其中一个参数可能为 null 并且适用于一系列类型。
不,那太好了! 像 PR 线程本身那样的评论将是理想的。
鉴于断言:
```c#
Assert.That(tester, Has.Item("wow"));
It producers the message:
预期:默认索引器接受参数 <
但是是:“未找到”
Given the assertion:
```c#
Assert.That(tester, Has.Item("wow").EqualTo(1));
它产生消息:
Default indexer accepting arguments < "wow" > was not found on NUnit.Framework.Constraints.IndexerConstraintTests+NamedIndexTester.
这是使用MsgUtils.FormatCollection
函数生成的。
因此,也更紧密地匹配属性消息,并添加了详细信息。
我非常喜欢第二种情况。 第一种情况对我来说在语义上仍然是错误的,因为它显示了参数类型而不是参数值。 我们只有参数值,每个参数值可以匹配索引器中的一系列参数类型。 让它看起来好像必须有一个带有System.String
参数的索引器是一种误导,因为IEnumerable<char>
参数也可以正常工作。
IEnumerable<char>
是一个古怪的用例......但当然,我明白了。
所以,回到 - 给定:
```c#
Assert.That(tester, Has.Item("wow"));
producers the message:
预期:默认索引器接受参数<“哇”>
但是是:“未找到”
For numbers it goes into the usual suffix - given:
```c#
Assert.That(tester, Has.Item(42d));
生产者:
Expected: Default indexer accepting arguments < 42.0d >
But was: "not found"
对我来说,“预期”消息看起来很棒。 But was: "not found"
消息看起来有点像是在谈论内容not found
的字符串实例,但是如果 PropertyConstraint 的行为方式相同,那么保持它们相同对我来说更可取。 默认情况下,更改 PropertyConstraint 将超出此 PR 的范围。 如果您愿意,我们仍然可以进行更改,并且我和框架团队中的其他人同意更改。
是的,我试图让它不这样做,但是我找不到带有错误消息的解决方案。 我希望它只是:
But was: not found
但是ConstraintResult
的返回是将字符串引号放在那里。
参考属性版本:
```c#
Assert.That(tester, Has.Property("NoItDoesNotHaveThis"));
produces:
预期:属性 NoItDoesNotHaveThis
但是是:
``
所以没有引号 - 但它返回类型 - 而不是值。
我不确定返回ConstraintResult
的重载是否是一个解决方案?
我认为我们应该坚持 PropertyConstraint 为保持一致性所做的事情。 当消息说失败的是找到属性时,显示actual
值的默认表示并不是最糟糕的事情。
返回从 ConstraintResult 派生的新类是其他约束如何自定义它,是的。
好的,太好了 - 听起来像 - 给出:
```c#
Assert.That(tester, Has.Item(42d));
producers:
预期:默认索引器接受参数 <42.0d>
但是是:
``
这与属性约束消息匹配。
@nunit/framework-team 和所有人,我希望能迅速解决这个问题,因为@Poimen的优秀公关 (https://github.com/nunit/nunit/pull/3609) 已准备好合并。
@Dreamescaper引起了我的注意,我在这个我忘记了的更关注Assert.That(collection, Has.Item("x"));
认为Has.Item
意味着Contains.Item
.
我们可以考虑在失败消息中添加一些内容,但这无助于在阅读源代码中的Has.Item
时正确理解它的问题:
Expected: Default indexer accepting arguments < 42.0d > (Did you mean to use Contains.Item?)
另一方面,我不喜欢Has.Index(42)
或Has.Index(42).EqualTo(43)
,这是我建议的而不是Item
。 该实例在索引有一个项目。 你不会说实例有一个索引并且索引本身等于 43。索引是你传入的东西,42。
Has.Item
。 放弃阅读源代码时可能导致的问题。 也许添加一些缓解措施以在编写源代码时提供帮助,例如 XML 文档和失败消息中的提示。Has.Index
尽管术语混杂很尴尬。Has.ItemAt
。 Has.ItemAt(42).EqualTo(43)
。等等,我刚刚爱上了最后一个建议,因为它与 LINQ ElementAt 方法有一个相似之处,该方法被赋予一个索引并返回一个项目。
每个人都对Has.ItemAt
满意吗? 欢迎提出建议,但我想再次尊重@Poimen已经投入的时间。
+1 为Has.ItemAt
:)
我同意你的看法, Has.Item
可能会令人困惑。 我喜欢Has.ItemAt
但既然你喜欢 LINQ 语法,为什么不使用Has.ElementAt
呢?
我不确定 ElementAt,因为它不是直接的等价物。
例如,对于Dictionary<string,string>
LINQ 的 ElementAt(0) 将返回第一对,而 Has.ItemTo(0).EqualTo("...") 将失败,因为没有索引器接受整数。
@CharliePoole这真是个好主意。 看起来它不会像查找那样直观,但我不知道为什么我会这么想。 我想我更喜欢Item
因为我们将在大概某种集合/字典/查找上调用索引器,而ElementAt
旨在用于查询和其他不一定是集合的枚举. 集合元素通常称为项。
@Dreamescaper你认为Has.ItemAt(4)
能让人们认为它像ElementAt
,枚举而不是调用索引器吗?
@jnm2
它可能可以,但我不知道更好的选择(而且我认为它仍然是比 Has.Item 更好的选择)。
@jnm2我实际上有点困惑。 这个问题一开始是关于属性的,现在仍然如此。 Has.Property 和 Has.ItemAt 在用户眼中将如何相互关联、交互或比较?
[我意识到属性和集合项在某种抽象级别上可以被视为等效,但我认为这不是我们大多数人通常操作的抽象级别。 :smiling_imp: ]
作为 NUnit 4.0 的东西,了解一下术语在 API 中的使用方式并稍微整理一下可能会很好。 “成员”一词通常等同于“项目”,但也可能表示属性、方法或变量。
我觉得ItemAt
比ElementAt
更舒服,部分原因是它看起来像是我们委托给Enumerable.ElementAt
或采用与ElementAt(int index)
给我留下深刻印象的不是它如何工作的细节,而是 BCL 中有At
后缀的先例,尤其是与名为index
的参数相关联
如果将其与 ILookup 一起使用,则 ILookup 的每个索引都会返回多个 TElement,而不是单个元素/项目。 Has.EntryAt
可能适用于集合、字典和多元素索引查找。 我最初的反应是Has.ItemAt
IEnumerable<TElement>
从 ILookup 返回的。
大家怎么看? 当您看到不止一个事情在起作用时,这很难,但是这里的多数偏好也可能有助于表明哪个名称最容易被人们找到。 除了@nunit/framework-team 之外的人,我并不是有意排斥你。 如果您正在观看并且有强烈的感觉,那么这是一个总是有用的数据点!
@CharliePoole Has.Property("X")
断言该实例具有名为X
的非参数化属性。 Has.ItemAt("X")
会断言该实例具有可以接受字符串参数的默认索引器(具有不相关的、众所周知的名称的参数化属性)。
Has.Property("X").EqualTo(3)
断言该实例具有名为X
的非参数化属性,如果您调用其非参数化get
访问器,该属性将返回3
。 Has.ItemAt("X").EqualTo(3)
将断言实例都有一个默认的索引(用不相关的,众所周知的名称的参数属性)返回3
如果您通过特定的字符串"X"
其get
存取器。
使默认索引器的名称无关紧要的是,C#、VB 和 F# 语法都可以在不指定名称的情况下调用默认索引器。 (因此术语“默认”。)没有默认索引器概念但具有命名索引器概念的语言必须指定名称。 默认索引器的名称通常是Item
,但是默认索引器可以任意命名并且仍然是默认索引器,因为这些语言仍然可以在不指定名称的情况下调用它。 在 C# 中,您可以使用[IndexerName("ArbitraryName")]
属性来完成此操作。
@jnm2我实际上了解有关索引器的所有 C# 等语言内容......但是......
我认为财产之间存在混淆的可能性
我强调 __you__ 不会感到困惑,我可能只会在与您交谈时假装如此(苏格拉底式的方法,你知道 :wink: )但我怀疑许多人可能会感到困惑,而您会向人们解释它非常多。
我不反对添加,但我认为需要在文档中详细说明上述区别。
PS:我完全没有被排斥的感觉。 :笑脸:
@CharliePoole好Has.ItemAt
/ Has.ElementAt
/ Has.EntryAt
/etc 之间的选择? 或者我是否遗漏了一点,并且您支持基于这种潜在混淆的特定命名选择?
到目前为止,我倾向于ItemAt
。 @Dreamescaper ,你还在那里吗? @CharliePoole我不确定您是表达对ElementAt
的偏好还是只是帮助我们思考整个过程。
参与Has.ItemAt
/ Has.ElementAt
/ Has.EntryAt
/other 的人越多,我们对人们会发现的直观内容就越有信心。 我想在第二天左右将其称为领先者,因为如果我们花费更长的时间,我们将错过 3.13 版本。
@jnm2只是想通过自己思考并鼓励同样的想法。
WRT 单词的选择,我想知道某些特定的措辞是否最适合我列出的三种表达方式,以及是否还有其他组合需要担心。 有了我提到的三个选项,你就会有
Has.ItemAt("foo").EqualTo("bar")
所以我想你是对的,措辞对任何选项都不重要
从长远来看,我宁愿使用带方括号的实际索引。 但这需要实际编译约束表达式,而不是建模为一组类。
我知道这高于我的工资等级 :smile:, 但我想我会在这里插话......
我喜欢ItemAt
。
正如@CharliePoole 所建议的那样,我花了一些时间思考一些用例。 我开始认为ElementAt
在IEnumerable
情况下使用得更多,并且语言索引器可以处理的不仅仅是IEnumerable
。 我在ElementAt
栅栏上坐了一会儿,然后考虑:
```c#
Assert.That(..., Has.ElementAt("one", "two"));
against:
```c#
Assert.That(..., Has.ItemAt("one", "two"));
ItemAt
表达多个值索引器感觉更自然。
我还认为这张罚单最初是用于财产限制的短路,而ItemAt
符合该叙述。 (然而,这并不排除它变成更多/更好/等的东西,只是一个观点)。
我也更喜欢ItemAt
,所以让我们称其为多数并支持这一点😺
为防止混淆,让我们确保在完成后迅速记录并包含在发行说明中。
@Poimen Re:你的工资等级,不要低估我们在这里所做的事情的程度。 感谢您分享您的想法!
至于薪酬等级,由于这里的每个人在 NUnit 工作的年薪约为 0 美元,我认为我们都处于相同的薪酬等级 😝
@Dreamescaper在 PR 中提出了另一个好问题。 无论是正数还是负数,这就像检查计数而不是检查索引器的存在:
Assert.That(new[] { 1, 2, 2 }, Has.ItemAt(3));
Assert.That(new[] { 1, 2, 2 }, Has.No.ItemAt(3));
在我们有更多时间考虑之前,移除 Exists 约束似乎是最安全的。 我更愿意为Has.Indexer(typeof(Type1), typeof(Type2), ...)
为索引器存在性检查打开一个单独的问题,并Has.ItemAt(arg1, arg2, ...)
实际运行索引器并断言有关结果值的内容。
有人会反对最初将Has.ItemAt(3)
运送为未解析为约束而将Has.ItemAt(3).EqualTo(4)
运送为解析吗?
Has.Indexer
对我来说很有意义。 我假设您还会支持Has.Indexer<T>()
、 Has.Indexer<T1, T2>()
等,以与其他约束保持一致。
感谢您的反馈! 我批准了拉取请求以使 Has.ItemAt(...) 不再是自我解决的,并且我提交了https://github.com/nunit/nunit/issues/3690以跟踪Has.Indexer
来提供这种能力。
最有用的评论
至于薪酬等级,由于这里的每个人在 NUnit 工作的年薪约为 0 美元,我认为我们都处于相同的薪酬等级 😝