【问题标题】:How to write a junit to verify if an exception thrown by the method is caught?如何编写一个junit来验证方法抛出的异常是否被捕获?
【发布时间】:2019-04-26 05:59:48
【问题描述】:

我的 Spring Boot 应用程序中有以下代码,用于验证电子邮件地址

class EmailValidation {

    public static void validate(List<String> s){
        try {
            for (String address : s) {
                if (s == null || s.indexOf("@") < 0) {  
                    throw new InvalidEmailAddressException("Email address is invalid ");
                }
                new InternetAddress(s);
            }
        } catch(AddressException e){
            LOGGER.Error("Please validate email addresses");
        }
        catch(InvalidEmailAddressesException e){
            LOGGER.error(e.getMessage());
        }
    }

    class InvalidEmailAddressException extends Exception {

        public InvalidEmailAddressException(String message) {
            super(message)
        }
    }
}

我想编写一个 Junit 测试来验证 InvalidEmailAddressesException 是否被抛出并被 CAUGHT。如何在 JUnit 中做到这一点?

【问题讨论】:

  • 为什么需要这样的测试?如果抛出异常并且该异常存在catch,它将被捕获。它不可能绕过那个 catch 块。你想完成什么?
  • 好吧,对于初学者,您首先不会将异常用于非异常条件。输入无效电子邮件的人并非异常行为。您将异常用作正常控制流的一部分,但它们并非用于此目的。
  • 上面的 cmets 已经暗示了这一点,但是您要测试的是内部 java 行为,例如当抛出异常时,该异常被捕获。已经写了许多测试:) 你粘贴的 sn-p 也有一些问题,但我假设这些是复制粘贴错误:)
  • 您不应该测试内部实现细节。你测试行为。在这种情况下,您唯一可以测试的是记录了错误。为什么你的方法会创建一个 InternetAddress 却什么也不做呢?

标签: java spring junit exception-handling


【解决方案1】:

总的来说,我同意 cmets 的观点,认为这样的测试可能是不必要的。

但是,如果我想测试类似的东西,我会分别测试这两种情况,这需要对您的代码进行少量修改。

首先,我会构造一个只有在有异常时才抛出异常的方法。

public static void checkAddresses(List<String> s) throws AddressException, InvalidEmailAddressException {
    for (String address : s) {
         if (s == null || s.indexOf("@") < 0) {  
             throw new InvalidEmailAddressException("Email address is invalid ");
         }
         new InternetAddress(s);
    }
}

那么我会在你的代码中这样使用它:

class EmailValidation {

    public static void validate(List<String> s){
         try {
             checkAddresses(s); // a wrapper method that throws the expected exceptions
         } catch(AddressException e){
             LOGGER.Error("Please validate email addresses");
         }
         catch(InvalidEmailAddressesException e){
             LOGGER.error(e.getMessage());
         }
     }

     // add checkAddresses here or somewhere appropriately

     class InvalidEmailAddressException extends Exception {

         public InvalidEmailAddressException(String message) {
             super(message)
         }
     }

}

然后,我将为checkAddresses 编写单独的测试,测试是否存在expected 异常和validate 的单独测试,(可能与checkAddresses 的输入相同)应该如果没有抛出异常,则通过。

另外,如果您想验证您的日志,您可以尝试类似that。

【讨论】:

    【解决方案2】:

    确实为共同原因使用java异常被认为是一种不好的做法,正如@Michael所说,异常必须是异常的,因为

    • 他们打破了流量控制
    • 他们很慢(更多细节在这里How slow are Java exceptions?)
    • 它们不与函数范式混合(Java 部分是通过添加 lamda 表达式来实现的

    但是,创建一个用于包装验证数据的自定义对象是一件好事,InvalidEmailAddressException 可以变成CheckedEmail:

    import java.util.List;
    import java.util.stream.Collectors;
    
    public class EmailValidator {
    
        public List<CheckedEmail> validate(List<String> emailAddresses) {
            return emailAddresses.stream().map(this::validate).collect(Collectors.toList());
        }
    
        public CheckedEmail validate(String emailAddress) {
            String[] emailParts = emailAddress.toString().split( "@", 3 );
            final boolean valid;
            if ( emailParts.length != 2 ) {
                valid = false;
            } else {
                // More validation can go here using one or more regex
                valid = true;
            }
            return new CheckedEmail(emailAddress, valid);
        }
    
        public static final class CheckedEmail {
            private final String emailAddress;
            private final boolean valid;
    
            private CheckedEmail(String emailAddress, boolean valid) {
                this.emailAddress = emailAddress;
                this.valid = valid;
            }
    
            public String getEmailAddress() {
                return emailAddress;
            }
    
            public boolean isValid() {
                return valid;
            }
        }
    }
    

    这反过来可以很容易地进行测试(并通过参数化测试进行改进):

    import static org.assertj.core.api.Assertions.assertThat;
    
    import java.util.Arrays;
    import java.util.List;
    
    import org.junit.Test;
    
    public class EmailValidatorTest {
    
        private final EmailValidator emailValidator = new EmailValidator();
    
        @Test
        public void invalid_email() {
            EmailValidator.CheckedEmail checkedEmail = emailValidator.validate("missing.an.at.symbol");
    
            assertThat(checkedEmail.isValid()).isFalse();
        }
    
        @Test
        public void valid_email() {
            EmailValidator.CheckedEmail checkedEmail = emailValidator.validate("at.symbol@present");
    
            assertThat(checkedEmail.isValid()).isTrue();
        }
    
        @Test
        public void multiple_email_addresses() {
            List<String> emailAddresses = Arrays.asList("missing.an.at.symbol", "at.symbol@present");
    
            List<EmailValidator.CheckedEmail> checkedEmails = emailValidator.validate(emailAddresses);
    
            assertThat(checkedEmails)
                    .extracting(ce -> ce.getEmailAddress() + " " + ce.isValid())
                    .containsExactly(
                            "missing.an.at.symbol false",
                            "at.symbol@present true");
        }
    }
    

    如果某个地方只是为了记录这个,那么:

    List<EmailValidator.CheckedEmail> checkedEmails = emailValidator.validate(emailAddresses);
    
    checkedEmails.stream()
            .filter(ce -> !ce.isValid())
            .map(ce -> String.format("Email address [%s] is invalid", ce.getEmailAddress()))
            .forEach(logger::error);
    

    希望这会有所帮助!

    【讨论】:

      【解决方案3】:

      不要以这种方式进行测试。您应该只测试代码的指定行为,而不是其实现细节。

      如果您正在测试的方法委托给一个引发已检查异常的方法,并且您正在测试的方法也没有声明它throws该已检查异常,编译器 将强制该方法捕获异常。所以在这种情况下,单元测试是不必要的。

      如果您正在测试的方法委托给一个抛出 unchecked 异常的方法,请查阅该方法的规范以确定是否可以接受被测方法也抛出(传播)该异常例外。如果它传播异常是不可接受的,那么您应该创建一个测试用例,使委托的方法抛出该未经检查的异常。如果该方法传播异常,则测试用例将失败。怎么做?这取决于委托给的方法,但在大多数情况下,您将需要使用依赖注入来提供一个引发异常的模拟对象。

      【讨论】:

        猜你喜欢
        • 2021-10-28
        • 1970-01-01
        • 1970-01-01
        • 2019-08-30
        • 2011-04-08
        • 1970-01-01
        • 2013-04-09
        • 2016-05-05
        • 2010-10-14
        相关资源
        最近更新 更多