最近,我开始尝试单元测试以熟悉实践,并最终编写出更好的代码。我已经开始我的一个大项目,它有一个相当大的未经测试的代码库,但我只是单元测试实用程序类,它们没有很多依赖关系并且很容易测试。
我已经阅读了相当多的一般单元测试介绍 Material ,经常出现的是单元测试应该始终测试尽可能小的行为单元。也就是说,测试通过或失败应该只取决于一段非常具体的代码,比如一个小方法。
但是,我已经发现了将这个想法付诸实践的问题。考虑例如:
@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 的情况下检查是否相等。但是,我觉得这类问题将成为更复杂行为的真正问题,因为现有方法可能不起作用而避免使用现有方法将涉及重写大量仅用于测试的代码。
如何在不使单元测试相互依赖的情况下将这些需求转化为代码?
有可能吗?如果不是,我是否应该停止为这个问题烦恼,并假设所有未在当前测试下工作的方法?
最佳答案
However, say it fails. Now, my first test also should fail, but for a reason beyond its scope.
测试隔离是一件非常重要的事情,你是对的,但也要注意这有一个限制。
您将不可避免地陷入某些情况,在您的单元测试中,您将不得不依赖其他对象的其他通用方法,例如 equals
/hashCode
或仍然是构造函数。
这是不可避免的。有时,耦合不是问题。
例如,使用构造函数创建传递给被测方法的对象或模拟值是很自然的,真的不应该避免!
但在其他情况下,与另一个类的API耦合是不可取的。
例如,您的示例代码就是这种情况,因为您的测试依赖于 equals()
方法,测试的字段可能会随着时间的推移而变化,并且在功能上也不同于要在测试中观察/断言的字段方法。
此外,这不会使断言的字段显式。
在这种情况下,要通过被测方法断言返回对象的字段值,您应该通过 getter 方法逐个断言。
为了避免编写这种重复且容易出错的代码,我使用了匹配器测试库,例如 AssertJ或 Hamcrest (一次包含在 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);
就这么一个简单的例子,匹配器测试库没有实际值(value)。你可以直接做:
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);
关于java - 单元测试的相互依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48632157/