Nunit: Solicitação: adicionar suporte de indexadores ao PropertyConstraint

Criado em 2 jun. 2017  ·  46Comentários  ·  Fonte: nunit/nunit

No momento, só é possível testar valores de propriedades normais e nenhuma forma de testar os valores do indexador. Preciso escrever um teste parecido com este:

`` `c #
[TestFixture]
public class TestClass
{
classe exemplo
{
dicionário privadodicionário = novo dicionário();

  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

Comentários muito úteis

E quanto ao nível salarial, uma vez que todos aqui estão ganhando um salário anual de cerca de US $ 0 trabalhando no NUnit, acho que estamos todos no mesmo nível salarial 😝

Todos 46 comentários

Eu gosto da ideia.

C # não oferece suporte a propriedades indexadas nomeadas, mas o VB.NET sim. Acho que a sintaxe .Property("Name", index1, ...) deve ser reservada para indexadores nomeados, portanto, não gostaria que fosse usada para um indexador padrão. Gostaria de poder respeitar a linguagem C #, não exigindo que as pessoas passem um parâmetro de nome.

O que você acha sobre Has.Item("TEST1").EqualTo("1") ou Has.Index("TEST1").EqualTo("1") ?

@ jnm2
Ter o método "Item" ou "Indexador" como atalho é conveniente, mas na verdade é possível alterar o nome do indexador em C #: https://stackoverflow.com/questions/5110403/class-with-indexer-and-property-named -item

@ Ch0senOne É possível alterar o nome. Para ser mais claro, eu deveria ter dito, não é possível fazer um indexador não padrão em C #. Por exemplo, você não pode declarar dois indexadores, nem pode consumir o nome do C #.
Pensando bem, temo que Has.Item("value") seria muito confundível com Contains.Item . Então, talvez Has.Index("value") ?

Portanto, eu seria a favor de Has.Index("IndexValue") para o indexador padrão e Has.Property("PropertyName", indexParams) para indexadores não padrão. Has.Property acabaria funcionando para indexadores padrão também, embora a coisa idiomática C # a fazer fosse usar Has.Index .


Ou se quiséssemos ser fofos, poderíamos fazer Has.Index["IndexValue"] e
Has.Property("PropertyName")[indexparams] ... 😁

Na verdade, eu me pergunto se poderíamos nos safar com Has.Item["AtIndex"] usando a sintaxe do indexador para evitar ser confundidos com Contains.Item("ItemValue") ... não tenho certeza do que penso sobre isso!


Vamos ver o que os outros membros da equipe @ nunit / framework-team pensam.

Já definimos uma direção para ir nas propriedades usando lambdas em vez de strings. Parece que seria melhor gastar tempo com isso. Estou ao telefone, por isso não posso dar-lhe o número do problema.

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

Isso faz sentido para Has.Property . Nós poderíamos fazer:

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

Ainda acho que não devemos fazer nada com Has.Property até que as propriedades não padrão sejam necessárias e, por enquanto, apenas forneça Has.Index(value1, ...) ou Has.Index[value1, ...] ou Has.Item[value1, ...] para o indexadores padrão muito mais comuns.

É esse mesmo. Eu concordo com você sobre indexadores nomeados, que não foram solicitados de qualquer maneira.

@ jnm2 Para sua consideração, você também pode fazer um indexador com várias

this[string key1, string key2]

Estou me perguntando se há alguma atualização sobre esse problema ou a forma atual de testar indexadores em um tipo que não herda IEnumerable. Suponho que eu poderia apenas verificar se há uma KeyNotFoundException sendo lançada. Mais alguma coisa que estou esquecendo para permitir que eu verifique a existência de uma chave em um indexador?

@ crush83 Sim, minha sintaxe index1, ... foi feita para indicar que devemos considerá-los tudo o que fazemos.

Não gosto da chave de terminologia, a menos que estejamos falando sobre um objeto que é na verdade um dicionário ou coleção com chave. Essas são coisas com indexadores padrão, mas nem todas as coisas com indexadores padrão têm chaves. (Já que você está usando essa terminologia, talvez ache nossa sintaxe Contains.Key útil? Podemos melhorá-la?)

Não houve nenhum progresso além do que você vê aqui. Precisamos de uma proposta. Para colocar algo lá fora, meu favorito atual é Has.Item(params object[] indexerArgs) que chamaria de indexador padrão, não importa qual seja o seu nome.
Isso faria tudo o que foi solicitado até agora?

+1

@ nunit / framework-team Você suportaria a nova API a seguir?

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

Eu provavelmente deixaria a classe IndexerConstraint interna até que haja um motivo para adicioná-la à API.

Parece bom para mim.

Outro voto a favor, eu só queria esse recurso para um teste de console. 🙂

@ jnm2 Este problema ainda é algo que a equipe deseja que seja feito?

Eu fiz uma análise rápida do problema e, como não entendo exatamente, se a API suportada deve ser parecida com - é:

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

ou é:

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

?

Seu exemplo leva params object[] então estava indo com a opção 2.

Assumindo opt. 2, a matriz seria necessária como se apenas um objeto fosse fornecido, afirmasse que a chave existe, 2 parâmetros - chave e valor? Outras combinações (parâmetros mais ou menos esperados), falham?

@Poimen Sim, e agradeceríamos a ajuda!

A matriz é necessária porque os indexadores podem ter mais de um argumento. Por exemplo:

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

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

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

Você usaria Has.Item(42).EqualTo("First indexer") , Has.Item("42").EqualTo("Second indexer") e Has.Item(1, 2).EqualTo("Third indexer") .

Veja, você aprende algo novo todos os dias - presumi que fosse um erro de digitação, opa: see_no_evil: Eu não tive o prazer de usar um indexador de várias chaves antes ...

Ok, legal, vou tentar montar algo esta semana.

@ jnm2 Eu criei um PR para este problema.

A única preocupação que tenho é a mensagem de erro produzida pelo código:

Has.Item(42)

A mensagem de erro é um pouco "hacky", mas não tenho certeza qual é a maneira mais limpa de resolver isso. O "hack" é:
https://github.com/nunit/nunit/pull/3609/commits/96154ca6402c692cec5e0ae6947f9922e72799fe#diff -ceeee4b8399633f181b6bccd5a39eb32R72

@Poimen Desculpe, não tenho certeza do que você quer dizer. Qual é a mensagem atual e qual você gostaria que fosse? Ou você está procurando sugestões?

@ jnm2 A mensagem de erro que aparece é:

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

A forma como as mensagens de erro são formadas StringifyArguments function é um pouco "hacky".

Portanto, minha pergunta mal formulada é mais se havia uma maneira mais limpa de produzir a saída de mensagem desejada.

@ jnm2 Acho que respondi aos comentários sobre a revisão (obrigado por eles). O rótulo diz awaiting:contributor .

Não tenho certeza se há algo mais que preciso fazer para que você saiba que estou "pronto": +1:

Acho que devemos torná-lo o mais próximo possível da mensagem que é mostrada quando Has.Property não consegue encontrar uma propriedade. Devemos então substituir property por default indexer e substituir o nome da propriedade por accepting arguments [42] ou algo parecido. Eu penso que devemos usar um dos auxiliares de formatação existentes, como MsgUtils.FormatCollection para mostrar os valores dos argumentos em vez de listar os tipos de argumentos. Por exemplo, um dos argumentos pode ser nulo e funcionar com uma variedade de tipos.

Não, isso é ótimo! Um comentário assim no próprio tópico de RP seria o ideal.

Dada a afirmação:
`` `c #
Assert.That (testador, Has.Item ("uau"));


It producers the message:

Esperado: indexador padrão aceitando argumentos <>
Mas foi: "não encontrado"


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

Produz as mensagens:

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

Isso é gerado usando a função MsgUtils.FormatCollection .

Portanto, também corresponde mais de perto as mensagens de propriedade, com os detalhes adicionados.

Gosto muito do segundo caso. O primeiro caso ainda parece semanticamente errado para mim porque mostra tipos de argumento em vez de valores de argumento. Temos apenas valores de argumento, e cada valor de argumento pode corresponder a uma variedade de tipos de argumento no indexador. É enganoso fazer parecer que deve haver um indexador com um argumento System.String porque um argumento IEnumerable<char> funcionaria da mesma forma.

IEnumerable<char> é um caso de uso maluco ... mas claro, entendi.

Então, de volta ao - dado:
`` `c #
Assert.That (testador, Has.Item ("uau"));


producers the message:

Esperado: indexador padrão aceitando argumentos <"wow">
Mas foi: "não encontrado"


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

produtores:

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

A mensagem 'Esperada' parece ótima para mim. A mensagem But was: "not found" parece um pouco como se estivesse falando sobre uma instância de string com o conteúdo not found , mas se PropertyConstraint se comportar da mesma maneira, mantê-los iguais é preferível para mim. Alterar PropertyConstraint estaria fora do escopo deste PR por padrão. Ainda poderíamos fazer uma alteração se você quisesse e eu e outra pessoa da equipe do framework aprovamos a alteração.

Sim, estava tentando fazer com que não acontecesse, mas não consegui encontrar uma solução com a mensagem de erro. Eu gostaria que fosse apenas:

But was: not found

mas o retorno de ConstraintResult está colocando as aspas da string lá.

Para referência, a versão da propriedade:
`` `c #
Assert.That (tester, Has.Property ("NoItDoesNotHaveThis"));

produces:

Esperado: propriedade NoItDoesNotHaveThis
Mas era:
`` `
Portanto, não há aspas - mas retorna o tipo - não um valor.

Eu não tinha certeza se retornar uma sobrecarga de ConstraintResult seria uma solução.

Acho que devemos seguir o que PropertyConstraint está fazendo para manter a consistência. Mostrar a representação padrão de qual é o actual não é a pior coisa quando a mensagem dizendo que o que falhou foi encontrar a propriedade.

Retornar uma nova classe derivada de ConstraintResult é como outras restrições personalizam isso, sim.

Ok, ótimo - parece - dado:
`` `c #
Assert.That (testador, Has.Item (42d));


producers:

Esperado: indexador padrão aceitando argumentos <42.0d>
Mas era:
`` `

Isso corresponde à mensagem de restrição da propriedade.

@ nunit / framework-team e todos, espero uma resolução rápida sobre essa questão, já que o excelente RP de @Poimen (https://github.com/nunit/nunit/pull/3609) está pronto para ser mesclado de outra forma.

@Dreamescaper chamou minha atenção para minha preocupação mais acima neste tópico que perdi de vista: tenho medo que as pessoas escrevam e leiam Assert.That(collection, Has.Item("x")); pensando que Has.Item significa Contains.Item .
Poderíamos considerar adicionar algo à mensagem de falha, mas isso não ajudará no problema de entendê-la corretamente ao ler Has.Item no código-fonte:

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

Por outro lado, não gosto de Has.Index(42) ou Has.Index(42).EqualTo(43) que foi o que sugeri em vez de Item . A instância possui um item em um índice. Você não diria que a instância tem um índice e que o próprio índice é igual a 43. O índice é o que você passa, 42.

Algumas das opções

  • Use Has.Item . Desista dos problemas que isso pode causar ao ler o código-fonte. Talvez adicione algumas atenuações para ajudar ao escrever o código-fonte, como documentos XML e dicas em mensagens de falha.
  • Use Has.Index apesar da mistura estranha de terminologia.
  • Use Has.ItemAt vez disso. Has.ItemAt(42).EqualTo(43) .

Espere aí, eu simplesmente me apaixonei pela última sugestão, pois ela tem um paralelo com o método LINQ ElementAt, que recebe um índice e retorna um item.

Todos estão felizes com Has.ItemAt ? Sugestões são bem-vindas, mas novamente eu gostaria de ser rápido em respeitar o tempo que @Poimen já dedicou.

+1 para Has.ItemAt :)

Eu concordo com você que Has.Item é potencialmente confuso. Eu gosto de Has.ItemAt mas como você gosta da sintaxe do LINQ, por que não usar Has.ElementAt ?

Não tenho certeza sobre ElementAt, pois não é um equivalente direto.
Por exemplo, para Dictionary<string,string> LINQ's ElementAt (0) retornará o primeiro par, e Has.ItemTo (0) .EqualTo ("...") irá falhar, pois não há indexador aceitando inteiros.

@CharliePoole Essa é uma ideia muito boa. Parece que não será tão intuitivo encontrar, mas não sei por que acho isso. Acho que prefiro Item porque chamaremos um indexador presumivelmente em uma coleção / dicionário / pesquisa de algum tipo, enquanto ElementAt se destina a trabalhar em consultas e outros enumeráveis ​​que não são necessariamente coleções . Os elementos da coleção são geralmente chamados de itens.

@Dreamescaper Você acha que Has.ItemAt(4) poderia fazer as pessoas pensarem que funciona exatamente como ElementAt , enumerando em vez de chamar um indexador?

@ jnm2
Provavelmente poderia, mas não conheço uma opção melhor (e acho que ainda é uma opção muito melhor do que Has.Item).

@ jnm2 Na verdade, estou um pouco confuso. Esta edição começou sendo sobre Propriedades e ainda assim é intitulada Como Has.Property e Has.ItemAt se relacionam, interagem ou se comparam aos olhos do usuário?

[Eu percebo que propriedades e itens de coleção podem ser tratados como equivalentes em um certo nível de abstração, mas não acho que seja o nível de abstração em que a maioria de nós normalmente opera. : smiling_imp:]

Como uma coisa do NUnit 4.0, pode ser bom ver como os termos são usados ​​na API e limpar um pouco. A palavra "membro" é freqüentemente usada como equivalente a "Item", mas também pode significar propriedade, método ou variável.

Eu me sinto mais confortável com ItemAt acima de ElementAt parte porque pode parecer que delegamos ou adotamos a mesma abordagem específica de Enumerable.ElementAt . O que me impressionou fortemente com ElementAt(int index) foi menos os detalhes de como funcionava e mais o fato de que há precedentes para sufixos At no BCL, especialmente associados a um parâmetro denominado index .

Se você usar isso com um ILookup, cada índice de um ILookup retorna vários TElements, não um único elemento / item. Has.EntryAt pode ser algo que funciona com coleções, dicionários e pesquisas de múltiplos elementos por índice. Minha reação inicial é que Has.ItemAt que os retornos IEnumerable<TElement> a partir de um iLookup é provavelmente o suficiente compreensível.

O que vocês acham? É difícil quando você vê mais de uma coisa funcionando, mas a preferência da maioria aqui também pode ajudar a indicar qual nome seria mais fácil de encontrar. Pessoas além de @ nunit / framework-team, não tive a intenção de excluí-los. Se você está assistindo e tem um sentimento forte, esse é um dado que é sempre útil!

@CharliePoole Has.Property("X") afirma que a instância tem uma propriedade não parametrizada chamada X . Has.ItemAt("X") afirmaria que a instância tem um indexador padrão (uma propriedade parametrizada com um nome irrelevante e conhecido) que pode aceitar um parâmetro de string.

Has.Property("X").EqualTo(3) afirma que a instância tem uma propriedade não parametrizada chamada X que retorna 3 se você chamar seu acessor get não parametrizado. Has.ItemAt("X").EqualTo(3) afirmaria que a instância tem um indexador padrão (uma propriedade parametrizada com um nome irrelevante e conhecido) que retorna 3 se você passar a string específica "X" para seu get acessor.

O que torna o nome do indexador padrão irrelevante é que as sintaxes C #, VB e F # podem chamar indexadores padrão sem especificar um nome. (Daí o termo 'padrão'.) Uma linguagem que não tivesse nenhum conceito de indexadores padrão, mas que tivesse um conceito de indexadores nomeados, teria que especificar o nome. O nome do indexador padrão geralmente é Item , mas o indexador padrão pode ser nomeado como algo arbitrário e ainda assim ser o indexador padrão porque essas linguagens ainda seriam capazes de chamá-lo sem especificar um nome. Em C #, você faria isso usando o atributo [IndexerName("ArbitraryName")] .

@ jnm2 Na verdade eu entendo todas as coisas da linguagem C # etc. sobre indexadores ... no entanto ...

Eu acho que há potencial para confusão entre a propriedade

  • __ser__ um indexador do objeto real
  • __retornando__ um objeto que possui um indexador
  • __ devolvendo__ uma coleção com itens nela

Enfatizo que __você__ não ficará confuso e provavelmente só fingirei estar ao falar com você (método socrático, sabe: wink :), mas suspeito que muitos podem achar confuso e você o explicará para as pessoas bastante.

Não sou contra a adição, mas acho que as distinções acima precisariam ser explicitadas nos documentos.

PS: Não me senti excluída de forma alguma. :risonho:

@CharliePoole Boa ideia. Podemos colocar essa consideração até chegarmos a um nome, já que parece que não afetará a escolha entre Has.ItemAt / Has.ElementAt / Has.EntryAt / etc? Ou há um ponto que eu perdi e você está apoiando uma escolha de nome específica com base neste potencial de confusão?

Até agora estou inclinado para ItemAt . @Dreamescaper , ainda é onde você está? @CharliePoole Eu não tinha certeza se você estava expressando uma preferência por ElementAt ou apenas nos ajudando a pensar no processo.

Quanto mais pessoas gritam Has.ItemAt / Has.ElementAt / Has.EntryAt / other, mais confiança podemos ter no que as pessoas acharão intuitivo. Eu gostaria de apenas chamá-lo pelo que parecer estar na liderança no dia seguinte ou depois, já que perderemos o lançamento do 3.13 por uma pequena quantidade se demorarmos muito mais do que isso.

@ jnm2 Apenas tentando pensar por mim mesmo e encorajar o mesmo.

WRT a escolha das palavras, gostaria de saber se alguma formulação específica funcionará melhor com os três tipos de expressões que listei e também se há outras combinações com que se preocupar. Com as três opções que mencionei, você teria

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

Então, acho que você está certo ao dizer que o texto não importa para nenhuma opção

Olhando para o longo prazo, prefiro usar a indexação real com colchetes. Mas isso exigiria realmente compilar a expressão de restrição em vez de modelar como um conjunto de classes.

Eu sei que isso está acima do meu nível salarial: sorria :, mas pensei em interromper aqui ...

Eu gosto de ItemAt .

Passei um tempo, como sugeriu @CharliePoole , pensando em alguns casos de uso. Cheguei à conclusão de que ElementAt é mais usado em situações de IEnumerable e os indexadores de linguagem podem lidar com mais do que apenas IEnumerable . Sentei na ElementAt por um tempo, então considerei:
`` `c #
Assert.That (..., Has.ElementAt ("um", "dois"));

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

ItemAt acima parece mais natural expressar vários indexadores de valor.

Também considerei que a passagem originalmente destinada a um curto-circuito para uma restrição de propriedade e ItemAt se encaixa nessa narrativa. (No entanto, isso não impede que ele se transforme em algo mais / melhor / etc, apenas um ponto de vista).

Eu também prefiro ItemAt , então vamos chamar de maioria e decidir a favor disso 😺

Para evitar confusão, vamos garantir que isso seja documentado rapidamente após a conclusão e incluído nas notas de lançamento.

@Poimen Re: seu nível de pagamento, não subestime até que ponto estamos todos inventando à medida que avançamos aqui. Obrigado por compartilhar suas idéias!

E quanto ao nível salarial, uma vez que todos aqui estão ganhando um salário anual de cerca de US $ 0 trabalhando no NUnit, acho que estamos todos no mesmo nível salarial 😝

@Dreamescaper fez outra boa pergunta no PR. Seja positivo ou negativo, é como se fosse verificar a contagem em vez de verificar a presença de um indexador:

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

Parece mais seguro remover a restrição Exists até que tenhamos mais tempo para pensar sobre ela. Eu preferiria abrir um problema separado para Has.Indexer(typeof(Type1), typeof(Type2), ...) para uma verificação de existência do indexador e Has.ItemAt(arg1, arg2, ...) para realmente executar o indexador e afirmar coisas sobre o valor resultante.

Alguém se oporia a enviar inicialmente Has.ItemAt(3) por não resolver uma restrição e Has.ItemAt(3).EqualTo(4) por resolver?

Has.Indexer faz sentido para mim. Eu suponho que você também suportaria Has.Indexer<T>() , Has.Indexer<T1, T2>() , etc. para consistência com outras restrições.

Obrigado pelo feedback! Dei o sinal verde para a solicitação de pull para fazer com que Has.ItemAt (...) não fosse mais autodeterminado e preenchi https://github.com/nunit/nunit/issues/3690 para rastrear Has.Indexer para fornecer essa capacidade.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

ffMathy picture ffMathy  ·  3Comentários

UyttenhoveSimon picture UyttenhoveSimon  ·  3Comentários

ChrisMaddock picture ChrisMaddock  ·  3Comentários

xplicit picture xplicit  ·  5Comentários

Thaina picture Thaina  ·  5Comentários