Модульный тест - проверка вызова метода с объектом, который не переопределяет равно - PullRequest
1 голос
/ 29 октября 2009

Это общий вопрос о том, как выполнить модульное тестирование Java-класса с использованием фиктивных объектов.
Я могу суммировать мою проблему с этим примером.
Допустим, у меня есть интерфейс с именем MyInterface.java и объект "TwoString", который не переопределяет equals ()

"TwoString.java"

   private String string1;
   private String string2;

   public TwoString(String string1, String string2) {
     this.string1 = string1;
     this.string2 = string2;
   }
   ...getters..setters..

"MyInterface.java"

void callMe(TwoString twoString);

Тогда у меня есть "MyClass.java" Объект. Его конструктор принимает конкретную реализацию MyInterface.
Метод MyClassToTest () содержит логику для создания объекта TwoString каким-либо образом. Допустим, он будет создан как

new TwoString("a","b")

Таким образом, когда вызывается метод ToTest (), он создает этот объект TwoString, который будет передан методу интерфейса

Я в основном хочу издеваться над интерфейсом. Создайте объект MyClass с этим макетом. Затем убедитесь, что метод макета вызывается с конкретным экземпляром TwoString.

Я использую EasyMock, и это некоторый код Java

"MyClassTest.java"

public void test() throws Exception {   
   MyInterface myInterfaceMock = createMock(MyInterface.class);
   MyClass myClass = new MyClass(myInterfaceMock);

   myInterfaceMock.callMe(new TwoString("a","b"));   <--- fails here
   expectLastCall();
   replay(myInterfaceMock);

   myClass.methodToTest();
   verify(myInterfaceMock);

Здесь возникает проблема. Объект TwoString, который я ожидаю при вызове

myInterfaceMock.callMe(new TwoString("a","b"));

отличается от сгенерированного в MyClass.methodToTest (), потому что TwoString.java не переопределяет равно.

Я могу пропустить проблему на экземпляре TwoString, используя

myInterfaceMock.callMe((TwoString)anyObject());

но я хочу быть уверенным, что метод интерфейса вызывается с конкретным экземпляром TwoString, который содержит "a" в качестве строки1 и "b" в качестве строки2.

В этом случае объект TwoString очень прост, и будет легко переопределить метод equals, но что, если мне нужно проверить более сложный объект.

Спасибо

редактирование:

В этом примере я постараюсь сделать его более читабельным

public class MyClassTest {
    private MyClass myClass;
    private TaskExecutor taskExecutorMock;

    @Before
    public void setUp() throws Exception {
        taskExecutorMock = createMock(TaskExecutor.class);
        myClass = new MyClass(taskExecutorMock);
    }

    @Test
    public void testRun() throws Exception {
        List<MyObj> myObjList = new ArrayList<MyObj>();
        myObjList.add(new MyObj("abc", referenceToSomethingElse));

        taskExecutorMock.execute(new SomeTask(referenceToSomethingElse,  ???new SomeObj("abc", referenceToSomethingElse, "whatever")));   <--- ??? = object created using data in myObjList
        expectLastCall();
        replay(taskExecutorMock);

        myClass.run(myObjList);

        verify(taskExecutorMock);
    }
}

??? SomeObj = объект, созданный myClass.run () с использованием данных, содержащихся в myObjList.
Допустим, SomeObj происходит из сторонней библиотеки и не переопределяет equals.

Я хочу быть уверен, что метод taskExecutorMock.execute () вызывается с конкретным экземпляром этого SomeObj

Как проверить, что myClass.run () фактически вызывает метод taskExecutorMock с правильным экземпляром

Ответы [ 5 ]

5 голосов
/ 29 октября 2009

Можно использовать пользовательский метод сопоставления равных, используя org.easymock.IArgumentMatcher.

Это должно выглядеть примерно так:

private static <T extends TwoString> T eqTwoString(final TwoString twoString) {
    reportMatcher(new IArgumentMatcher() {
        /** Required to get nice output */
        public void appendTo(StringBuffer buffer) {
            buffer.append("eqTwoString(" + twoString.getString1() + "," + twoString.getString2() + ")");
        }

        /** Implement equals basically */
        public boolean matches(Object object) {
            if (object instanceof TwoString) {
                TwoString other = (TwoString) object;
                // Consider adding null checks below
                return twoString.getString1().equals(object.getString1()) && twoString.getString2().equals(object.getString2());
            }
            else {
                return false;
            }
        }
    });
    return null;
}

А используется следующим образом:

myInterfaceMock.callMe(eqTwoString(new TwoString("a","b")));

Некоторые детали могут быть неверными, но по сути это то, как я делал это раньше. Есть другой пример и более подробные объяснения, доступные в документации EasyMock . Просто найдите IArgumentMatcher.

2 голосов
/ 29 октября 2009

Взгляните на Jakarta Commons Lang: EqualsBuilder.reflectionEquals ()

Хотя я согласен с dtsazza , что все объекты-значения должны иметь метод equals()hashCode()), они не всегда подходят для тестирования: большинство объектов-значений будут основывать равенство на ключе. , а не на всех полях.

В то же время я с подозрением отношусь к любому тесту, который хочет проверить все поля: мне кажется, что он говорит: «Убедитесь, что этот метод не изменил то, что я не планировал изменить. " Это действительный тест, и на некотором уровне очень хороший тест, но немного страшно, что вы чувствуете необходимость в нем.

2 голосов
/ 29 октября 2009

Сначала - вы, вероятно, имеете в виду «переопределить равные», а не реализовать, поскольку все классы имеют некоторую реализацию равных (ту, что они наследуют от Object, если они ничего не переопределяют).

Ответ в этом случае прост - все объекты-значения действительно должны реализовывать equals и hashcode. Будь то простой объект типа TwoString или более сложный объект, на который вы намекаете, объект должен проверить, равен ли он какому-либо другому объекту.

Единственная другая альтернатива - деконструкция объекта в тестовом коде - вместо

assertEquals(expected, twoStr);

ты бы сделал

assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());

Надеюсь, вы видите, что это плохо, по крайней мере, тремя способами. Во-первых, вы в основном дублируете логику, которая должна быть в методе equals () класса; и везде, где вы хотите сравнить эти объекты, вам придется писать один и тот же код.

Во-вторых, вы можете видеть только открытое состояние объекта, вполне может быть какое-то частное состояние, которое приводит к тому, что два , по-видимому, похожих объекта не будут равны (например, класс Lift может иметь общедоступный "этаж" атрибут, но частное состояние "идет вверх или вниз" тоже.)

Наконец, нарушением закона Деметры является то, что сторонний класс в основном возится с внутренностями TwoString, пытающимися выяснить, равны ли они.

Сам объект должен реализовать свой собственный метод equals () - чистый и простой.

0 голосов
/ 29 октября 2009

В этом случае объект TwoString очень прост, и будет легко переопределить метод equals - но что, если мне нужно проверить более сложный объект.

Как только ваши объекты станут настолько сложными, что вы не сможете тривиально проверить, равны ли они из других источников, вам, вероятно, следует провести рефакторинг и внедрить их в качестве зависимости. Это изменит дизайн, но обычно это к лучшему.

Вы также, кажется, полагаетесь на некоторые знания о внутреннем поведении ваших классов. Выше приведен тест взаимодействия между двумя классами, который все еще работает, но чем больше ваш набор тестируемых компонентов, тем меньше вы действительно можете говорить о «модульных» тестах. В определенный момент вы покидаете сферу модульных тестов и начинаете проводить интеграционные тесты, и в этом случае вам может быть лучше использовать полноценный тестовый набор и изолировать поведение в определенных местах ...

0 голосов
/ 29 октября 2009

Этого можно добиться с помощью захватчиков аргументов в Mockito 1.8.

http://mockito.googlecode.com/svn/branches/1.8.0/javadoc/org/mockito/Mockito.html#15

Я знаю, что вы используете EasyMock, но перейти на Mockito легко и намного лучше!

...