Здравствуйте, я тестирую класс, в котором есть несколько методов проверки, и мне интересно, есть ли способ уменьшить дублированный код.
@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.
Не имеет отношения к вопросу, но почему есть список элементов для проверки? Если каждый элемент списка является крайним случаем, то дайте ему отдельный тест, например, validateUserNameWhenUserNameIsAllLowerCase().
проблема в том, что каждый цикл for использует другой метод проверки UserCredentialsValidator. Я не уверен, как извлечь его в метод, который можно было бы использовать в тестах.
@javamat UserCredentialsValidator.usernameValidation(username, userList) опечатка? почему он принимает 2 аргумента?
на самом деле этому методу требуется список, потому что он вызывает другой метод, который ищет пользователя в списке и возвращает его индекс, если он есть.
Комментарий о таких тестах. Я предпочитаю тестировать/отчитываться и утверждать элементы все. Если вы потерпите неудачу (скажем) на первом элементе, у вас будет видимость нет для других тестовых случаев, в которых вы собираетесь потерпеть неудачу. Возможно, используйте потоки для сбора имени + логического значения, указывающего на достоверность, регистрируйте это и, наконец, подтверждайте любые сбои в вашем потоке...
@Stultuske это не так просто, у методов разные сигнатуры
Вы передаете в своем имени пользователя а также список? Это похоже на проблему безопасности. Обычно я ожидаю, что валидатор получит доступных пользователей из какого-то другого (надежного) источника.
Код @AndrewTobilko, который не является обычным, не является обычным кодом. ОП спросил, как обрабатывать общий код. Единственный «дублированный код» в показанном коде: assertTrue(isValid); поэтому немного избыточно пытаться уменьшить дублирующийся код
Кстати, вы мог используете функциональные интерфейсы (при условии, что вы используете Java8+) для передачи методов, но ваш код, похоже, не имеет такого большого дублирования (единственные «дубликаты», которые я вижу, — это цикл и вызов assertTrue(isValid) — операторы печати используйте разные строки, поэтому они не совсем равны). Я не уверен, что изменение кода, который вы разместили, сильно поможет.




Тот факт, что вы изо всех сил пытаетесь проверить, указывает на дизайн запах.
Самое время изучить шаблон разработки стратегии здесь.
В основном ваш основной код будет выглядеть примерно так
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 op следовал плохому дизайну, и поэтому его трудно протестировать. Я просто предлагаю подход, который легче поддерживать и тестировать.
нет, его тесты вообще не проблема, они работают. у него (почти) дублированный код в одном месте, вместо того, чтобы распространяться по разным методам тестирования, которые он пытается выполнить. Однако это не значит, что вы ошибаетесь в подходе.
interface IValidator(){ не является синтаксисом Java
Как я уже говорил в своем комментарии к вашему вопросу, я не уверен, что изменение вашего кода сильно поможет. Однако для сравнения приведена версия 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 строки, в то время как второй может быть проще для повторного использования, но сложнее для понимания и менее гибкий (например, если вам нужно будет передать дополнительные параметры и т. д.).
Боюсь, ваши тесты низкого качества.
Проблемы, которые должны быть устранены немедленно, включают
UserCredentialsValidator.usernameValidation(username, userList); Метод не должен принимать второй аргумент. Место, откуда извлекается этот список, должно быть скрыто от потребителя API.
List<String> correctEmails = Arrays.asList(...) и List<String> correctUsernames = Arrays.asList(...) следует удалить. Вам лучше сделать тесты параметризованными с помощью @ParameterizedTest и @ValueSource.
Я бы предпочел удалить утверждения 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 Приятель, ты должен придумать еще один. Честно говоря, ваш подход отстой. Глобальная общедоступная статическая переменная никогда не бывает хорошим знаком. Ограничьте область действия этой переменной, чтобы она была доступна только из нескольких мест (проверка, поиск пользователем). Получите список внутри usernameValidation, не передавайте его методу.
Я понимаю. Было бы хорошей идеей создать класс UserList, который просто хранит список пользователей? Делая это таким образом, я мог бы удалить второй параметр и в методе findUser получить доступ к списку с классом UserList
@javamat да, это имело бы смысл. Это может быть класс UserStorage/UserRepository (красивое имя, вдохновленное Spring), который инкапсулирует этот список и предоставляет некоторые необходимые операции, связанные со списком.
Используйте параметризованные тесты:
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, должен ли он относиться исключительно к статическому методу?
извлекать общий код в методах так же, как в обычном коде