Lombok的@NonNull注解与@Builder一起在测试覆盖率中未反映出来。

huangapple go评论91阅读模式
英文:

Lombok @NonNull annotation with @Builder not reflecting in test coverage

问题

我在测试使用 lombok 注解的类时遇到了一个问题。
描述:-

当我们使用 @Builder 注解一个 Java 类(POJO),并且在某些实例变量上使用 @NonNull 约束时,当编写测试用例来检查 NullPointerException 时,我们不能使用构建器实例化 POJO 并期望它将与空值检查进行比较。我对该类进行了去 Lombok 处理,并发现非空约束流入生成的构建器类本身,这意味着在构造对象之前甚至会抛出空指针异常。从某种意义上说,这是正确的行为,但我需要知道是否可以测试这种情况。

@Builder
public class Sample {
    @NonNull
    private final String a;
    private final String b;
}

现在我需要测试如果 a 为 Null 时会抛出 NullPointerException 的情况。
对于这种情况,我有两个选项:

  • 要么我可以使用构造函数创建 Sample 类 - new Sample(null, null)
  • 要么我可以使用构建器创建 - Sample.builder.a(null).build();

我的问题在于第二部分,因为第一部分将正常工作。
当我去除 Lombok 注解时,我会得到类似以下的结果:

public class Sample {
    @NonNull
    private final String a;
    private final String b;

    Sample(@NonNull String a, String b) {
        this.a = a;
        this.b = b;
    }

    public static SampleBuilder builder() {
        return new SampleBuilder();
    }

    public static class SampleBuilder {
        private @NonNull String a;
        private String b;

        SampleBuilder() {
        }

        public SampleBuilder a(@NonNull String a) {
            this.a = a;
            return this;
        }

        public SampleBuilder b(String b) {
            this.b = b;
            return this;
        }

        public Sample build() {
            return new Sample(a, b);
        }

        public String toString() {
            return "Sample.SampleBuilder(a=" + this.a + ", b=" + this.b + ")";
        }
    }
}

在这里,如果你注意到,NullPointerException 将在 SampleBuilder 内部抛出,因为它接受 @NonNull 参数,构造函数将永远不会执行非空属性的检查条件,因此测试覆盖率会下降。如果我们使用 @SuperBuilder,则不会发生这种情况,因为它不接受构建器参数中的 @NonNull

英文:

I have come across an issue while testing lombok annotated class.
Description :-

When we annotate a java class(POJO) with @Builder, and have certain instance variable with @NonNull constraints, while writing the test case to check for NullPointerException, we cannot instantiate the POJO with builder and expect it will evaluate against the null check.I delomboked the class and saw that the non null constraint flows inside the generated builder class itself which means a null pointer exception is thrown even before we construct the object.This is a correct behavior in some sense but I needed to know if I can test such a scenario.

@Builder
public class Sample {
    @NonNull
    private final String a;
    private final String b;
}

Now I need to test the case where NullPointerException is thrown in case 'a' is Null.
For such a scenario I have 2 options:

  • Either I can create the Sample class with constructor - new Sample(null, null)
  • Or I can create through builder - Sample.builder.a(null).build();

My question is on the second part as first one will work just fine.
When I delombok this I will get something like :-

public class Sample {
    @NonNull
    private final String a;
    private final String b;

    Sample(@NonNull String a, String b) {
        this.a = a;
        this.b = b;
    }

    public static SampleBuilder builder() {
        return new SampleBuilder();
    }

    public static class SampleBuilder {
        private @NonNull String a;
        private String b;

        SampleBuilder() {
        }

        public SampleBuilder a(@NonNull String a) {
            this.a = a;
            return this;
        }

        public SampleBuilder b(String b) {
            this.b = b;
            return this;
        }

        public Sample build() {
            return new Sample(a, b);
        }

        public String toString() {
            return "Sample.SampleBuilder(a=" + this.a + ", b=" + this.b + ")";
        }
    }
}

Here if you see NullPointerException will be thrown inside SampleBuilder itself as it takes @NonNull arguments and constructor will never execute the condition to check for non null attribute due to which test coverage will fall.If we use @SuperBuilder , this will not happen as it does not take @NonNull in builder arguments.

答案1

得分: 0

以下是您要翻译的内容:

"不太确定您期望什么,但下面的源代码可以正确测试Sample类的构造函数和构建器的null检查。

您可以在https://repl.it/repls/CleverWiryTruespace上运行它。

import lombok.NonNull;
import lombok.Builder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Before;
import org.junit.rules.ExpectedException;
import org.junit.runner.JUnitCore;

class Main {
  @Builder
  public static class Sample {
      @NonNull
      private final String a;
      private final String b;
  }

  public static class TestSample {
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Before
    public void setUp() {
      thrown.expect(NullPointerException.class);
      thrown.expectMessage("a is marked non-null but is null");
    }

    @Test
    public void test1() {
      Sample s = new Sample(null, "abc");
    }

    @Test
    public void test2() {
      Sample.builder().a(null).build();
    }
  }

  public static void main(String[] args) {
    new JUnitCore().main(TestSample.class.getName());
  }
}
英文:

Not really sure what you are expecting, but the source code below can properly test the null check of both constructor and builder of Sample class.

You can see it running at https://repl.it/repls/CleverWiryTruespace.

import lombok.NonNull;
import lombok.Builder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Before;
import org.junit.rules.ExpectedException;
import org.junit.runner.JUnitCore;

class Main {
  @Builder
  public static class Sample {
      @NonNull
      private final String a;
      private final String b;
  }

  public static class TestSample {
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Before
    public void setUp() {
      thrown.expect(NullPointerException.class);
      thrown.expectMessage("a is marked non-null but is null");
    }

    @Test
    public void test1() {
      Sample s = new Sample(null, "abc");
    }

    @Test
    public void test2() {
      Sample.builder().a(null).build();
    }
  }

  public static void main(String[] args) {
    new JUnitCore().main(TestSample.class.getName());
  }
}

答案2

得分: 0

从代码中我们可以看到,如果我们想在构造函数级别触发NPE,我们需要将@NonNull参数留空,而不是将其设置为null,这将触发构建器方法级别的NPE。
如果我以以下方式构建Sample对象,应该足以满足我的要求:

Sample sample = Sample.builder.b("b").build();

在这里,如果我想在构造函数级别测试参数'a'的NPE,我没有明确地将参数'a'设置为NULL。

英文:

From the code we can see that if we want to trigger NPE at constructor level we need to leave the @NonNull parameter as empty rather than setting it to null which will trigger builder method level NPE.
If I build Sample object in the following manner it should suffice my case:-

Sample sample = Sample.builder.b("b").build();

Here I am not explicitly setting parameter 'a' to NULL if I want to test NPE of 'a' at constructor level.

huangapple
  • 本文由 发表于 2020年7月28日 07:21:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/63124975.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定