Передача статического метода в качестве параметра в Java

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

@Test
void testCorrectEmailValidator() {
    List<String> correctEmails = Arrays.asList("[email protected]", "[email protected]", "[email protected]",
            "[email protected]", "[email protected]", "[email protected]");

    for (String email : correctEmails) {
        boolean isValid = UserCredentialsValidator.emailValidator(email);
        System.out.println("Email is valid: " + email + ": " + isValid);
        assertTrue(isValid);
    }
}

@Test
void testCorrectUsernameValidator() {
    List<String> correctUsernames = Arrays.asList("username", "123username", "username3", "user2name",
            "USERNAME", "USERNAME123", "123USERNAME123", "2uSERname33");

    for(String username : correctUsernames) {
        boolean isValid = UserCredentialsValidator.usernameValidation(username, userList);
        System.out.println("Username is valid:    " + username + "     : " + isValid);
        assertTrue(isValid);
    }
}

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

Код, который я хотел бы заменить некоторым методом, - это цикл for.

извлекать общий код в методах так же, как в обычном коде

Stultuske 13.03.2019 13:43

Не имеет отношения к вопросу, но почему есть список элементов для проверки? Если каждый элемент списка является крайним случаем, то дайте ему отдельный тест, например, validateUserNameWhenUserNameIsAllLowerCase().

Andrew S 13.03.2019 13:46

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

javamat 13.03.2019 13:46

@javamat UserCredentialsValidator.usernameValidation(username, userList) опечатка? почему он принимает 2 аргумента?

Andrew Tobilko 13.03.2019 13:47

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

javamat 13.03.2019 13:49

Комментарий о таких тестах. Я предпочитаю тестировать/отчитываться и утверждать элементы все. Если вы потерпите неудачу (скажем) на первом элементе, у вас будет видимость нет для других тестовых случаев, в которых вы собираетесь потерпеть неудачу. Возможно, используйте потоки для сбора имени + логического значения, указывающего на достоверность, регистрируйте это и, наконец, подтверждайте любые сбои в вашем потоке...

Brian Agnew 13.03.2019 13:51

@Stultuske это не так просто, у методов разные сигнатуры

Andrew Tobilko 13.03.2019 13:51

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

Thomas 13.03.2019 13:51

Код @AndrewTobilko, который не является обычным, не является обычным кодом. ОП спросил, как обрабатывать общий код. Единственный «дублированный код» в показанном коде: assertTrue(isValid); поэтому немного избыточно пытаться уменьшить дублирующийся код

Stultuske 13.03.2019 13:54

Кстати, вы мог используете функциональные интерфейсы (при условии, что вы используете Java8+) для передачи методов, но ваш код, похоже, не имеет такого большого дублирования (единственные «дубликаты», которые я вижу, — это цикл и вызов assertTrue(isValid) — операторы печати используйте разные строки, поэтому они не совсем равны). Я не уверен, что изменение кода, который вы разместили, сильно поможет.

Thomas 13.03.2019 13:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
10
129
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Тот факт, что вы изо всех сил пытаетесь проверить, указывает на дизайн запах.

Самое время изучить шаблон разработки стратегии здесь.

В основном ваш основной код будет выглядеть примерно так

interface IValidator {
     boolean isValid(List<String> yourDataToBeValidated);
}

Теперь создайте несколько классов валидаторов для разных полей, таких как электронная почта, имя пользователя и т. д.

class EmailValidator implements IValidator {
      boolean isValid(List<String> yourDataToBeValidated){
         //Email specific validations goes here
      }
}

Вы можете создать больше валидаторов, если вам нужно на ходу.

Теперь в ваших модульных тестах создайте new EmailValidator() или new UsernameValidator() и передайте свой emailIds или usernames метод isValid(), как показано ниже:

    boolean isValid = new EmailValidator().isValid(Arrays.asList("[email protected]", "[email protected]");
    assertTrue(isValid);

Это на самом деле не решает проблему под рукой.

Stultuske 13.03.2019 14:01

@Stultuske op следовал плохому дизайну, и поэтому его трудно протестировать. Я просто предлагаю подход, который легче поддерживать и тестировать.

Shanu Gupta 13.03.2019 14:03

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

Stultuske 13.03.2019 14:05
interface IValidator(){ не является синтаксисом Java
Andrew Tobilko 13.03.2019 14:50

Как я уже говорил в своем комментарии к вашему вопросу, я не уверен, что изменение вашего кода сильно поможет. Однако для сравнения приведена версия Java8+, в которой используется общий метод:

@Test
void testCorrectEmailValidator() {
  List<String> correctEmails = Arrays.asList("[email protected]", "[email protected]", "[email protected]",
          "[email protected]", "[email protected]", "[email protected]");

  testValidator( "Email", correctEmails , email -> UserCredentialsValidator.emailValidator(email) );
}

@Test
void testCorrectUsernameValidator() {
  List<String> correctUsernames = Arrays.asList("username", "123username", "username3", "user2name",
        "USERNAME", "USERNAME123", "123USERNAME123", "2uSERname33");

  //I don't know where userList does come from but it would need to be final to be used here
  testValidator( "Username", correctUsernames, username -> UserCredentialsValidator.usernameValidation(username, userList) );
}

void testValidator( String name, List<String> data, Predicate<String> validator) {
  for( String element : data ) {
    boolean isValid = validator.test( element );
    System.out.println( name + " is valid:    " + element + "     : " + isValid);
    assertTrue(isValid);
  }
}

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

Ответ принят как подходящий

Боюсь, ваши тесты низкого качества.

Проблемы, которые должны быть устранены немедленно, включают

  1. UserCredentialsValidator.usernameValidation(username, userList); Метод не должен принимать второй аргумент. Место, откуда извлекается этот список, должно быть скрыто от потребителя API.

  2. List<String> correctEmails = Arrays.asList(...) и List<String> correctUsernames = Arrays.asList(...) следует удалить. Вам лучше сделать тесты параметризованными с помощью @ParameterizedTest и @ValueSource.

  3. Я бы предпочел удалить утверждения System.out.println. От них мало толку в тестах.


@ParameterizedTest
@ValueSource(strings = {"[email protected]", "[email protected]"})
void testUserEmailValidationWithValidUserEmailShouldPass(String validUserEmail) {
    boolean isValid = UserCredentialsValidator.emailValidator(validUserEmail);
    assertTrue(isValid);
}

@ParameterizedTest
@ValueSource(strings = {"username", "123username"})
void testUserNameValidationWithValidUserNameShouldPass(String validUserName) {
    boolean isValid = UserCredentialsValidator.usernameValidation(validUserName);
    assertTrue(isValid);
}

Теперь нечего уменьшать.

#3: После запуска usernameValidation вызывается метод findUser, который принимает List<User> в качестве параметра. Я не мог придумать решение, как передать этот список методу findUser. Я делаю какое-то консольное приложение, а List<User> является статической переменной в основном классе.

javamat 13.03.2019 14:21

@javamat Приятель, ты должен придумать еще один. Честно говоря, ваш подход отстой. Глобальная общедоступная статическая переменная никогда не бывает хорошим знаком. Ограничьте область действия этой переменной, чтобы она была доступна только из нескольких мест (проверка, поиск пользователем). Получите список внутри usernameValidation, не передавайте его методу.

Andrew Tobilko 13.03.2019 14:29

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

javamat 13.03.2019 14:44

@javamat да, это имело бы смысл. Это может быть класс UserStorage/UserRepository (красивое имя, вдохновленное Spring), который инкапсулирует этот список и предоставляет некоторые необходимые операции, связанные со списком.

Andrew Tobilko 13.03.2019 14:48

Используйте параметризованные тесты:

static Stream<String> emailsSource() {
    return Stream.of("[email protected]", "[email protected]", "[email protected]",
        "[email protected]", "[email protected]", "[email protected]");
}

@Test
@MethodSource("emailsSource")
void testCorrectEmailValidator(String email) {
    boolean isValid = UserCredentialsValidator.emailValidator(email);
    assertTrue(isValid);
}

Повторите для usernameSource и т. д. ИМХО, этого достаточно, чтобы исключить дублирование.

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

static Stream<Pair<String,Predicate<String>>> allSources() {
    return Stream.of(
        Pair.of("[email protected]", UserCredentialsValidator::emailValidator),
        Pair.of("username", UserCredentialsValidator::usernameValidationOneArg), // like usernameValidation but with argument userList fixed
        ...
    );
}

@Test
@MethodSource("allSources")
void testAll(Pair<String,Predicate<String>> p) {
    String s = p.getLeft();
    Predicate<String> test = p.getRight();
    boolean isValid = test.apply(email);
    assertTrue(isValid);
}

Я никогда раньше не использовал @MethodSource, должен ли он относиться исключительно к статическому методу?

Andrew Tobilko 13.03.2019 14:33

Другие вопросы по теме