关于单元测试概念的问题:
class A {
public void m1() {
// code
m2();
//code
}
public void m2() {
//some code
}
}
根据最佳做法,我应该如何测试m1
方法?单位是类还是方法的单位?
我的观点 - 我应该单独测试m2
,我不应该测试m1
和m2
集成。
使用我的观点很难 - 我应该使用复杂的框架来测试和使用非常现代的东西。
根据我的单元测试感,测试应该很简单!如果您的代码是好的,您可以在没有复杂功能的情况下进行测试但是在m1()中调用m2()是正常的代码。
请澄清我的误解。
模拟示例(伪代码):
//prepare
A testClass = mock(A.class);
when(testClass.m2()).doNothing();
when(testClass.m1()).callRealMethod();
//execute:
testClass.m1();
//verify
check testClass state after method m1 invocation.
这就是我测试一个模拟类的方法。这是正常的吗?
答案 0 :(得分:3)
首先,在进行单元测试时,测试所有公共方法。在您的示例中,m1
和m2
是公开的,因此您可以对两者进行测试。
您可能想要存根或模拟m2
有几个原因:
如果在测试m1
时遇到任何问题,因为m1
调用m2
,存根或模拟m2
。您可能遇到的一些问题:
m2
可能会调用外部服务m2
可能只是很慢m1
的参数调用m2
(您的m2
没有参数,但我通常会说话)有时候,当你测试一个调用另一个方法的方法并测试另一个方法时,你会发现两个方法的测试之间存在重复 - 调用方法的一些测试确实在测试被叫方法。通过在调用方法中存根或模拟被调用的方法来处理它,测试调用方法就足以证明调用被调用的方法,并彻底测试被调用的方法。
如果您执行TDD,则可以在编写m1
之前编写m2
。然后,您将存根或模拟m2
,以便m1
的测试通过,然后继续测试并编写m2
。
但如果你没有任何理由存根或嘲笑m2
,请不要这样做。一种方法以不需要存根或嘲弄的方式调用其他方法是常见且合理的。被调用的方法可能简短,或者调用方法可能只是分解为一堆辅助方法以便于阅读。如果被调用的方法在另一个类中(因为它被多个调用方法使用),这甚至是正确的;如果它通过调用它的方法的测试进行了全面测试,并且如果测试之间没有重复,则不需要存根或模拟。
以上模拟m2
而不运行m1
的示例是一件非常正常的事情,它可以完成任务,但是由于Mockito接管了所有的工作,所以它很丑陋阶级的方法。你可以用Mockito间谍更好地做到这一点,在这里讨论:What is the difference between mocking and spying when using Mockito?。
A a = spy(new A());
doNothing().when(spy).m2();
a.m1();
# check state of a
答案 1 :(得分:0)
你仍然应该测试m1()。
你可以使用方法m2()的模拟,
答案 2 :(得分:0)
术语单元测试来自"工作单元"。一个独特的工作单元通常是,但并不总是单一的方法。在实践中,单元测试通常是指测试单个类(比较SRP)并对比集成和验收测试。 JUnit框架来自单元测试,但现在用于所有类型和层次的测试。
作为一个基本的经验法则,您应该测试所有公共方法。如果需要,请添加否定测试,这些测试会使用无效输入提供方法。通常他们会揭示代码的弱点。如果两种公共方法重叠 - 就像你的情况一样 - 我不知道一直使用的原则。你可以
A)为您提供对m1代码更敏感的测试,但成本更高。如果m1代码作为单个工作单元很重要,请选择此选项。 B)是更快的结果,在我看来是更好的选择,因为在m1错误的情况下,如果m2中也有错误,你会立即看到。也许为其他解释依赖性的程序员留下一个小小的好评。
答案 3 :(得分:0)
请考虑m1
依赖于m2
。
现在考虑m1
没有调用m2
,而是重复m2
内部的代码。
(在上述任一方案中,您都会测试m1
和m2
,对吗?)
现在考虑如果重构m1
以删除重复代码,通过调用m2
会发生什么。由于这是一个真正的重构(即,它不会改变方法的行为),您的现有测试应继续通过。
我的观点是m1
对m2
的依赖是一个私有的实现细节。您通常希望隐藏测试中的实现细节,以防止它们变得脆弱。因此,您应该编写测试,就好像他们不知道这种依赖关系一样。
修改:添加一些代码以进行演示。
想象一下,我们编写了以下代码和测试(对于任何语法错误道歉;我没有编译器方便):
interface Foo {
public void doStuff(int value);
}
interface Bar {
public void doOtherStuff();
}
class MyClass {
private Foo foo;
private Bar bar;
public MyClass(Foo foo, Bar bar) {
this.foo = foo;
this.bar = bar;
}
public void m1() {
foo.doStuff(42);
foo.doOtherStuff();
}
public void m2() {
foo.doStuff(42);
}
}
@Test
public void m1ShouldDoALotOfStuff() throws Exception {
Foo foo = PowerMockito.mock(Foo.class);
Bar bar = PowerMockito.mock(Bar.class);
MyClass sut = new MyClass(foo, bar);
sut.m1();
verify(foo).doStuff(42);
verify(bar).doOtherStuff();
}
@Test
public void m2ShouldJustDoStuff() throws Exception {
Foo foo = PowerMockito.mock(Foo.class);
Bar bar = PowerMockito.mock(Bar.class);
MyClass sut = new MyClass(foo, bar);
sut.m2();
verify(foo).doStuff(42);
}
在上面的代码中,我们对m1
和m2
进行了绿色测试。现在我们注意到m1
和m2
中有一些重复的代码:foo.doStuff(42);
。所以我们重构m1
以摆脱重复:
public void m1() {
m2();
foo.doOtherStuff();
}
我们的测试在进行此更改后仍为绿色,因为我们没有更改m1
的行为;我们只更改了详细信息它执行该行为的方式。为了保持健壮,我们的测试应该测试我们期望SUT做什么而不测试如何它。