Existe-t-il un moyen élégant d'affirmer que les nombres sont égaux tout en ignorant leurs classes? Je veux l'utiliser dans le framework de tests JUnit mais par exemple
Assert.assertEquals(1,1L)
échoue avec Java.lang.AssertionError: attendu: Java.lang.Integer <1> mais était: Java.lang.Long <1>
Je pense qu’il existe une méthode Nice quelque part qui compare uniquement la valeur et fonctionne avec int, long, float, octet, double, BigDecimal, BigInteger, nommez-le.
Une solution de contournement consiste à insérer les valeurs dans BigDecimal objects, car les surcharges de constructeur BigDecimal
acceptent les primitives long
, int
et double
.
Depuis new BigDecimal(1l).equals(new BigDecimal(1.0))
vaut true
,
Assert.assertEquals(new BigDecimal(1.0), new BigDecimal(1l));
devrait travailler pour vous.
Modifier
Comme Hulk indique ci-dessous, l'échelle des objets BigDecimal
est utilisée dans la comparaison equals
, mais pas dans la comparaison compareTo
. Bien que l'échelle soit définie sur un 0
par défaut pour le constructeur prenant long
, elle est déduite par un calcul dans le constructeur prenant double
. Par conséquent, le moyen le plus sûr de comparer les valeurs (c'est-à-dire dans les cas Edge pour les valeurs double
) peut-être est d'appeler compareTo
et de vérifier le résultat, à la place, 0
.
D'après mes lectures sur le JLS, la résolution de surcharge pour
Assert.assertEquals(1,1L)
devrait résoudre à
Assert.assertEquals(long, long)
(Pour mémoire, assertEquals(long, long)
, assertEquals(float, float)
et assertEquals(double, double)
sont applicables par invocation stricte, et le premier est le plus spécifique; voir JLS 15.12.2.2 . Le contexte d'invocation strict autorise un élargissement primitif, mais pas la mise en boîte ou unboxing.)
Si (comme le suggère la preuve) votre appel résout Assert.assertEquals(Object, Object)
, cela implique que l'un des opérandes doit déjà être un type encadré. Le problème avec cette surcharge est qu’il utilise la méthode equals(Object)
pour comparer des objets et que le contrat pour cette méthode spécifie que le résultat est false
si les types respectifs des objets sont différents.
Si c'est ce qui se passe dans votre code réel, alors je doute que la suggestion d'utiliser la fonction is(T)
Matcher
fonctionne également. L'attribut is(T)
équivaut à is(equalTo(T))
et ce dernier s'appuie sur equals(Object)
...
Existe-t-il une "méthode de Nice" existante?
Autant que je sache, non.
Je pense que la vraie solution est d’être un peu plus attentif aux types; par exemple.
int i = 1;
Long l = 1L;
Assert.assertEquals(i, l); // Fails
Assert.assertEquals((long) i, l); // OK - assertEquals(Object, Object)
Assert.assertEquals((Long) i, l); // OK - assertEquals(Object, Object)
Assert.assertEquals(i, (int) l); // OK - assertEquals(long, long)
// it would bind to an (int, int)
// overload ... it it existed.
Assert.assertEquals(i, (long) l); // OK - assertEquals(long, long)
Ecrire une coutume Matcher
fonctionnerait aussi.
Emballez cette fonctionnalité dans votre propre Matcher et utilisez-la avec assertThat
.
Échantillon de matcher:
class IsAnyNumber extends BaseMatcher {
final Object expected;
//...
public boolean matches(Object actual) {
// compare / transform / check type / ensure: String, double, int, long
// example via BigDecimal as seen from Mena (without checks)
return new BigDecimal(expected).equals(new BigDecimal(actual));
}
// ...
}
// somewhere else:
public static IsAnyNumber is(Object expected) {
return new IsAnyNumber(expected);
}
Dans vos tests, vous appelez ensuite cette méthode statique:
assertThat(1, is(1L));
assertThat(1, is(1.0));
assertThat(1L, is(1));
De cette façon, vous pouvez réutiliser votre outil de correspondance et la déclaration d'assertion est plus lisible à la fin.
Avertissement: il ne s'agit que de pseudo-code et n'a pas encore été testé, mais devrait fonctionner avec quelques ajustements.
Mais méfiez-vous aussi de Comparaison des nombres en Java
Créez vos propres méthodes d'assertion et comparez les valeurs doubles des primitives. Si une BigDecimal
est utilisée, la valeur de la primitive doit être convertie en une BigDecimal
static void assertEquals(Number number1, Number number2) {
Assert.assertEquals(number1.doubleValue(), number2.doubleValue());
}
static void assertEquals(BigDecimal number1, BigDecimal number2) {
if (number2.compareTo(number1) != 0) {
Assert.fail("Values are not equal. ..... ");
}
}
static void assertEquals(Number number1, BigDecimal number2) {
assertEquals(new BigDecimal(number1.doubleValue()), number2);
}
static void assertEquals(BigDecimal number1, Number number2) {
assertEquals(number2, number1);
}
Il peut être utilisé de cette façon:
assertEquals(1, new BigDecimal("1.0"));
assertEquals(1.0d, 1);
assertEquals(new Float(1.0f), 1.0d);
assertEquals(new BigDecimal("1.00000"), new BigDecimal("1.0"));
...
Je pense que pour accepter les huit types de valeurs numériques (primitive et objet), la méthode doit prendre des arguments de type chaîne. L'appelant devra se rappeler de transtyper la valeur en chaîne par cet idiome:
""+value
De même, si la valeur n'est pas un entier (int
, Integer
, long
, Long
), mais une représentation en virgule flottante (float
, double
, Float
, Double
), la méthode doit également prendre un argument epsilon
pour tolérer l'imprécision due à la représentation.
Voici donc une idée de mise en œuvre (pour l'instant, j'ignore les cas de NaN et les zéros positif et négatif de double - ils peuvent être ajoutés si une mise en œuvre réellement solide est nécessaire).
private static boolean equalsNumerically(String n1String
, String n2String
, double epsilon) {
try {
Long n1Long = new Long(n1String);
Long n2Long = new Long(n2String);
return n1Long.equals(n2Long);
} catch (NumberFormatException e) {
/*
* If either one of the number is not an integer, try comparing
* the two as Double
*/
try {
Double n1Double = new Double(n1String);
Double n2Double = new Double(n2String);
double delta = ( n1Double - n2Double) / n2Double;
if (delta<epsilon) {
return true;
} else {
return false;
}
} catch (NumberFormatException e2) {
return false;
}
}
}
Code de test
int primitiveInt = 1;
long primitiveLong = 1L;
float primitiveFloat = 0.999999F;
double primitiveDouble = 0.999999D;
Integer objectInt = new Integer(1);
Long objectLong = new Long(1);
Float objectFloat = new Float(0.999999);
Double objectDouble = new Double(0.999999);
final double epsilon = 1E-3;
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveLong, 0));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+primitiveLong, 0): %s %s %s%n"
, primitiveInt, primitiveLong, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveLong, epsilon));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+primitiveLong, epsilon)): %s %s %s%n"
, primitiveInt, primitiveLong, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveFloat, epsilon));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+primitiveFloat, 0): %s %s %s%n"
, primitiveInt, primitiveFloat, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveDouble, epsilon));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+primitiveDouble, epsilon): %s %s %s%n"
, primitiveInt, primitiveDouble, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectInt, 0));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+objectInt, 0): %s %s %s%n"
, primitiveInt, objectInt, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectLong, 0));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+objectLong"
+ ", \"\"+objectLong, 0): %s %s %s%n"
, primitiveInt, primitiveLong, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectFloat, epsilon));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+objectFloat, epsilon)): %s %s %s%n"
, primitiveInt, objectFloat, epsilon);
Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectDouble, epsilon));
System.out.format("Test passed: "
+ "Assert.assertTrue(equalsNumerically(\"\"+primitiveInt"
+ ", \"\"+objectDouble, 0): %s %s %s%n"
, primitiveInt, objectDouble, epsilon);
Test de sortie
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveLong, 0): 1 1 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveLong, epsilon)): 1 1 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveFloat, 0): 1 0.999999 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+primitiveDouble, epsilon): 1 0.999999 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectInt, 0): 1 1 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectLong, 0): 1 1 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectFloat, epsilon)): 1 0.999999 0.001
Test passed: Assert.assertTrue(equalsNumerically(""+primitiveInt, ""+objectDouble, 0): 1 0.999999 0.001