单元测试 equals 和 hashcode - 一个复杂的故事

发布于 2024-11-02 21:08:20 字数 1184 浏览 4 评论 0原文

我陷入了道德困境。我的应用程序中有一些值对象,它们是不可变的并且非常简单。我已经使用 IDE(在我的例子中是 intellij)生成了 equals 和 hashcode,但这样做导致代码覆盖率下降,而且报告现在表明这些值对象非常复杂(使用 圈复杂度 指标),而事实上它们非常简单。

作为示例,以下 equals 位于具有 3 个不可变属性的值对象中。代码复杂度为 14 (javaNCSS),有 26 个执行分支 (Cobertura)。我还应该补充一点,如果任何方法的复杂性大于 10,我都会失败。

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    TranscriptTaskDetails that = (TranscriptTaskDetails) o;

    if (inputFile != null ? !inputFile.equals(that.inputFile) : that.inputFile != null) {
        return false;
    }
    if (language != that.language) {
        return false;
    }
    if (outputFile != null ? !outputFile.equals(that.outputFile) : that.outputFile != null) {
        return false;
    }

    return true;
}

我想知道其他开发人员使用什么来规避这个问题,因为我非常关注复杂性报告,根据我的经验高复杂度指标与更多错误相关,因此这种自动生成的等于和哈希码会污染报告。

我正在考虑使用 apache commons-lang 中的 EqualsBuilder 和 HashcodeBuilder 来规避这个问题,但我并不是 100% 高兴:S。

编辑

我应该添加我为这个项目编写的部分代码是一个将由其他业务部门使用的库......并且也将由不同的团队维护:S。

I'm having a moral dilemma. I have some value objects in my application, which are immutable and extremely simple. I've generated the equals and hashcode with an IDE (intellij in my case) but doing that, made the code coverage drop, plus the reports now indicate that those value objects are very complex (using the cyclomatic complexity metric) when in fact they're dead simple.

As an example, the following equals is in a value object that has 3 immutable attributes. The Code complexity is 14 (javaNCSS) and it has 26 execution branches (Cobertura). I should add too, that I fail the build if any method has a complexity greater than 10.

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    TranscriptTaskDetails that = (TranscriptTaskDetails) o;

    if (inputFile != null ? !inputFile.equals(that.inputFile) : that.inputFile != null) {
        return false;
    }
    if (language != that.language) {
        return false;
    }
    if (outputFile != null ? !outputFile.equals(that.outputFile) : that.outputFile != null) {
        return false;
    }

    return true;
}

I'm wondering what other devs use to circumvent this, as I pay quite a lot of attention to the complexity reports, as in my experience a high complexity metric relates to more bugs, so this auto-generated equals and hashcodes are polluting the reports.

I'm thinking of using EqualsBuilder and HashcodeBuilder from apache commons-lang to circumvent this, but I'm not 100% happy :S.

Edit

I should have added that part of the code I'm writing for this project is a library that will be used by other business units... And will be maintained by a different team too :S.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

好多鱼好多余 2024-11-09 21:08:20

我正在考虑使用 apache commons-lang 中的 EqualsBuilder 和 HashcodeBuilder 来规避这个问题,但我并不是 100% 高兴:S。

为什么不使用这些呢?

使用它们可以减少您自己的方法的大小和复杂性,并且更容易直观地验证 equals 和 hashCode 的实现是否一致。

这当然也是一个好主意,并且很容易测试 equals/hashCode 契约是否得到满足,因此无论如何都要编写测试。

I'm thinking of using EqualsBuilder and HashcodeBuilder from apache commons-lang to circumvent this, but I'm not 100% happy :S.

Why not use these?

Using them reduces the size and complexity of your own methods and it's much easier to verify visually that equals and hashCode are implemented consistently.

It's of course also a good idea and pretty easy to test that the equals/hashCode contract is satisfied, so by all means, write tests.

够钟 2024-11-09 21:08:20

我认为您真正的问题是过于相信人为测量,例如代码覆盖圈复杂度。

简单的事实是:

  • 生成的代码是正确的......以您选择了正确的平等模型为模,并且
  • 生成的代码并不复杂......或者至少没有比需要的更复杂。

学会相信自己的判断,并停止依赖工具来为您做出设计决策。


我刚才所说的推论是,如果您认为可以(并且应该)简化生成的代码,同时仍然确保它对于您当前和预计的用例是正确的,那么请继续简化它。


顺便说一句,生成的 equals / hashcode 对可能比手写的更正确......由“普通熊”程序员编写。例如,请注意生成的代码处理空字段和类型比较的谨慎方式。许多开发人员都无法做到这一点。

I think that your real problem is placing too much faith in artificial measure such as code coverage cyclomatic complexity.

The plain facts are that:

  • the generated code is correct ... modulo that you have chosen the correct model of equality, and
  • the generated code is not complicated ... or at least, no more complicated than it needs to be.

Learn to trust your own judgment, and stop relying on tools to make your design decisions for you.


And the corollary of what I just said is that if you think you can (and should) simplify the generated code while still ensuring that it is correct for your current and projected use cases, go ahead and simplify it.


By the way, a generated equals / hashcode pair are probably more likely to be more correct than a hand written one ... written by an "average bear" programmer. For instance, note the careful way that the generated code handles null fields and the comparison of the types. A lot of developers wouldn't get those right.

骄傲 2024-11-09 21:08:20

这里有一个矛盾。您根本没有任何理由担心自动生成代码的复杂性。您应该担心的是手工编码。

There's a contradiction here. You don't have any reason at all to worry about the complexity of auto-generated code. It's the hand coding you should be worrying about.

拥有 2024-11-09 21:08:20

您的代码覆盖率有多高?有些人认为,100% 的命中率是肛门保留倾向的标志。如果你在 70-80% 的范围内,并且你知道你没有测试过的东西不是问题,那为什么还要担心呢?

另一方面,这些测试编写起来并不困难。为什么不写下来,完成它,然后晚上睡觉呢?在此处输入您的道德困境并等待答案所需的时间内,您就已经完成了测试。

How high is your code coverage? Some people argue that shooting for 100% is a sign of anal retentive tendencies. If you're in the 70-80% range, and you know that what you haven't tested isn't a problem, then why worry about it?

On the other hand, these tests aren't that difficult to write. Why not write them, be done with it, and sleep at night ? You would have finished the test in the time it took to type your moral dilemma here and waiting for answers.

最笨的告白 2024-11-09 21:08:20

我认为您的低复杂性和高覆盖率的目标令人钦佩。我发现 Google Guava 库在解决此类问题时非常有用(也非常适合其他问题。)您可以编写以下内容:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    TranscriptTaskDetails that = (TranscriptTaskDetails) o;
    return Objects.equal(inputFile, that.inputFile) && Objects.equal(language, that.language) && Objects.equal(outputFile, that.outputFile);

}

然后您可以使用 EqualsTester 快速覆盖此方法。您可以获得简单性和测试覆盖范围。感谢这个问题帮助我找到了 EqualsTester。

我最近开始使用的另一个选项是 Lombok。 Lombok 是一个用于编写不可变值对象的出色库。这是一个示例 lombok 类:

@Data
public class Value {
    private final String field1;
    private final Foo field2;
}

@Data 注释使 lombok 为您生成一堆代码。您将获得 getters(以及非最终字段)setters。您将获得 equals、hashCode 和 toString。你会得到一个全参数构造函数。当您更改字段列表时,所有这些方法都会自动更新。我已经用 Lombok 从我当前的项目中修剪了数百行,我的值对象现在总是完全实现。

I think your goals of low complexity and high coverage are admirable. I've found that the Google Guava library is quite useful in this kind of problem (and also great for other problems.) You could write the following:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    TranscriptTaskDetails that = (TranscriptTaskDetails) o;
    return Objects.equal(inputFile, that.inputFile) && Objects.equal(language, that.language) && Objects.equal(outputFile, that.outputFile);

}

You could then use the EqualsTester to quickly get coverage of this method. You get simplicity and testing coverage. Thanks to this SO question for helping me find EqualsTester.

Another option that I've recently started using is Lombok. Lombok is a great library for writing immutable value objects. Here is a sample lombok class:

@Data
public class Value {
    private final String field1;
    private final Foo field2;
}

The @Data annotation causes lombok to generate a bunch of code for you. You get getters (and for non-final fields) setters. You get equals, hashCode, and toString. You get an all args constructor. And all those methods are automatically updated when you change the field list. I've trimmed hundreds of lines from my current project with Lombok and my value objects are now always completely implemented.

两相知 2024-11-09 21:08:20

简单的 POJO——无论有没有构建器都是不可变的,或者是可变的,最多首先从简单的 DSL 描述自动生成,直到 Java 获得对这个简单但重要的功能的直接语言支持。

Simple POJOs - immutable with or without builders, or mutable, are at best automatically generated from a simple DSL description in the first place, until Java gets direct language support for this simple, yet important feature.

最美不过初阳 2024-11-09 21:08:20

在我看来,看到代码覆盖率下降表明您应该查看代码并亲自找出覆盖率下降是否合理。生成的 equals 或 hashcode 之类的代码指标并不重要,它们非常简单,可以正常工作。对于简单的 getter 和 setter 也是如此,谁真正关心其中一些是否未被覆盖? (这可能是其他一些未测试的事情的症状,但这不是重点)。

工具是为了帮助我们编写好的代码和应用程​​序......我们不应该成为它们的奴隶。

In my opinion, seeing code coverage drop is an indicator that you should have a look at the code and find out by yourself if the drop in coverage is justified. Code metrics on things like generated equals or hashcode are not really important, they are so simple that they just work. Same for simple getters and setters, who really cares if some of them are not covered? (That may be a symptom of some other thing not being tested though, but that is beside the point).

Tools are here to help us write good code and applications... we should not be slaves to them.

伴随着你 2024-11-09 21:08:20

您还可以通过确保某些字段不能为空来降低复杂性,因为它没有意义。您可以在构造时强制执行此操作,并使其值或整个对象不可变。这节省了 equals 代码中的大量 null 检查。

同时,您的类变得更容易推理,并且任何依赖类都不必处理某些字段为空的情况,从而降低了它们的复杂性。

You can reduce complexity as well by making sure certain fields cannot be null because it makes no sense. You can enforce this at construction time and have their values or the complete object be immutable. This saves a lot of null checks in the equals code.

At the same time, your class becomes easier to reason about, and any dependent classes don't have to deal with some fields being null, in turn reducing their complexity.

一身骄傲 2024-11-09 21:08:20

如果您正在寻找一些工具来弥补覆盖范围并确保 equals 和 hashcode 正确实现,您可以尝试 https:// jqno.nl/equalsverifier/

我看到您只有不可变字段,这应该更容易。

您可以尝试自动检测并生成 Junit 测试用例缺失的断言(也称为测试放大)的其他操作:
https://github.com/STAMP-project/dspot
PS:我刚刚开始探索这些项目。

If you are looking for some tool to make up coverage and ensure equals and hashcode is implemented properly, You can try https://jqno.nl/equalsverifier/

I see that you are having immutable fields only and this should be easier.

Additional things you can try to Automatically detect and generate missing assertions for Junit test cases (also known as test amplification):
https://github.com/STAMP-project/dspot
PS: I have just started exploring the projects.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文