Nunit: Solicitud: agregar compatibilidad con indexadores a PropertyConstraint

Creado en 2 jun. 2017  ·  46Comentarios  ·  Fuente: nunit/nunit

Por el momento, solo es posible probar los valores de las propiedades normales y no hay forma de probar los valores del indexador. Necesito escribir una prueba que se parezca a esto:

`` c #
[Accesorio de prueba]
clase pública TestClass
{
ejemplo de clase
{
diccionario privadodictionary = nuevo diccionario();

  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

Comentario más útil

Y en cuanto al grado de pago, dado que todos aquí ganan un salario anual de aproximadamente $ 0 trabajando en NUnit, creo que todos tenemos el mismo grado de pago 😝

Todos 46 comentarios

Me gusta la idea.

C # no admite propiedades indexadas con nombre, pero VB.NET sí. Creo que la sintaxis .Property("Name", index1, ...) debería reservarse para indexadores con nombre, por lo que no me gustaría ver que se use para un indexador predeterminado. Me gustaría poder respetar el lenguaje C # al no requerir que las personas pasen un parámetro de nombre.

¿Qué opinas de Has.Item("TEST1").EqualTo("1") o Has.Index("TEST1").EqualTo("1") ?

@ jnm2
Tener el método "Elemento" o "Indexador" como acceso directo es conveniente, pero en realidad es posible cambiar el nombre del indexador en C #: https://stackoverflow.com/questions/5110403/class-with-indexer-and-property-named -Articulo

@ Ch0senOne Es posible cambiar el nombre. Para ser más claro, debería haber dicho que no es posible hacer un indexador no predeterminado en C #. Por ejemplo, no puede declarar dos indexadores, ni puede consumir el nombre de C #.
Pensándolo bien, me temo que Has.Item("value") sería demasiado confuso con Contains.Item . Entonces, ¿quizás Has.Index("value") ?

Así que estaría a favor de Has.Index("IndexValue") para el indexador predeterminado y Has.Property("PropertyName", indexParams) para los indexadores no predeterminados. Has.Property terminaría funcionando también para indexadores predeterminados, aunque la cosa idiomática de C # sería usar Has.Index .


O si quisiéramos ser lindos, podríamos hacer Has.Index["IndexValue"] y
Has.Property("PropertyName")[indexparams] ... 😁

En realidad, me pregunto si podríamos salirse con la nuestra con Has.Item["AtIndex"] usando la sintaxis del indexador para evitar ser confundidos con Contains.Item("ItemValue") ... ¡no estoy seguro de lo que pienso sobre eso!


Veamos qué piensan los otros miembros del equipo de @ nunit / framework-team.

Ya definimos una dirección para seguir las propiedades usando lambdas en lugar de cadenas. Parece que sería mejor gastar el tiempo en eso. Estoy en mi teléfono, así que no puedo darte el número de problema.

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

Eso tiene sentido para Has.Property . Podríamos hacer:

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

Sigo pensando que no deberíamos hacer nada con Has.Property hasta que se necesiten propiedades no predeterminadas, y por ahora solo proporcione Has.Index(value1, ...) o Has.Index[value1, ...] o Has.Item[value1, ...] para el indexadores predeterminados mucho más comunes.

Ese es. Estoy de acuerdo con usted sobre indexadores con nombre, que de todos modos no se han solicitado.

@ jnm2 Para su consideración, también puede hacer un indexador de varias claves. Por ejemplo

this[string key1, string key2]

Me pregunto si hay alguna actualización sobre este problema o la forma actual de probar los indexadores en un tipo que no hereda IEnumerable. Supongo que podría verificar si se lanza una KeyNotFoundException. ¿Algo más que esté pasando por alto que me permita verificar la existencia de una clave en un indexador?

@ crush83 Sí, mi sintaxis index1, ... estaba destinada a indicar que deberíamos considerarlos en todo lo que hagamos.

No me gusta la clave terminológica a menos que estemos hablando de un objeto que en realidad es un diccionario o una colección con clave. Esas son cosas con indexadores predeterminados, pero no todas las cosas con indexadores predeterminados tienen claves. (Dado que está utilizando esta terminología, tal vez encuentre útil nuestra sintaxis Contains.Key ? ¿Podemos mejorarla?)

No ha habido ningún progreso más que lo que ves aquí. Necesitamos una propuesta. Para publicar algo, mi favorito actual es Has.Item(params object[] indexerArgs) que llamaría al indexador predeterminado sin importar cuál sea su nombre.
¿Haría esto todo lo solicitado hasta ahora?

+1

@ nunit / framework-team ¿Apoyaría la siguiente API nueva?

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

Probablemente dejaría la clase IndexerConstraint interna hasta que haya una razón para agregarla a la API.

Suena bien para mí.

Otro voto a favor, solo quería esta función para una prueba de consola. 🙂

@ jnm2 ¿

Tuve una revisión rápida del problema y, como no entiendo exactamente, quiero que se vea la API compatible, ¿es así:

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

O es eso:

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

?

Su ejemplo toma params object[] así que iba con la opción 2.

Suponiendo opt. 2, ¿se requeriría la matriz como si solo se proporcionara un objeto, afirmar que la clave existe, 2 parámetros: clave y valor? Otras combinaciones (parámetros más o menos esperados), ¿fallan?

@Poimen Sí, ¡y agradeceríamos la ayuda!

La matriz es necesaria porque los indexadores pueden aceptar más de un argumento. Por ejemplo:

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

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

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

Usaría Has.Item(42).EqualTo("First indexer") , Has.Item("42").EqualTo("Second indexer") y Has.Item(1, 2).EqualTo("Third indexer") .

Mira, aprendes algo nuevo todos los días; había asumido que era un error tipográfico, oops: see_no_evil: no he tenido el placer de usar un indexador de múltiples claves antes ...

Ok, genial, buscaré armar algo esta semana.

@ jnm2 He creado un PR para este problema.

La única preocupación que tengo es el mensaje de error que produce el código:

Has.Item(42)

El mensaje de error es un poco "hacky", pero no estoy seguro de cuál es la forma más limpia de resolverlo. El "truco" es:
https://github.com/nunit/nunit/pull/3609/commits/96154ca6402c692cec5e0ae6947f9922e72799fe#diff -ceeee4b8399633f181b6bccd5a39eb32R72

@Poimen Lo siento, no estoy seguro de lo que quieres decir. ¿Cuál es el mensaje actualmente y cuál le gustaría que fuera? ¿O estás buscando sugerencias?

@ jnm2 El mensaje de error que aparece es:

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

La forma en que se forman los mensajes de error StringifyArguments función es un poco "hacky".

Entonces, mi pregunta mal expresada es más si había una forma más limpia de producir la salida de mensaje deseada.

@ jnm2 Creo que he abordado los comentarios sobre la revisión (gracias por ellos). La etiqueta dice awaiting:contributor .

No estoy seguro de si hay algo más que deba hacer para que sepas que he "terminado": +1:

Creo que deberíamos acercarnos lo más posible al mensaje que se muestra cuando Has.Property no puede encontrar una propiedad. Entonces deberíamos reemplazar property con default indexer y reemplazar el nombre de la propiedad con accepting arguments [42] o algo así. Me inclino a pensar que deberíamos usar uno de los ayudantes de formato existentes como MsgUtils.FormatCollection para mostrar los valores de los argumentos en lugar de enumerar los tipos de argumentos. Por ejemplo, uno de los argumentos puede ser nulo y funcionar con una variedad de tipos.

¡No, eso es genial! Un comentario como ese en el hilo de relaciones públicas sería ideal.

Dada la afirmación:
`` c #
Afirmar.Eso (probador, Has.Item ("guau"));


It producers the message:

Se esperaba: indexador predeterminado que acepta argumentos <>
Pero fue: "no encontrado"


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

Produce los mensajes:

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

Esto se generó usando la función MsgUtils.FormatCollection .

Por lo tanto, también coincide más con los mensajes de propiedad, con los detalles agregados.

Me gusta mucho el segundo caso. El primer caso todavía me parece semánticamente incorrecto porque muestra tipos de argumentos en lugar de valores de argumentos. Solo tenemos valores de argumento, y cada valor de argumento puede coincidir con un rango de tipos de argumento en el indexador. Es engañoso hacer que parezca que debe haber un indexador con un argumento System.String porque un argumento IEnumerable<char> funcionaría igual de bien.

IEnumerable<char> es un caso de uso loco ... pero claro, entiendo el punto.

Entonces, volviendo a - dado:
`` c #
Afirmar.Eso (probador, Has.Item ("guau"));


producers the message:

Se esperaba: indexador predeterminado que acepta argumentos <"wow">
Pero fue: "no encontrado"


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

productores:

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

El mensaje 'Esperado' me parece genial. El mensaje But was: "not found" parece un poco a una instancia de cadena con el contenido not found , pero si PropertyConstraint se comporta de la misma manera, es preferible para mí mantenerlos iguales. Cambiar PropertyConstraint estaría fuera del alcance de este RP de forma predeterminada. Aún podríamos hacer un cambio si quisieras y yo y alguien más en el equipo marco aprobamos el cambio.

Sí, estaba tratando de que no hiciera esto, pero no pude encontrar una solución con el mensaje de error. Me hubiera gustado que solo fuera:

But was: not found

pero el retorno de ConstraintResult está poniendo la cadena entre comillas allí.

Para referencia, la versión de la propiedad:
`` c #
Assert.That (tester, Has.Property ("NoItDoesNotHaveThis"));

produces:

Se esperaba: propiedad NoItDoesNotHaveThis
Pero fue:
''
Por lo tanto, no hay comillas, pero devuelve el tipo, no un valor.

¿No estaba seguro de si devolver una sobrecarga de ConstraintResult sería una solución?

Creo que deberíamos seguir con lo que PropertyConstraint está haciendo para mantener la coherencia. Mostrar la representación predeterminada de cuál es el actual no es lo peor cuando el mensaje dice que lo que falló fue encontrar la propiedad.

Devolver una nueva clase derivada de ConstraintResult es cómo otras restricciones personalizan esto, sí.

Ok, genial - suena como - dado:
`` c #
Afirmar eso (probador, Has.Item (42d));


producers:

Se esperaba: indexador predeterminado que acepta argumentos <42.0d>
Pero fue:
''

Esto coincide con el mensaje de restricción de propiedad.

@ nunit / framework-team y todos, espero una resolución rápida sobre esta pregunta, ya que el excelente PR de @Poimen (https://github.com/nunit/nunit/pull/3609) está listo para fusionarse de lo contrario.

@Dreamescaper me llamó la atención sobre mi preocupación más arriba en este hilo del que perdí la pista: me temo que la gente escribirá y leerá Assert.That(collection, Has.Item("x")); pensando que Has.Item significa Contains.Item .
Podríamos considerar agregar algo al mensaje de falla, pero eso no ayudará con el problema de entenderlo correctamente al leer Has.Item en el código fuente:

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

Por otro lado, no me gusta Has.Index(42) o Has.Index(42).EqualTo(43) que es lo que había sugerido en lugar de Item . La instancia tiene un elemento en un índice. No diría que la instancia tiene un índice y que el índice en sí es igual a 43. El índice es lo que ingresa, 42.

Algunas de las opciones

  • Utilice Has.Item . Renuncie a los problemas que podría causar al leer el código fuente. Tal vez agregue algunas mitigaciones para ayudar al escribir código fuente, como documentos XML y sugerencias en mensajes de falla.
  • Use Has.Index pesar de la incómoda mezcla de terminología.
  • Utilice Has.ItemAt lugar. Has.ItemAt(42).EqualTo(43) .

Espera, me enamoré de la última sugerencia ya que tiene un paralelo con el método LINQ ElementAt al que se le da un índice y devuelve un elemento.

¿Están todos contentos con Has.ItemAt ? Las sugerencias son bienvenidas, pero nuevamente me gustaría ser rápido para respetar el tiempo que @Poimen ya ha dedicado.

+1 por Has.ItemAt :)

Estoy de acuerdo contigo en que Has.Item es potencialmente confuso. Me gusta Has.ItemAt pero como te gusta la sintaxis LINQ, ¿por qué no usar Has.ElementAt ?

No estoy seguro de ElementAt, ya que no es un equivalente directo.
Por ejemplo, para Dictionary<string,string> LINQ, ElementAt (0) devolverá el primer par y Has.ItemTo (0) .EqualTo ("...") fallará, ya que no hay un indexador que acepte enteros.

@CharliePoole Esa es una muy buena idea. Parece que no será tan intuitivo de encontrar, pero no sé por qué pienso eso. Creo que prefiero Item porque llamaremos a un indexador presumiblemente en una colección / diccionario / búsqueda de algún tipo, mientras que ElementAt está destinado a funcionar en consultas y otros enumerables que no son necesariamente colecciones . Los elementos de la colección generalmente se denominan artículos.

@Dreamescaper ¿Crees que Has.ItemAt(4) podría hacer que la gente piense que funciona igual que ElementAt , enumerando en lugar de llamar a un indexador?

@ jnm2
Probablemente podría, pero no conozco una mejor opción (y creo que sigue siendo una opción mucho mejor que Has.Item).

@ jnm2 En realidad estoy un poco confundido. Este problema comenzó siendo sobre propiedades y todavía se titula. ¿Cómo se relacionarán, interactuarán o compararán Has.Property y Has.ItemAt entre sí a los ojos del usuario?

[Me doy cuenta de que las propiedades y los elementos de colección pueden tratarse como equivalentes en un cierto nivel de abstracción, pero no creo que sea el nivel de abstracción en el que la mayoría de nosotros operamos habitualmente. : smile_imp:]

Como cosa de NUnit 4.0, sería bueno repasar cómo se usan los términos en la API y limpiar un poco. La palabra "miembro" es una que se usa a menudo como equivalente a "Elemento", pero también puede significar propiedad, método o variable.

Me siento más cómodo con ItemAt sobre ElementAt parte porque podría parecer que delegamos o adoptamos el mismo enfoque particular que Enumerable.ElementAt . Lo que me sorprendió mucho con ElementAt(int index) fue menos los detalles de cómo funcionaba y más el hecho de que hay un precedente para los sufijos At en el BCL, especialmente asociado con un parámetro llamado index .

Si usa esto con un ILookup, cada índice de un ILookup devuelve múltiples TElements, no un solo elemento / elemento. Has.EntryAt podría ser algo que funcione con colecciones, diccionarios y búsquedas de varios elementos por índice. Mi reacción inicial es que Has.ItemAt que devuelve IEnumerable<TElement> de un ILookup es probablemente lo suficientemente comprensible.

¿Qué piensan todos ustedes? Es difícil cuando puede ver que funciona más de una cosa, pero la preferencia de la mayoría aquí también podría ayudar a indicar qué nombre sería más fácil de encontrar para las personas. Personas además de @ nunit / framework-team, no quise excluirlos. Si estás mirando y tienes un sentimiento fuerte, ¡ese es un punto de datos que siempre es útil!

@CharliePoole Has.Property("X") afirma que la instancia tiene una propiedad no parametrizada llamada X . Has.ItemAt("X") afirmaría que la instancia tiene un indexador predeterminado (una propiedad parametrizada con un nombre conocido e irrelevante) que puede aceptar un parámetro de cadena.

Has.Property("X").EqualTo(3) afirma que la instancia tiene una propiedad no parametrizada llamada X que devuelve 3 si llama a su descriptor de acceso get no parametrizado. Has.ItemAt("X").EqualTo(3) afirmaría que la instancia tiene un indexador predeterminado (una propiedad parametrizada con un nombre conocido e irrelevante) que devuelve 3 si pasa la cadena específica "X" a su get accesor.

Lo que hace que el nombre del indexador predeterminado sea irrelevante es que las sintaxis de C #, VB y F # pueden llamar a los indexadores predeterminados sin especificar un nombre. (De ahí el término 'predeterminado'). Un lenguaje que no tuviera un concepto de indexadores predeterminados pero que tuviera un concepto de indexadores con nombre tendría que especificar el nombre. El nombre del indexador predeterminado suele ser Item , pero el indexador predeterminado puede tener un nombre arbitrario y seguir siendo el indexador predeterminado porque esos idiomas aún podrían llamarlo sin especificar un nombre. En C #, lograría esto usando el atributo [IndexerName("ArbitraryName")] .

@ jnm2 De hecho, entiendo todas las cosas del lenguaje C #,

Creo que existe la posibilidad de confusión entre la propiedad

  • __ser__ un indexador del objeto real
  • __returning__ un objeto que tiene un indexador
  • __returning__ una colección con elementos en ella

Hago hincapié en que __ usted__ no estará confundido y probablemente solo fingiré estarlo cuando hable con usted (método socrático, ya sabe: guiño:) pero sospecho que muchos lo encontrarán confuso y usted se lo estará explicando a la gente. bastante.

No estoy en contra de la adición, pero creo que las distinciones anteriores deberían especificarse en los documentos.

PD: No me sentí excluido en absoluto. : smiley:

@CharliePoole Buen pensamiento. ¿Podemos presentar esa consideración hasta que establezcamos un nombre, ya que parece que no afectará la elección entre Has.ItemAt / Has.ElementAt / Has.EntryAt / etc? ¿O hay un punto que me perdí y estás apoyando una elección de nombre en particular basada en este potencial de confusión?

Hasta ahora me estoy inclinando hacia ItemAt . @Dreamescaper , ¿es ahí donde tú también estás? @CharliePoole No estaba seguro si estaba expresando una preferencia por ElementAt o simplemente ayudándonos a pensar en el proceso.

Cuantas más personas intervengan en Has.ItemAt / Has.ElementAt / Has.EntryAt / otro, más confianza tendremos en lo que la gente encontrará intuitivo. Me gustaría simplemente llamarlo por lo que parezca estar a la cabeza en el próximo día más o menos, ya que perderemos la versión 3.13 por una cantidad tan pequeña si demoramos mucho más que eso.

@ jnm2 Solo trato de pensarlo a través de mí mismo y

WRT la elección de las palabras, me preguntaba si alguna redacción en particular funcionará mejor con los tres tipos de expresiones que enumeré y también si hay otras combinaciones de las que preocuparse. Con las tres opciones que mencioné, tendrías

  • Has.ItemAt("foo").EqualTo("bar")
  • `Has.Property (" Bing "). With.ItemAt (" foo "). EqualTo (" bar ")
  • `Has.Property (" Bing "). With.One.EqualTo (" barra ")

Así que supongo que tienes razón en que la redacción no importa para ninguna opción.

Mirando hacia el largo plazo, prefiero usar la indexación real con corchetes. Pero eso requeriría realmente compilar la expresión de restricción en lugar de modelar como un conjunto de clases.

Sé que esto está por encima de mi nivel de pago: sonrisa :, pero pensé en intervenir aquí ...

Me gusta ItemAt .

Pasé tiempo, como sugirió @CharliePoole , pensando en algunos casos de uso. Llegué a la idea de que ElementAt se usa más en situaciones de IEnumerable y los indexadores de idiomas pueden manejar más que solo IEnumerable . Me senté en la cerca ElementAt por un tiempo, luego consideré:
`` c #
Afirmar que (..., Has.ElementAt ("uno", "dos"));

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

ItemAt en lo anterior se siente más natural para expresar índices de valores múltiples.

También consideré que el boleto originalmente destinado a un cortocircuito por una restricción de propiedad y ItemAt encaja con esa narrativa. (Sin embargo, esto no impide que se convierta en algo más / mejor / etc, solo un punto de vista).

También prefiero ItemAt , así que llamémoslo mayoría y dictaminemos a favor de eso 😺

Para evitar confusiones, asegurémonos de que esto se documente rápidamente una vez finalizado y se incluya en las notas de la versión.

@Poimen Re: su nivel salarial, no subestime el grado en que todos estamos inventando cosas a medida que avanzamos aquí. ¡Gracias por compartir tus pensamientos!

Y en cuanto al grado de pago, dado que todos aquí ganan un salario anual de aproximadamente $ 0 trabajando en NUnit, creo que todos tenemos el mismo grado de pago 😝

@Dreamescaper hizo otra buena pregunta en las relaciones públicas. Ya sea positivo o negativo, esto sería verificar el recuento en lugar de verificar la presencia de un indexador:

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

Parece más seguro eliminar la restricción Exists hasta que tengamos más tiempo para pensar en ello. Preferiría abrir una edición separada para Has.Indexer(typeof(Type1), typeof(Type2), ...) para una verificación de existencia del indexador, y Has.ItemAt(arg1, arg2, ...) para ejecutar realmente el indexador y afirmar cosas sobre el valor resultante.

¿Alguien se opondría al envío inicial de Has.ItemAt(3) por no resolverse a una restricción y Has.ItemAt(3).EqualTo(4) por resolverse?

Has.Indexer tiene sentido para mí. Supongo que también apoyaría Has.Indexer<T>() , Has.Indexer<T1, T2>() , etc. para mantener la coherencia con otras restricciones.

¡Gracias por la respuesta! Di el visto bueno a la solicitud de extracción para hacer que Has.ItemAt (...) ya no se resuelva por sí mismo, y presenté https://github.com/nunit/nunit/issues/3690 para rastrear Has.Indexer para proporcionar esa capacidad.

¿Fue útil esta página
0 / 5 - 0 calificaciones