Nunit: Demande : Ajouter la prise en charge des indexeurs à la PropertyConstraint

Créé le 2 juin 2017  ·  46Commentaires  ·  Source: nunit/nunit

Pour le moment, il est uniquement possible de tester les valeurs des propriétés normales et aucun moyen de tester les valeurs de l'indexeur. J'ai besoin d'écrire un test qui ressemble à ceci:

```c#
[Test Fixture]
classe publique TestClass
{
classe Exemple
{
Dictionnaire privédictionnaire = nouveau Dictionnaire();

  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"));
}

}
```

done help wanted enhancement normal

Commentaire le plus utile

Et en ce qui concerne le niveau de rémunération, puisque tout le monde ici gagne un salaire annuel d'environ 0 $ en travaillant sur NUnit, je pense que nous sommes tous au même niveau de rémunération 😝

Tous les 46 commentaires

J'aime l'idée.

C# ne prend pas en charge les propriétés indexées nommées, contrairement à VB.NET. Je pense que la syntaxe .Property("Name", index1, ...) devrait être réservée aux indexeurs nommés, donc je ne voudrais pas qu'elle soit utilisée pour un indexeur par défaut. J'aimerais pouvoir respecter le langage C# en n'obligeant pas les gens à passer un paramètre de nom.

Que pensez-vous de Has.Item("TEST1").EqualTo("1") ou Has.Index("TEST1").EqualTo("1") ?

@jnm2
Avoir la méthode "Item" ou "Indexer" comme raccourci est pratique, mais il est en fait possible de changer le nom de l'indexeur en C# : https://stackoverflow.com/questions/5110403/class-with-indexer-and-property-named -Objet

@Ch0senOne Il est possible de changer le nom. Pour être plus clair, j'aurais dû dire qu'il n'est pas possible de créer un indexeur non par défaut en C#. Par exemple, vous ne pouvez pas déclarer deux indexeurs, ni utiliser le nom de C#.
En y réfléchissant, j'ai peur que Has.Item("value") soit trop confus avec Contains.Item . Alors peut-être Has.Index("value") ?

Je serais donc en faveur de Has.Index("IndexValue") pour l'indexeur par défaut et de Has.Property("PropertyName", indexParams) pour les indexeurs non par défaut. Has.Property finirait par fonctionner pour les indexeurs par défaut même si la chose idiomatique C# à faire serait d'utiliser Has.Index .


Ou si nous voulions être mignons, nous pourrions faire Has.Index["IndexValue"] et
Has.Property("PropertyName")[indexparams] ...

En fait, je me demande si on pourrait s'en tirer avec Has.Item["AtIndex"] utilisant la syntaxe de l'indexeur afin d'éviter d'être confondu avec Contains.Item("ItemValue") ... pas sûr de ce que j'en pense !


Voyons ce que pensent les autres membres de la @nunit/framework-team.

Nous avons déjà défini une direction pour aller sur les propriétés en utilisant des lambdas plutôt que des chaînes. On dirait qu'il vaut mieux y consacrer du temps. Je suis sur mon téléphone donc je ne peux pas vous donner le numéro d'émission.

@CharliePoole https://github.com/nuni/nunit/issues/26?

Cela a du sens pour Has.Property . Nous pourrions faire:

  • Has.Property(_ => _.PropertyName[index1, ...])
  • Has.Property(_ => _.PropertyName, index1, ...)

Je pense toujours que nous ne devrions rien faire avec Has.Property tant que des propriétés non par défaut ne sont pas nécessaires, et pour l'instant, fournissez simplement Has.Index(value1, ...) ou Has.Index[value1, ...] ou Has.Item[value1, ...] pour le indexeurs par défaut beaucoup plus courants.

C'est celui-là. Je suis d'accord avec vous sur les indexeurs nommés, qui n'ont de toute façon pas été demandés.

@jnm2 Pour votre considération, vous pouvez également créer un indexeur à plusieurs clés. Par exemple

this[string key1, string key2]

Je me demande s'il y a une mise à jour sur ce problème, ou une façon actuelle de tester les indexeurs sur un type qui n'hérite pas de IEnumerable. Je suppose que je pourrais simplement vérifier si une KeyNotFoundException est levée. Y a-t-il autre chose que j'oublie pour me permettre de vérifier l'existence d'une clé dans un indexeur ?

@crush83 Oui, ma syntaxe index1, ... était censée indiquer que nous devrions les considérer quoi que nous fassions.

Je n'aime pas la clé terminologique sauf si nous parlons d'un objet qui est en fait un dictionnaire ou une collection à clé. Ce sont des choses avec des indexeurs par défaut, mais toutes les choses avec des indexeurs par défaut n'ont pas de clés. (Puisque vous utilisez cette terminologie, peut-être trouveriez-vous notre syntaxe Contains.Key utile ? Pouvons-nous l'améliorer ?)

Il n'y a eu aucun progrès autre que ce que vous voyez ici. Nous avons besoin d'une proposition. Pour mettre quelque chose là-bas, mon préféré actuel est Has.Item(params object[] indexerArgs) qui appellerait l'indexeur par défaut quel que soit son nom.
Est-ce que cela ferait tout ce qui est demandé jusqu'à présent ?

+1

@nunit/framework-team Souhaitez-vous prendre en charge la nouvelle API suivante ?

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);
    }
}

Je laisserais probablement la classe IndexerConstraint interne jusqu'à ce qu'il y ait une raison de l'ajouter à l'API.

Cela me semble bien.

Encore un vote en faveur, je voulais juste cette fonctionnalité pour un test console. 🙂

@jnm2 Ce problème est-il toujours quelque chose que l'équipe souhaite

J'ai eu un rapide squiz sur le problème et comme je ne comprends pas exactement ce que l'API prise en charge devrait ressembler - est-ce:

/// obj from the initial post example
Assert.That(obj, Has.Item("TEST1").EqualTo(1).And.Item("TEST2").EqualTo(2));

ou est-ce:

Assert.That(obj, Has.Item("TEST1", 1).And.Item("TEST2", 2));

?

Votre exemple prend params object[] donc allait avec l'option 2.

En supposant que l'opt. 2, le tableau serait-il requis comme si un seul objet fournissait, affirmait que la clé existe, 2 paramètres - clé et valeur ? Autres combinaisons (paramètres plus ou moins attendus), échec ?

@Poimen Oui, et nous apprécierions votre aide !

Le tableau est obligatoire car les indexeurs peuvent prendre plusieurs arguments. Par exemple:

public string this[int x] => "First indexer";

public string this[string x] => "Second indexer";

public string this[int x, int y] => "Third indexer";

Vous utiliseriez Has.Item(42).EqualTo("First indexer") , Has.Item("42").EqualTo("Second indexer") et Has.Item(1, 2).EqualTo("Third indexer") .

Vous voyez, vous apprenez quelque chose de nouveau tous les jours - j'avais supposé que c'était une faute de frappe, oups :see_no_evil: Je n'ai jamais eu le plaisir d'utiliser un indexeur multi-clés auparavant...

Ok, cool, je vais essayer de préparer quelque chose cette semaine.

@jnm2 J'ai créé un PR pour ce problème.

Le seul souci que j'ai est le message d'erreur qui est produit par le code :

Has.Item(42)

Le message d'erreur est un peu "hacky", mais je ne sais pas quel est le moyen le plus propre de résoudre ce problème. Le « piratage » est :
https://github.com/nunit/nunit/pull/3609/commits/96154ca6402c692cec5e0ae6947f9922e72799fe#diff -ceeee4b8399633f181b6bccd5a39eb32R72

@Poimen Désolé,

@jnm2 Le message d'erreur qui apparaît est :

  Expected: indexer [System.Int32]
  But was:  "not found"

La façon dont les messages d'erreur sont formés par la fonction StringifyArguments est un peu "hacky".

Ma question mal exprimée est donc plutôt de savoir s'il y avait un moyen plus propre de produire le message souhaité.

@jnm2 Je pense avoir répondu aux commentaires sur la critique (merci pour eux). L'étiquette indique awaiting:contributor .

Je ne sais pas s'il y a quelque chose de plus que je dois faire pour vous faire savoir que j'ai "fini" :+1:

Je pense que nous devrions nous rapprocher le plus possible du message qui s'affiche lorsque Has.Property ne trouve pas de propriété. Nous devrions alors remplacer property par default indexer et remplacer le nom de la propriété par accepting arguments [42] ou quelque chose comme ça. Je penche pour penser que nous devrions utiliser l'un des assistants de formatage existants comme MsgUtils.FormatCollection pour afficher les valeurs des arguments plutôt que de lister les types d'arguments. Par exemple, l'un des arguments peut être null et fonctionner avec une gamme de types.

Non, c'est super ! Un commentaire comme celui-là dans le fil de relations publiques lui-même serait idéal.

Étant donné l'affirmation :
```c#
Assert.That(testeur, Has.Item("wow"));


It producers the message:

Attendu : indexeur par défaut acceptant les arguments <>
Mais était : "pas trouvé"


Given the assertion:
```c#
Assert.That(tester, Has.Item("wow").EqualTo(1));

Il produit les messages :

Default indexer accepting arguments < "wow" > was not found on NUnit.Framework.Constraints.IndexerConstraintTests+NamedIndexTester.

Celui-ci est généré à l'aide de la fonction MsgUtils.FormatCollection .

Donc, correspond également plus étroitement aux messages de propriété, avec les détails ajoutés.

J'aime beaucoup le deuxième cas. Le premier cas me semble toujours sémantiquement faux car il affiche les types d'arguments au lieu des valeurs d'arguments. Nous n'avons que des valeurs d'argument, et chaque valeur d'argument peut correspondre à une plage de types d'arguments dans l'indexeur. Il est trompeur de donner l'impression qu'il doit y avoir un indexeur avec un argument System.String car un argument IEnumerable<char> fonctionnerait tout aussi bien.

IEnumerable<char> est un cas d'utilisation farfelu... mais bien sûr, j'ai compris.

Donc, revenons à - étant donné :
```c#
Assert.That(testeur, Has.Item("wow"));


producers the message:

Attendu : indexeur par défaut acceptant les arguments < "wow" >
Mais était : "pas trouvé"


For numbers it goes into the usual suffix - given:
```c#
Assert.That(tester, Has.Item(42d));

producteurs :

  Expected: Default indexer accepting arguments < 42.0d >
  But was:  "not found"

Le message « attendu » me paraît alors très bien. Le message But was: "not found" ressemble un peu à une instance de chaîne avec le contenu not found , mais si PropertyConstraint se comporte de la même manière, il est préférable pour moi de les garder identiques. La modification de PropertyConstraint serait hors de portée pour ce PR par défaut. Nous pourrions toujours apporter un changement si vous le vouliez et moi-même et quelqu'un d'autre dans l'équipe du cadre avons approuvé le changement.

Oui, j'essayais de ne pas le faire, mais je n'ai pas trouvé de solution avec le message d'erreur. J'aurais aimé que ce soit juste :

But was: not found

mais le retour de ConstraintResult met les guillemets de chaîne là-dedans.

Pour référence la version de la propriété :
```c#
Assert.That(testeur, Has.Property("NoItDoesNotHaveThis"));

produces:

Attendu : propriété NoItDoesNotHaveThis
Mais était:
```
Il n'y a donc pas de guillemets - mais il renvoie le type - pas une valeur.

Je ne savais pas si retourner une surcharge de ConstraintResult serait une solution ?

Je pense que nous devrions nous en tenir à ce que PropertyConstraint fait pour la cohérence. Afficher la représentation par défaut de la actual n'est pas la pire chose lorsque le message indiquant que ce qui a échoué était de trouver la propriété.

Le retour d'une nouvelle classe dérivée de ConstraintResult est la façon dont d'autres contraintes personnalisent cela, oui.

Ok, super - on dirait - étant donné :
```c#
Assert.That(testeur, Has.Item(42d));


producers:

Attendu : indexeur par défaut acceptant les arguments < 42.0d >
Mais était:
```

Cela correspond au message de contrainte de propriété.

@nunit/framework-team et tout le monde, j'espère une résolution rapide sur cette question puisque l'excellent PR de @Poimen (https://github.com/nunit/nunit/pull/3609) est prêt à fusionner autrement.

@Dreamescaper a attiré mon attention sur mon inquiétude plus haut dans ce fil dont j'ai perdu la trace : j'ai peur que les gens écrivent et liront à la fois Assert.That(collection, Has.Item("x")); pensant que Has.Item signifie Contains.Item .
Nous pourrions envisager d'ajouter quelque chose au message d'échec, mais cela n'aidera pas à le comprendre correctement lors de la lecture de Has.Item dans le code source :

  Expected: Default indexer accepting arguments < 42.0d > (Did you mean to use Contains.Item?)

Par contre, je n'aime pas Has.Index(42) ou Has.Index(42).EqualTo(43) ce que j'avais suggéré au lieu de Item . L'instance a un élément à un index. Vous ne diriez pas que l'instance a un index et que l'index lui-même est égal à 43. L'index est ce que vous transmettez, 42.

Certaines des options

  • Utilisez Has.Item . Abandonnez les problèmes que cela pourrait causer lors de la lecture du code source. Peut-être ajouter quelques atténuations pour aider lors de l'écriture de code source, comme des documents XML et des conseils dans les messages d'échec.
  • Utilisez Has.Index malgré le mélange maladroit de terminologie.
  • Utilisez plutôt Has.ItemAt . Has.ItemAt(42).EqualTo(43) .

Attendez, je viens de tomber amoureux de la dernière suggestion car elle a un parallèle avec la méthode LINQ ElementAt qui reçoit un index et renvoie un élément.

Est-ce que tout le monde est content de Has.ItemAt ? Les suggestions sont les bienvenues mais encore une fois j'aimerais être rapide pour respecter le temps que @Poimen a déjà consacré.

+1 pour Has.ItemAt :)

Je suis d'accord avec vous que Has.Item est potentiellement déroutant. J'aime Has.ItemAt mais puisque vous aimez la syntaxe LINQ, pourquoi ne pas utiliser Has.ElementAt ?

Je ne suis pas sûr d'ElementAt, car ce n'est pas un équivalent direct.
Par exemple, pour Dictionary<string,string> , ElementAt(0)

@CharliePoole C'est une très bonne idée. Il semble que ce ne sera pas aussi intuitif à trouver, mais je ne sais pas pourquoi je pense cela. Je pense que je préfère Item parce que nous appellerons un indexeur sur vraisemblablement une collection/dictionnaire/recherche quelconque, alors que ElementAt est destiné à fonctionner dans des requêtes et autres énumérables qui ne sont pas nécessairement des collections . Les éléments de collection sont généralement appelés éléments.

@Dreamescaper Pensez-vous que Has.ItemAt(4) pourrait faire croire aux gens que cela fonctionne comme ElementAt , en énumérant plutôt qu'en appelant un indexeur ?

@jnm2
Cela pourrait probablement le faire, mais je ne connais pas de meilleure option (et je pense que c'est toujours une bien meilleure option que Has.Item).

@jnm2 Je suis en fait un peu confus. Ce numéro a commencé par porter sur les propriétés et porte toujours ce titre. Comment Has.Property et Has.ItemAt vont-ils se rapporter, interagir ou se comparer aux yeux de l'utilisateur ?

[Je me rends compte que les propriétés et les éléments de collection peuvent être traités comme équivalents à un certain niveau d'abstraction, mais je ne pense pas que ce soit le niveau d'abstraction auquel la plupart d'entre nous fonctionnent habituellement. :smileing_imp: ]

En tant que chose NUnit 4.0, il serait peut-être bien de passer en revue la façon dont les termes sont utilisés dans l'API et de nettoyer un peu. Le mot « membre » est souvent utilisé comme équivalent à « élément », mais peut également signifier une propriété, une méthode ou une variable.

Je me sens plus à l'aise avec ItemAt sur ElementAt partie parce qu'il peut sembler que nous déléguons ou adoptons la même approche particulière que Enumerable.ElementAt . Ce qui m'a fortement frappé avec ElementAt(int index) était moins les détails de son fonctionnement et plus le fait qu'il existe un précédent pour un suffixe At dans la BCL, en particulier associé à un paramètre nommé index .

Si vous l'utilisez avec un ILookup, chaque index d'un ILookup renvoie plusieurs TElements, pas un seul élément/élément. Has.EntryAt pourrait être quelque chose qui fonctionne avec les collections, les dictionnaires et les recherches à plusieurs éléments par index. Ma première réaction est que Has.ItemAt qui renvoie IEnumerable<TElement> partir d'un ILookup est probablement assez compréhensible .

Qu'en pensez-vous tous ? C'est difficile quand vous pouvez voir plus d'une chose fonctionner, mais la préférence de la majorité ici pourrait également aider à indiquer quel nom serait le plus facile à trouver pour les gens. Les gens en plus de @nunit/framework-team, je ne voulais pas vous exclure. Si vous regardez et que vous avez un sentiment fort, c'est un point de données qui est toujours utile !

@CharliePoole Has.Property("X") affirme que l'instance a une propriété non paramétrée appelée X . Has.ItemAt("X") affirmerait que l'instance a un indexeur par défaut (une propriété paramétrée avec un nom non pertinent et bien connu) qui peut accepter un paramètre de chaîne.

Has.Property("X").EqualTo(3) affirme que l'instance a une propriété non paramétrée appelée X qui renvoie 3 si vous appelez son accesseur non paramétré get . Has.ItemAt("X").EqualTo(3) affirmerait que l'instance a un indexeur par défaut (une propriété paramétrée avec un nom non pertinent et bien connu) qui renvoie 3 si vous passez la chaîne spécifique "X" à son get accessoire.

Ce qui rend le nom de l'indexeur par défaut non pertinent, c'est que les syntaxes C#, VB et F# peuvent toutes appeler des indexeurs par défaut sans spécifier de nom. (D'où le terme 'default.') Un langage qui n'avait pas de concept d'indexeurs par défaut mais qui avait un concept d'indexeurs nommés devrait spécifier le nom. Le nom de l'indexeur par défaut est généralement Item , mais l'indexeur par défaut peut être nommé arbitrairement et toujours être l'indexeur par défaut car ces langages pourraient toujours l'appeler sans spécifier de nom. En C#, vous accompliriez cela en utilisant l'attribut [IndexerName("ArbitraryName")] .

@jnm2 Je comprends en fait tout le langage C# etc. sur les indexeurs... cependant...

Je pense qu'il y a un risque de confusion entre la propriété

  • __étant__ un indexeur de l'objet réel
  • __retournant__ un objet qui a un indexeur
  • __retourner__ une collection contenant des éléments

Je souligne que __vous__ ne sera pas confus et je ferai probablement semblant de l'être en parlant avec vous (méthode socratique, vous savez :wink: ) mais je soupçonne que beaucoup peuvent trouver cela déroutant et vous l'expliquerez aux gens beaucoup.

Je ne suis pas contre l'ajout mais je pense que les distinctions ci-dessus devraient être précisées dans la doc.

PS : je ne me suis pas du tout senti exclu. :souriant:

@CharliePoole Bonne pensée. Pouvons-nous reporter cette considération jusqu'à ce que nous décidions d'un nom, puisqu'il semble que cela n'affectera pas le choix entre Has.ItemAt / Has.ElementAt / Has.EntryAt /etc ? Ou y a-t-il un point que j'ai manqué et que vous soutenez un choix de nom particulier basé sur ce potentiel de confusion ?

Jusqu'à présent, je penche pour ItemAt . @Dreamescaper , est-ce toujours là que vous êtes aussi ? @CharliePoole Je ne savais pas si vous ElementAt ou si vous nous aidiez simplement à réfléchir au processus.

Plus les gens interviendront sur Has.ItemAt / Has.ElementAt / Has.EntryAt /autre, plus nous pourrons avoir confiance en ce que les gens trouveront intuitif. J'aimerais juste l'appeler pour celui qui semble être en tête dans les prochains jours, car nous manquerons la version 3.13 d'une si petite quantité si nous prenons beaucoup plus de temps que cela.

@jnm2 J'essaie juste de réfléchir par moi-même et d'encourager la même chose.

WRT le choix des mots, je me demandais si une formulation particulière fonctionnerait mieux avec les trois types d'expressions que j'ai énumérés et aussi s'il y avait d'autres combinaisons à craindre. Avec les trois options que j'ai mentionnées, vous auriez

  • Has.ItemAt("foo").EqualTo("bar")
  • `A.Propriété("Bing").Avec.ItemAt("foo").EqualTo("bar")
  • `A.Propriété("Bing").Avec.Un.ÉgalÀ("bar")

Donc je suppose que vous avez raison que le libellé n'a pas d'importance pour les options

À plus long terme, je préfère utiliser une indexation réelle avec des crochets. Mais cela nécessiterait de compiler l'expression de la contrainte plutôt que de la modéliser comme un ensemble de classes.

Je sais que c'est au-dessus de mon niveau de rémunération :sourire:, mais j'ai pensé que je sonnerais ici...

Je prends goût à ItemAt .

J'ai passé du temps, comme @CharliePoole l'a suggéré, à réfléchir à certains cas d'utilisation. J'ai pensé que ElementAt était plus utilisé dans les situations IEnumerable et que les indexeurs de langage pouvaient gérer plus que IEnumerable . Je me suis assis sur la clôture ElementAt pendant un moment, puis j'ai réfléchi à :
```c#
Assert.Cela(..., Has.ElementAt("un", "deux"));

against:
```c#
Assert.That(..., Has.ItemAt("one", "two"));

ItemAt dans ce qui précède semble plus naturel pour exprimer plusieurs indexeurs de valeurs.

J'ai également considéré que le billet destiné à l'origine à un court-circuit pour une contrainte de propriété et ItemAt correspond à ce récit. (Cependant, cela ne l'empêche pas de se transformer en quelque chose de plus/mieux/etc, juste un point de vue).

Je préfère aussi ItemAt , alors appelons ça une majorité et décidons en faveur de ça 😺

Pour éviter toute confusion, assurons-nous que cela est documenté rapidement après l'achèvement et inclus dans les notes de version.

@Poimen Re: votre niveau de rémunération, ne sous-estimez pas à quel point nous inventons tous des choses au fur et à mesure que nous avançons ici. Merci d'avoir partagé vos avis!

Et en ce qui concerne le niveau de rémunération, puisque tout le monde ici gagne un salaire annuel d'environ 0 $ en travaillant sur NUnit, je pense que nous sommes tous au même niveau de rémunération 😝

@Dreamescaper a posé une autre bonne question dans le PR. Qu'il soit positif ou négatif, cela revient à vérifier le nombre plutôt que de vérifier la présence d'un indexeur :

Assert.That(new[] { 1, 2, 2 }, Has.ItemAt(3));
Assert.That(new[] { 1, 2, 2 }, Has.No.ItemAt(3));

Il semble plus sûr de supprimer la contrainte Exists jusqu'à ce que nous ayons plus de temps pour y réfléchir. Je préférerais ouvrir un numéro distinct pour Has.Indexer(typeof(Type1), typeof(Type2), ...) pour une vérification d'existence de l'indexeur, et Has.ItemAt(arg1, arg2, ...) pour exécuter l'indexeur et affirmer des choses sur la valeur résultante.

Quelqu'un s'opposerait-il à l'envoi initial de Has.ItemAt(3) comme ne résolvant pas une contrainte et de Has.ItemAt(3).EqualTo(4) comme résolvant ?

Has.Indexer sens pour moi. Je suppose que vous prendriez également en charge Has.Indexer<T>() , Has.Indexer<T1, T2>() , etc. pour assurer la cohérence avec d'autres contraintes.

Merci pour les commentaires! J'ai donné le feu vert à la pull request pour que Has.ItemAt(...) ne soit plus autorésolu, et j'ai déposé https://github.com/nunit/nunit/issues/3690 pour suivre Has.Indexer pour fournir cette capacité.

Cette page vous a été utile?
0 / 5 - 0 notes