Estou tentando escrever testes de unidade para uma variedade de clone()
operações dentro de um grande projeto e me pergunto se existe uma classe em algum lugar que seja capaz de pegar dois objetos do mesmo tipo, fazer uma comparação profunda e dizer se eles é idêntico ou não?
java
comparison
equals
Uri
fonte
fonte
Respostas:
Unitils tem esta funcionalidade:
fonte
unitils
é falha precisamente porque compara variáveis mesmo quando elas podem não ter um impacto observável . Outra consequência (indesejável) da comparação de variáveis é que encerramentos puros (sem estado próprio) não são suportados. Além disso, requer que os objetos comparados sejam do mesmo tipo de tempo de execução. Arregacei as mangas e criei minha própria versão da ferramenta de comparação profunda que trata dessas questões.Eu amo essa pergunta! Principalmente porque quase nunca é respondida ou mal respondida. É como se ninguém ainda tivesse descoberto. Território virgem :)
Primeiro, nem pense em usar
equals
. O contrato deequals
, conforme definido no javadoc, é uma relação de equivalência (reflexiva, simétrica e transitiva), não uma relação de igualdade. Para isso, também teria que ser anti-simétrico. A única implementaçãoequals
disso é (ou poderia ser) uma verdadeira relação de igualdade emjava.lang.Object
. Mesmo que você useequals
para comparar tudo no gráfico, o risco de quebra do contrato é bem alto. Como Josh Bloch apontou em Effective Java , o contrato de iguais é muito fácil de quebrar:Além do mais, de que serve um método booleano? Seria bom encapsular todas as diferenças entre o original e o clone, você não acha? Além disso, assumirei aqui que você não quer se incomodar em escrever / manter código de comparação para cada objeto no gráfico, mas em vez disso, está procurando por algo que será escalado com a origem à medida que ela muda com o tempo.
Entããão, o que você realmente quer é algum tipo de ferramenta de comparação de estado. A forma como essa ferramenta é implementada realmente depende da natureza de seu modelo de domínio e de suas restrições de desempenho. Na minha experiência, não existe uma fórmula mágica genérica. E será lento em um grande número de iterações. Mas para testar a integridade de uma operação de clone, ele fará o trabalho muito bem. Suas duas melhores opções são serialização e reflexão.
Alguns problemas que você encontrará:
XStream é muito rápido e combinado com XMLUnit fará o trabalho em apenas algumas linhas de código. XMLUnit é bom porque pode relatar todas as diferenças, ou apenas parar na primeira que encontrar. E sua saída inclui o xpath para os nós diferentes, o que é bom. Por padrão, ele não permite coleções não ordenadas, mas pode ser configurado para isso. Injetando um manipulador de diferença especial (chamado de
DifferenceListener
) permite que você especifique a maneira como deseja lidar com as diferenças, incluindo ignorar a ordem. No entanto, assim que você deseja fazer algo além da personalização mais simples, torna-se difícil escrever e os detalhes tendem a ser vinculados a um objeto de domínio específico.Minha preferência pessoal é usar reflexão para percorrer todos os campos declarados e detalhar cada um, rastreando as diferenças conforme eu prossigo. Aviso: não use recursão, a menos que goste de exceções de estouro de pilha. Mantenha as coisas dentro do escopo com uma pilha (use um
LinkedList
ou alguma coisa). Eu geralmente ignoro os campos transientes e estáticos, e pulo os pares de objetos que já comparei, então não acabo em loops infinitos se alguém decidir escrever código auto-referencial (No entanto, eu sempre comparo wrappers primitivos, não importa o que , uma vez que os mesmos refs de objeto são frequentemente reutilizados). Você pode configurar as coisas com antecedência para ignorar a ordem da coleção e para ignorar tipos ou campos especiais, mas eu gosto de definir minhas políticas de comparação de estado nos próprios campos por meio de anotações. Isso, IMHO, é exatamente para o que as anotações foram feitas, para disponibilizar metadados sobre a classe em tempo de execução. Algo como:Acho que esse é um problema realmente difícil, mas totalmente solucionável! E quando você tem algo que funciona para você, é muito, muito útil :)
Então boa sorte. E se você vier com algo que é pura genialidade, não se esqueça de compartilhar!
fonte
Consulte DeepEquals e DeepHashCode () em java-util: https://github.com/jdereg/java-util
Esta classe faz exatamente o que o autor original solicita.
fonte
public
deve se aplicar a todos os tipos, ao invés de ser aplicável apenas a coleções / mapas.Substituir o método equals ()
Você pode simplesmente substituir o método equals () da classe usando EqualsBuilder.reflectionEquals () conforme explicado aqui :
fonte
Apenas tive que implementar a comparação de duas instâncias de entidade revisadas por Hibernate Envers. Comecei a escrever meu próprio diferente, mas depois encontrei a seguinte estrutura.
https://github.com/SQiShER/java-object-diff
Você pode comparar dois objetos do mesmo tipo e ele mostrará alterações, adições e remoções. Se não houver mudanças, então os objetos são iguais (em teoria). As anotações são fornecidas para getters que devem ser ignorados durante a verificação. O quadro de trabalho tem aplicações muito mais amplas do que verificação de igualdade, ou seja, estou usando para gerar um log de alterações.
Seu desempenho é OK, ao comparar entidades JPA, certifique-se de separá-las do gerenciador de entidades primeiro.
fonte
Estou usando o XStream:
fonte
Em AssertJ , você pode fazer:
Provavelmente não funcionará em todos os casos, mas funcionará em mais casos do que você imagina.
Aqui está o que a documentação diz:
fonte
isEqualToComparingFieldByFieldRecursively
agora está obsoleto. Use emassertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
vez :)http://www.unitils.org/tutorial-reflectionassert.html
fonte
Hamcrest tem o Matcher samePropertyValuesAs . Mas ele se baseia na Convenção JavaBeans (usa getters e setters). Se os objetos a serem comparados não tiverem getters e setters para seus atributos, isso não funcionará.
O bean do usuário - com getters e setters
fonte
isFoo
método de leitura para umaBoolean
propriedade. Há um PR que está aberto desde 2016 para consertar isso. github.com/hamcrest/JavaHamcrest/pull/136Se seus objetos implementam Serializable, você pode usar isto:
fonte
Seu exemplo de lista vinculada não é tão difícil de manusear. Conforme o código atravessa os dois gráficos de objeto, ele coloca os objetos visitados em um Conjunto ou Mapa. Antes de atravessar para outra referência de objeto, este conjunto é testado para ver se o objeto já foi atravessado. Em caso afirmativo, não há necessidade de ir mais longe.
Concordo com a pessoa acima que disse usar um LinkedList (como um Stack, mas sem métodos sincronizados nele, então é mais rápido). Percorrer o gráfico do objeto usando uma Pilha, enquanto usa reflexão para obter cada campo, é a solução ideal. Escrito uma vez, este hashCode () "externo" equals () e "externo" é o que todos os métodos equals () e hashCode () devem chamar. Nunca mais você precisará de um método equals () do cliente.
Eu escrevi um pedaço de código que atravessa um gráfico de objeto completo, listado no Google Code. Consulte json-io (http://code.google.com/p/json-io/). Ele serializa um gráfico de objeto Java em JSON e desserializa a partir dele. Ele lida com todos os objetos Java, com ou sem construtores públicos, Serializáveis ou não Serializáveis, etc. Este mesmo código de passagem será a base para a implementação externa "equals ()" e externa "hashcode ()". A propósito, o JsonReader / JsonWriter (json-io) é geralmente mais rápido do que o ObjectInputStream / ObjectOutputStream integrado.
Este JsonReader / JsonWriter poderia ser usado para comparação, mas não ajudará com o hashcode. Se você deseja um hashcode universal () e equals (), ele precisa de seu próprio código. Posso conseguir fazer isso com um visitante de gráfico genérico. Veremos.
Outras considerações - campos estáticos - que são fáceis - podem ser ignoradas porque todas as instâncias de equals () teriam o mesmo valor para campos estáticos, já que os campos estáticos são compartilhados por todas as instâncias.
Quanto aos campos transitórios - essa será uma opção selecionável. Às vezes você pode querer que os transientes sejam contados, outras vezes não. "Às vezes você se sente um maluco, às vezes não."
Volte para o projeto json-io (para meus outros projetos) e você encontrará o projeto externo equals () / hashcode (). Ainda não tenho um nome para isso, mas será óbvio.
fonte
O Apache dá a você algo, converta ambos os objetos em string e compare strings, mas você tem que Substituir toString ()
Substituir toString ()
Se todos os campos forem tipos primitivos:
Se você tiver campos e / ou coleção e / ou mapa não primitivos:
fonte
Eu acho que você sabe disso, mas em teoria, você deve sempre substituir .equals para afirmar que dois objetos são realmente iguais. Isso implicaria que eles verificassem os métodos .equals substituídos em seus membros.
É por esse tipo de coisa que .equals é definido em Object.
Se isso fosse feito de forma consistente, você não teria um problema.
fonte
Uma garantia provisória para uma comparação tão profunda pode ser um problema. O que o seguinte deve fazer? (Se você implementar tal comparador, isso seria um bom teste de unidade.)
Aqui está outro:
fonte
Using an answer instead of a comment to get a longer limit and better formatting.
Se este é um comentário, por que usar a seção de resposta? É por isso que sinalizei. não por causa do?
. Esta resposta já foi sinalizada por outra pessoa, que não deixou o comentário. Acabei de colocar isso na fila de revisão. Pode ser meu mal, eu deveria ter sido mais cuidadoso.Acho que a solução mais fácil inspirada na solução de Ray Hulha é serializar o objeto e depois comparar profundamente o resultado bruto.
A serialização pode ser byte, json, xml ou toString simples, etc. ToString parece ser mais barato. O Lombok gera ToSTring customizável fácil e gratuito para nós. Veja o exemplo abaixo.
fonte
Desde Java 7
Objects.deepEquals(Object, Object)
,.fonte