在Java中将静态方法作为参数传递

6

您好,我正在测试一些验证方法的类,我想知道是否有办法减少重复的代码。

@Test
void testCorrectEmailValidator() {
    List<String> correctEmails = Arrays.asList("test@test.com", "test123@test123.com", "test@test.com.in",
            "test.test2@test.com", "test.test2.test3@test.com", "TEST.2test@test.com");

    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);
    }
}

我还有其他字段的验证器,例如用户名等。我考虑实现一个帮助方法,它将接受:已测试的凭据作为字符串、一个列表,但是我遇到了最后一个参数——验证方法的问题,不确定如何传递它。

我想用一些方法来替换这个for循环的代码。


1
提取方法中的公共代码,就像在常规代码中一样。 - Stultuske
问题在于每个for循环都使用了不同的UserCredentialsValidator验证方法。我不确定如何将其提取到可以在测试中共享的方法中。 - javamat
@javamat,UserCredentialsValidator.usernameValidation(username, userList) 是打字错误吗?为什么它需要两个参数? - Andrew Tobilko
1
@AndrewTobilko 不常见的代码不是常见的代码。OP问如何处理常见代码。在所示代码中,唯一的“重复代码”是:assertTrue(isValid); 因此,试图减少重复代码有点多余。 - Stultuske
顺便说一下,你可以使用函数接口(假设你正在使用Java8+)来传递方法,但是你的代码似乎没有那么多重复(我看到的唯一“重复”的部分是循环和assertTrue(isValid)调用 - 打印语句使用不同的字符串,因此它们并不完全相等)。我不确定重新排列你发布的代码会有多大帮助。 - Thomas
显示剩余5条评论
4个回答

4

很抱歉,您的测试质量较低。

需要立即修复的问题包括:

  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 = {"test@test.com", "test123@test123.com"})
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>是Main类中的静态变量。 - javamat
@javamat 哥们,你得想出另一个方法。说实话,你的方法太烂了。全局公共静态变量从来不是一个好兆头。限制该变量的作用域,使其仅能从几个地方访问(验证、用户检索)。在 usernameValidation 中获取列表,而不要将其传递给该方法。 - Andrew Tobilko
我明白了。创建一个仅存储用户列表的UserList类是否是个好主意?通过这种方式,我可以删除第二个参数,并在findUser方法中使用UserList类访问列表。 - javamat
@javamat 是的,那样做很有意义。可以创建一个类 UserStorage/UserRepository(一个不错的受 Spring 启发的名称),封装该列表并提供所需的一些特定列表操作。 - Andrew Tobilko

0

你若是在测试时遇到了困难,这可能意味着存在设计上的问题。

现在是一个很好的机会来探索策略设计模式

基本上,你的主要代码应该长成这个样子:

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()并将您的emailIdsusernames传递给isValid()方法,就像下面这样:

    boolean isValid = new EmailValidator().isValid(Arrays.asList("test@test.com", "test123@test123.com");
    assertTrue(isValid);

1
这并没有真正解决手头的问题。 - Stultuske
@Stultuske的代码遵循了糟糕的设计,因此很难进行测试。我只是建议一种更易于维护和测试的方法。 - Shanu Gupta
不,他的测试并不是问题所在,它们是有效的。问题在于将(几乎)重复的代码放在一个地方,而不是分散在不同的测试方法中。但这并不意味着你的方法是错误的。 - Stultuske
interface IValidator(){ 不是 Java 语法。 - Andrew Tobilko

0

正如我在评论中所述,我不确定重新排列代码是否会有很大帮助。然而,作为比较,这里是一个使用常见方法的Java8+版本:

@Test
void testCorrectEmailValidator() {
  List<String> correctEmails = Arrays.asList("test@test.com", "test123@test123.com", "test@test.com.in",
          "test.test2@test.com", "test.test2.test3@test.com", "TEST.2test@test.com");

  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行长,而第二种方法可能更易于重用,但更难理解并且不够灵活(例如,如果您需要传递其他参数等)。

0

使用参数化测试:

static Stream<String> emailsSource() {
    return Stream.of("test@test.com", "test123@test123.com", "test@test.com.in",
        "test.test2@test.com", "test.test2.test3@test.com", "TEST.2test@test.com");
}

@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("test@test.com", 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接