Перегрузка статического импорта

В тестовом классе я хотел бы предоставить свою собственную перегрузку assertEquals с некоторой специальной логикой, не зависящей от Object.equals. К сожалению, это не работает, потому что как только я объявляю свой метод assertEquals локально, Java больше не находит статический импорт из org.junit.Assert.*.

Есть ли способ обойти это? т.е. есть ли способ предоставить дополнительную перегрузку для статически импортированного метода? (Довольно очевидное решение состоит в том, чтобы назвать метод по-другому, но это решение не имеет такой же эстетической привлекательности.)

Мой файл тестового класса выглядит примерно так:

package org.foo.bar;

import static org.junit.Assert.*;

import org.junit.Test;

public class BarTest {
    private static void assertEquals(Bar expected, Bar other) {
        // Some custom logic to test equality.
    }

    @Test
    public void testGetFoo() throws Exception {
        Bar a = new Bar();
        assertEquals(42, a.getFoo()); // Error *
    }

    @Test
    public void testCopyConstructor() throws Exception {
        Bar a = new Bar();
        // Fill a.
        Bar b = new Bar(a);
        assertEquals(a, b);
    }
}

Error * означает «Метод assertEquals(Bar, Bar) в типе BarTest неприменим для аргументов (int, int)».


person Konrad Rudolph    schedule 19.09.2009    source источник
comment
Каков тип возврата getFoo() в классе Bar - int или иначе?   -  person Vineet Reynolds    schedule 19.09.2009
comment
Неважно, понял, что это было int через сообщение об ошибке.   -  person Vineet Reynolds    schedule 19.09.2009
comment
Это не прямой ответ, но я бы действительно поставил под сомнение ваш мотив для этого, эстетическую привлекательность, как вы выразились. Если я разработчик, читающий модульный тест, я хочу, чтобы assertEquals всегда был реализацией JUnit. На мой взгляд, использование метода assertBarEquals намного более коммуникативно. Это правильный вопрос, и я вижу, как он может быть полезен в других ситуациях, я просто не верю, что какой-либо другой способ лучше, чем assertBarEquals().   -  person Grundlefleck    schedule 19.09.2009
comment
P.S. в заголовке вопроса есть опечатка, которую я не могу исправить, перегрузка -> перегрузка. :)   -  person Grundlefleck    schedule 19.09.2009


Ответы (4)


В этом ответе есть два раздела: один об ошибке компиляции, а другой об использовании assertEquals().

Проблема в том, что есть два метода assertEquals() в двух разных пространствах имен — один присутствует в пространстве имен org.junit.Assert, а другой — в пространстве имен org.foo.bar.BarTest (текущее пространство имен).

Компилятор сообщает об ошибке из-за правила теневого копирования, объявленные в Спецификации языка Java. Статический импорт Assert.assertEquals() затенен assertEquals(), объявленным в классе BarTest.

Исправление (всегда в случае теневых объявлений) заключается в использовании FQN (полных имен). Если вы собираетесь использовать assertEquals(...) класса JUnit Assert, используйте

org.junit.Assert.assertEquals(...)

и когда вам нужно использовать свою декларацию, просто используйте

assertEquals(...)

только в BarTest, где он затенен. Во всех других классах, которым требуется только Assert.assertEquals() или BarTest.asserEquals(), вы можете импортировать Assert или BarTest (я не думаю, что вам нужно импортировать BarTest где-то еще, но тем не менее указал это).

При отсутствии затенения вы можете позволить себе просто импортировать класс или статический метод и использовать его без полных доменных имен.

Дополнительные темы для размышления

Assert.assertEquals() внутренне использует метод equals() классов аргументов. Объявление assertEquals() в вашем тестовом примере нарушает принцип DRY, поскольку метод типа equals() должен быть реализован и использоваться последовательно - две разные реализации в исходном коде и в модульных тестах обязательно вызовут путаницу.

Лучшим подходом было бы реализовать equals() на Bar, а затем использовать Assert.assertEquals() в ваших тестовых примерах. Если у вас уже есть, вам не нужен BarTest.assertEquals(). Псевдокод для assertEquals() выглядит примерно так:

  1. Если оба аргумента равны нулю, вернуть true.
  2. Если ожидаемое не равно null, вызовите equals() для ожидаемого, передав фактическое в качестве аргумента. Верните true, если объекты равны.
  3. Если объекты не равны, выдайте AssertionError с форматированным сообщением.
person Vineet Reynolds    schedule 19.09.2009
comment
Я действительно не думаю, что это нарушает DRY (скорее POLS, поскольку мы используем аббревиатуры): equals намеренно не существует в моем типе, потому что для этих типов нет абсолютно никакого смысла сравнивать равенство — за исключением этого модульного теста. - так они это не реализуют. - person Konrad Rudolph; 19.09.2009
comment
Сложная ситуация, которая делает спорным вопрос о том, следует ли иметь реализацию equals() или затенить assertEquals(). Я бы выбрал equals(). - person Vineet Reynolds; 19.09.2009

Одним из возможных решений для вашего конкретного примера вызова assertEquals(Bar, Bar) в модульном тесте было бы расширение класса классом, который предоставляет статический метод, следующим образом:

class BarAssert extends Assert {
  public static void assertEquals(Bar expected, Bar other) {
        // Some custom logic to test equality.
    }
}

Затем вы можете включить import static BarAssert.assertEquals; и использовать собственную логику.

Извините, что это не дает прямого ответа на вопрос и больше направлено на ваш пример. Согласно моему комментарию, прикрепленному к вопросу, я бы рекомендовал не использовать этот подход.

person Grundlefleck    schedule 19.09.2009
comment
+1. Любой, кто читает это и использует этот подход, также должен выдавать AssertionError при сбое. После этого тестировщики смогут пометить неудачный тест красным цветом. - person Vineet Reynolds; 19.09.2009
comment
Это будет java.lang.AssertionError java. sun.com/j2se/1.5.0/docs/api/java/lang/AssertionError.html - person Vineet Reynolds; 19.09.2009
comment
@Vineet: почему бы просто не использовать Assert.fail? - person Konrad Rudolph; 19.09.2009
comment
@Конрад, мой плохой. Я забыл, что функция fail() доступна при создании подкласса. Внутри он создает и выдает AssertionError. - person Vineet Reynolds; 19.09.2009

Единственный способ - полностью квалифицировать тот или иной.

import static org.junit.Assert.*;

import org.junit.Test;

public class BarTest {

    private static void assertEquals(Bar expected, Bar other) {
        // Some custom logic to test equality.
    }

    @Test
    public void testGetFoo() throws Exception {
        Bar a = new Bar();
        org.junit.Assert.assertEquals(42, a.getFoo());
    }
}
person SingleShot    schedule 19.09.2009

this.assertEquals(a,b);

or

BarTest.assertEquals(a,b);

Я бы выбрал первый, поскольку, несмотря на то, что это статический метод, вам понадобится экземпляр для его использования (он частный), а this не будет подвержен прихоти будущих переименований.

person Carl    schedule 19.09.2009