Замена вызова метода java из поля вызовом метода

Я пытаюсь создать насмешливую структуру в java, которая соответствует конкретным требованиям проекта.

Сценарий таков, у меня есть метод

 public String returnRandom(){

    String randomString = this.randomGenerator.returnRandom()

    }

randomGenerator является зависимостью этого класса и внедряется в объект только во время выполнения. Означает, что это будет нуль, если объект будет создан без инфраструктуры внедрения зависимостей.

Во время теста изоляции я хочу, чтобы замена присваивалась оператору

this.randomGenerator.returnRandom();

с методом, который возвращает случайное значение, например "Helloworld".

Для того же я пытался использовать javassist.expr.FieldAccess, с помощью которого я могу заменить поле на отсутствие операции, а вызов метода можно изменить с помощью javassist.expr.MethodCall.

Я не могу понять, как заменить поле фиктивным или нет операции. Возможно ли это с помощью java-помощника или мне следует использовать более низкоуровневую манипуляцию с байт-кодом, например asm?

Примечание. Я мог бы добиться замены вызова метода, который не исходит из поля, с помощью javassist.expr.MethodCall. Например, если приведенный выше пример

public String returnRandom(){

    String randomString = returnRandom();

    }

Я могу заменить как

public String returnRandom(){

    String randomString = MockedString.getSampleRandom();

    }

person arunvg    schedule 26.01.2015    source источник
comment
При имитации возникают две проблемы: 1) описание поведения фиктивных объектов, 2) внедрение фиктивных объектов. (1) по своей сути не является бережливым, потому что у вас может быть другое поведение, которое необходимо протестировать. Хорошая настройка модульного теста предлагает хорошие значения по умолчанию для фиктивных объектов и внедряет их. Модульные тесты ниже могут получить доступ к этим фиктивным объектам, чтобы изменить их поведение. Вы хотите внедрить фиктивные объекты и уже удовлетворены системным методом создания и описания фиктивных объектов?   -  person Jose Martinez    schedule 26.01.2015
comment
Именно, наша целевая системная архитектура и процесс согласованы таким образом, что для большинства объектов мы знаем их значения по умолчанию (действие, выполняемое на самом этапе проектирования). Затем задача состоит в том, чтобы определить тесты. Мы пытаемся упростить определение теста с закрытой структурой, а не с очень открытыми тестовыми примерами в стиле junit и эмпирическими механизмами, такими как все пары. И при выполнении этих тестовых определений мы пытались манипулировать байт-кодом (или исходным кодом), чтобы внедрить эти фиктивные объекты.   -  person arunvg    schedule 26.01.2015
comment
Можно ли управлять байт-кодом во время выполнения? У меня сложилось впечатление, что нельзя. Это может вызвать беспокойство, потому что если есть сотни тестов, перекомпиляция для каждого теста может привести к длинному циклу модульного тестирования.   -  person Jose Martinez    schedule 26.01.2015
comment
Байт-кодом можно манипулировать во время выполнения, и существуют доступные библиотеки, которые обычно называются инженерными библиотеками байт-кода (например, java-помощь, asm). Да, задержки возникают, особенно когда все тесты выполняются в среде непрерывной интеграции. Нам удалось это сделать, воспользовавшись высокой модульностью нашей системы и проводя тесты параллельно. Когда тесты выполняются индивидуально, время отклика человека (750 мс) вполне укладывается в рамки времени.   -  person arunvg    schedule 26.01.2015


Ответы (3)


Лучшее, что вы можете сделать, это создать интерфейс для генератора случайных чисел, скажем:

public interface RandomGenerator {
  public String returnRandom();
}

Тогда ваш исходный генератор случайных чисел может реализовать этот интерфейс, а класс, использующий генератор случайных чисел, может зависеть от класса с интерфейсом RandomGenerator.

Если у вас есть это, это довольно просто проверить. Вы создаете фиктивный генератор, который делает то, что вы хотите:

public class MockRndGenerator implements RandomGenerator {
  public String returnRandom() {
    return "Helloworld";
  }
}

и когда вы тестируете, вы вводите этот класс вместо оригинала.

public class Demo {
  public Demo (RandomGenerator rndGenerator) {
    this.randomGenerator = rndGenerator;
  }
  public String returnRandom(){
    String randomString = this.randomGenerator.returnRandom()
  }
}

* ОБНОВЛЕНИЕ *

Поскольку я не могу добавлять код в комментарии, вот решение Mockito:

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

class Test {
  public static rndTest() {
    RandomGenerator rnd = Mockito.mock(RandomGenerator.class);
    Mockito.when(rnd.returnRandom()).thenReturn("Helloworld");
    Demo = new Demo(rnd);
  }
}
person meza    schedule 26.01.2015
comment
Спасибо меса за ответ. Это именно та проблема, которую мы пытаемся решить с помощью новой среды тестирования. Мы использовали этот подход раньше и получили большое количество java-классов для имитации. Мы были обеспокоены ремонтопригодностью тестов и хотели создать лучшую структуру, которая давала бы разработчикам меньшие накладные расходы на написание и поддержку модульных тестов. - person arunvg; 26.01.2015
comment
Да, это просто и проверяемо. И, что более важно, это приводит к хорошему стилю программирования. - person Seelenvirtuose; 26.01.2015
comment
aurnvg: вы всегда можете использовать Mockito, чтобы избежать создания физических моков, а затем вы можете настроить ожидания и проверки по пути. class Test { public static rndTest () { RandomGenerator rnd = Mockito.mock (RandomGenerator.class); Mockito.when(rnd.returnRandom()).thenReturn(Helloworld); Демонстрация = новая демонстрация (rnd); } } - person meza; 26.01.2015
comment
Я добавил это решение к моему ответу - person meza; 26.01.2015
comment
Еще раз спасибо. Мы тоже пробовали мокито. Но результат не вписывался в философию бережливого производства, которой мы следуем. Мы хотели более компактные тестовые исходные файлы. Мы изучаем возможности разработки байт-кода и манипулирования исходным кодом, чтобы уменьшить размер наших тестовых исходных файлов. И мы попробовали существующие фреймворки для тестирования, и они не соответствовали нашим требованиям. - person arunvg; 26.01.2015
comment
Вообще говоря, если это сложно протестировать, скорее всего, проблема в дизайне исходного класса. :) Кроме того, если вы чувствуете, что повторяетесь при написании тестов, это может быть еще одним признаком того, что что-то еще не так. Большинство фиктивных фреймворков хорошо справляются со своей задачей, и если ваш дизайн хорош, они вообще не вторгаются в тестовые классы. - person meza; 26.01.2015
comment
Спасибо, meza, мы добрые, занимаемся исследованиями и разработками и пытаемся найти механизм тестирования, который был бы более компактным, удобным в сопровождении и экономичным. Мы изучили существующие механизмы и оценили плюсы и минусы. И пытается сделать POC в поисках лучших механизмов. - person arunvg; 26.01.2015

Я мог бы решить проблему, используя javassist.expr.MethodCall. Ниже приведен класс редактора выражений, используемый для проверки возможности.

Это заменяет вызов targetMethod (methodcalltoReplace) кодом, используемым для получения фиктивного объекта.

new ExprEditor() {
        @Override
        public void edit(MethodCall m) throws CannotCompileException {
            try {
                if (m.where().getName().equals(sourceMethod)) {
                    if (m.getMethod().getName().equals(methodcalltoReplace)) {
                        if(lineNumberOfMethodcalltoReplace == m.getLineNumber()){
                            // The content of the hardcoded string can be replaced with runtime data
                            m.replace("$_ = ($r)"+"new com.nuwaza.aqua.sample.SampleForMethodInvocationFieldAccess().helloworld();");
                        }
                    }
                }
            } catch (NotFoundException e) {
                e.printStackTrace();
            }
            super.edit(m);
        }

Подробную документацию см. в руководстве по Javaassist. , самоанализ и персонализация

person arunvg    schedule 27.01.2015

Один из подходов, который я использовал в прошлом, состоит в том, чтобы создать бэкдор-статический метод для замены вашего генератора случайных чисел имитированной версией. Затем вы можете использовать mockito, чтобы указать, что он должен возвращать. Делая его статическим, вы избегаете необходимости создавать множество из них, а только один для всего класса. Как и все остальное, у него есть плюсы и минусы, но он может соответствовать вашим конкретным потребностям.

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

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

person Jose Martinez    schedule 26.01.2015
comment
Спасибо, Хосе Мартинес. В основном вы предлагаете сделать доступ к зависимостям из отдельного метода (в основном это будет частный). Наша структура может достичь этого, отвечая нашим (строгим) требованиям. (Пожалуйста, смотрите пример в примечании, добавленном к вопросу). Но нас беспокоил аспект юзабилити. Это означает, что разработчики должны кодировать, помня об ограничениях инструмента тестирования. Это последнее, что мы хотим сделать... то есть посягать на свободу разработчиков писать код. - person arunvg; 26.01.2015
comment
Хм. Это может быть субъективной проблемой. Скажите своим разработчикам, что модульное тестирование — это первоклассный гражданин, оно первым попадает в поезд. Все проблемные дочерние зависимости должны иметь лазейку для имитации. Это решает проблему создания большого количества классов, потому что ваша фиктивная среда может обнаруживать статические сеттеры и может вставлять фиктивные объекты для разработчиков. Затем разработчики могут решить, какие из этих фиктивных объектов они хотят использовать в дальнейшем. Вы даже можете написать свои классы с предлагаемыми фиктивными объектами по умолчанию, чтобы их не нужно было разрабатывать во время модульного тестирования. - person Jose Martinez; 26.01.2015
comment
Спасибо, Хосе Мартинес. :). Мы стремимся решать проблемы, достигая нашей цели и не идя на компромисс с ограничениями игры. Для нас разработчики похожи на наших клиентов, требующих лучшего пользовательского опыта. - person arunvg; 26.01.2015
comment
Понял. Я буду держать глаза открытыми, чтобы увидеть, что еще я могу найти, что может помочь вам. - person Jose Martinez; 26.01.2015