Пересмешивать локально созданные объекты в Java с помощью Mockito2 - PullRequest
0 голосов
/ 27 июня 2018

Я пишу модульные тесты для проекта, использующего testng и Mockito2. Я хочу издеваться над несколькими методами, которые делают исходящие запросы. Теперь объект для моделирования создается локально в методе другого объекта. Итак, если я скажу, 4 класса, A, B, C и D, такие, что A создает объект типа B, B создает объект типа C и так далее, и объект типа D должен быть смоделирован, я видите, у меня есть два варианта, чтобы высмеять это.

Вариант 1 состоит в том, чтобы шпионить за объектами типа A, B, C и вводить шпион B в A и C в B и, наконец, вводить макет D в C во время создания объекта. Ниже приведен пример.

class A {
        public B createB()
        {
            retrun new B();
        }
        public void someMethod ()
        {
            B b = createB();
        }
    }

Таким образом, я могу следить за A и вводить фиктивный объект для B, когда вызывается createB. Таким образом, я могу в конечном итоге издеваться над Д.

Вариант 2 состоит в том, чтобы не издеваться над прерывистыми классами и напрямую иметь класс Factory, как показано ниже:

class DFactory {
    private static D d;
    static public void setD (D newD)
    {
         d = newD;
    }
    public static D getD()
    {
        if (d!=null)
        {
            return d;
        } else
        {
            return new D();
        }
    }
}

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

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

Обратите внимание, что я не хочу использовать powermockito или любые другие подобные фреймворки, которые поощряют плохой дизайн кода. Я хочу придерживаться mockito2. Я в порядке с рефакторингом моего кода, чтобы сделать его более тестируемым.

1 Ответ

0 голосов
/ 28 июня 2018

Теперь, когда у вас есть A, создающий B и B, создающий C и C, создающий D, все это создание - подробности реализации, которые вы не можете увидеть или изменить , в частности создание объектов зависимостей .

Вы превосходно избегаете использования PowerMockito, и вы также превосходно заинтересованы в рефакторинге своего кода для корректной обработки этого изменения, что означает делегирование выбора D создателю A . Хотя я понимаю, что вы действительно имеете в виду, что этот выбор будет происходить при тестировании, язык этого не знает; вы выбираете другую реализацию для зависимости и отбираете выбор у реализации C. Это известно как инверсия управления или внедрение зависимостей . (Вы, наверное, слышали о них раньше, но я ввожу эти термины в конце, потому что они обычно связаны с весом и структурами, которые на самом деле не нужны для этого разговора прямо сейчас.)

Это немного хитрее, потому что похоже, что вам нужна не просто реализация D, а необходимость создания новых реализаций D. Это делает вещи немного сложнее, но не намного, особенно если вы можете использовать лямбды Java 8 и ссылки на методы. Везде, где вы видите ссылку на D::new, это ссылка на метод на конструктор D , которая может быть принята как Supplier<D> параметр .

Я бы реструктурировал ваш класс одним из следующих способов:

  • Создайте A подобно new A(), но оставьте контроль над реализацией D, когда вы на самом деле вызываете A, например aInstance.doSomething(new D()) или aInstance.doSomething(D::new). Это означает, что C будет делегировать вызывающей стороне каждый раз, когда вы вызываете метод, предоставляя больше контроля вызывающим. Конечно, вы можете предложить перегрузку aInstance.doSomething(), которая внутренне вызывает aInstance.doSomething(new D()), чтобы упростить задачу по умолчанию.
  • Построить A как new A(D::new), где A звонит new B(dSupplier), а B звонит new C(dSupplier). Это затрудняет замену B и C в модульных тестах, но если единственное вероятное изменение заключается в представлении сетевого стека в виде D, то вы изменяете свой код только в соответствии с вашим вариантом использования.
  • Конструкция А, как new A(new B(new C(D::new))). Это означает, что A связан только со своим непосредственным сотрудником B, и значительно упрощает замену любой реализации B на модульные тесты A. Это предполагает, что A нужен только один экземпляр B без необходимости его создания, что может быть не очень хорошим предположением; если все классы должны создать новые экземпляры своих потомков, A примет Supplier<B>, а конструкция A будет выглядеть как new A(() -> new B(() -> new C(D::new))). Это компактно, но сложно, и вы можете создать класс AFactory, который управляет созданием A и настройкой его зависимостей.

Если третий вариант заманчив для вас, и вы думаете, что вам может понадобиться автоматически сгенерировать класс, такой как AFactory, подумайте о рассмотрении структуры внедрения зависимостей , такой как Guice или Dagger .

...