Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 单元测试的相互依赖性_Java_Unit Testing_Junit - Fatal编程技术网

Java 单元测试的相互依赖性

Java 单元测试的相互依赖性,java,unit-testing,junit,Java,Unit Testing,Junit,从最近开始,我一直在尝试单元测试,以熟悉这种做法,并最终编写更好的代码。我已经开始了我的一个大项目,有一个相当大的未经测试的代码库,但我只是单元测试实用程序类,它没有太多依赖项,并且非常容易测试 我已经阅读了很多关于单元测试的介绍性材料,经常出现的是单元测试应该总是测试最小的行为单元。也就是说,测试是否通过应该只取决于一段非常特定的代码,比如一个小方法 然而,我已经发现了将这一想法付诸实践的问题。例如,考虑: @Test public void testSomethingWithFoo() {

从最近开始,我一直在尝试单元测试,以熟悉这种做法,并最终编写更好的代码。我已经开始了我的一个大项目,有一个相当大的未经测试的代码库,但我只是单元测试实用程序类,它没有太多依赖项,并且非常容易测试

我已经阅读了很多关于单元测试的介绍性材料,经常出现的是单元测试应该总是测试最小的行为单元。也就是说,测试是否通过应该只取决于一段非常特定的代码,比如一个小方法

然而,我已经发现了将这一想法付诸实践的问题。例如,考虑:

@Test
public void testSomethingWithFoo() {
    Foo foo = new Foo(5);

    Bar result = foo.bar(); //Suppose this returns new Bar(42) for some reason.

    Bar expected = new Bar(42);
    Assert.assertEquals(expected, result); // Implicitly calls Bar.equals, which returns true if both Bars' constructor parameters are equal.
}
显然,这个测试依赖于两个元素:foo.bar()方法,还有由断言隐式调用的bar.equals(otherBar)方法

现在,我可以编写另一个测试,它断言这个bar.equals()方法工作正常。然而,我认为它失败了。现在,我的第一次测试也应该失败,但原因超出了它的范围

我的观点是,对于这个特殊的例子,没有什么真正的问题;我可以不用equals来检查是否相等。然而,我觉得这类问题将成为更复杂行为的实际问题,因为避免现有方法可能不起作用,这将涉及到仅为测试重写大量代码

如何将这些需求转化为代码,而不使单元测试相互依赖?


这有可能吗?如果不是,我是否应该停止担心这个问题,并假设所有方法都不在当前的测试工作中?

我发现将我的类分为两种主要类型很有帮助:

  • 保存数据的类型。它们表示数据的结构,几乎不包含逻辑
  • 包含逻辑且不包含状态的类型
  • 这样做的原因是,许多层上的许多类将不可避免地绑定到数据模型,因此您希望数据模型的表示是坚如磐石的,并且不太可能产生任何意外

    如果
    Bar
    是一个数据结构,那么依赖其
    .equals()
    方法的行为与依赖
    int
    String
    .equals()
    行为没有太大区别。你应该停止担心它

    另一方面,如果
    Bar
    是一个逻辑类,那么当您尝试测试
    Foo
    时,可能根本不应该依赖它的行为。事实上,
    Foo
    根本不应该创建一个
    Bar
    (除非
    Foo
    是一个
    BarFactory
    ,例如)。相反,它应该依赖于可以模拟的接口

    当涉及到测试数据类型时(特别是在数据类型确实需要一些逻辑的特殊情况下,如Uri类,您必须测试该类中的多个方法(如Bar的构造函数和equals方法).但是所有的测试都应该是专门测试那个等级的。确保你有足够的测试来保持这些坚如磐石

    在测试服务类(带有逻辑的服务类)时,您将有效地假设您正在处理的任何数据类型都经过了充分的测试,因此您真正测试的是您担心的类型(
    Foo
    )的行为,而不是您碰巧与之交互的其他类型(
    Bar

    但是,假设它失败了。现在,我的第一次测试也应该失败了,但是有一个原因 理性超出了它的范围

    测试隔离是一件非常重要的事情,您是对的,但请注意,这有一个局限性。
    在单元测试中,您将不得不依赖其他对象的其他通用方法,例如
    equals
    /
    hashCode
    或still构造函数 这是不可避免的。有时,耦合不是问题。
    例如,使用构造函数创建传递给被测方法的对象或模拟值是很自然的,不应该避免

    但在其他情况下,与另一个类的API耦合是不可取的。
    例如,在您的示例代码中,您的测试依赖于
    equals()
    方法,被测试的字段可能会随着时间的推移而改变,并且在功能上也不同于被测试方法中要监视/断言的字段。
    此外,这不会使断言的字段显式

    在这种情况下,要通过被测方法断言返回对象的字段值,您应该通过getter方法逐个字段地断言。
    为了避免编写这种重复且容易出错的代码,我使用了一个matcher测试库,比如or(一次包含在JUnit中),它提供了一种流畅而灵活的方式来断言实例的内容

    例如,使用AssertJ:

    Foo foo = new Foo(5);
    Bar actualBar = foo.bar();
    Assertions.assertThat(actualBar)
              .extracting(Bar::getId) // supposing 42 is stored in id field              
              .containsExactly(42);
    
    通过这样一个简单的示例,matcher测试库没有实际价值。您可以直接执行以下操作:

    Foo foo = new Foo(5);
    Bar actualBar = foo.bar();
    Assert.assertEquals(42, actualBar.getId())
    
    但对于要检查多个字段的情况(非常常见的情况),它非常有用:

    Foo foo = new Foo(5);
    Bar actualBar = foo.bar();
    Assertions.assertThat(actualBar)
              .extracting(Bar::getId, Bar::getName, Bar::getType)                
              .containsExactly(42, "foo", "foo-type);
    

    假设您有一个获取“42”的getter,断言
    result.get()==expected.get()
    (因为您不需要实现
    int
    )的相等方法;然后断言
    result.equals(expected)
    。我不知道你想把这些断言放在哪里;不管怎样,我的部分观点是,这很容易做到,但它假设了非常简单的对象。然而,假设这个条实际上有3个字段,每个字段都是我项目中另一个类的实例。要假设equals函数正确工作,我必须假设每个field的value的equals函数也能正常工作。当重点是测试equals函数本身时,这并没有问题,但我的问题是:如果我只是用它来测试其他东西呢?很有趣,因为我开始使用这种simp