尽管模拟对象是进行单元测试的理想工具,但通过模拟框架进行模拟可能会将您的单元测试变成难以维护的混乱。
这种复杂性的根本原因是我们的对象太大。 他们有很多方法,这些方法返回其他对象,这些对象也有方法。 当将此类对象的模拟版本作为参数传递时,我们应确保其所有方法都返回有效对象。
这导致不可避免的复杂性,这使得单元测试浪费几乎不可能维护。
对象层次结构
以jcabi-dynamo的Region
接口为例(为简洁起见,此代码段和本文中的所有其他代码段均进行了简化):
public interface Region {
Table table(String name);
}
它的table()
方法返回Table
接口的实例,该实例具有自己的方法:
public interface Table {
Frame frame();
Item put(Attributes attrs);
Region region();
}
由frame()
方法返回的接口Frame
也具有自己的方法。 等等。 为了创建一个正确的接口Region
模拟实例,通常会创建许多其他模拟对象。 使用Mockito时 ,它将如下所示:
public void testMe() {
// many more lines here...
Frame frame = Mockito.mock(Frame.class);
Mockito.doReturn(...).when(frame).iterator();
Table table = Mockito.mock(Table.class);
Mockito.doReturn(frame).when(table).frame();
Region region = Mockito.mock(Region.class);
Mockito.doReturn(table).when(region).table(Mockito.anyString());
}
所有这些只是实际测试之前的脚手架。
样例用例
假设您正在开发一个项目,该项目使用jcabi-dynamo来管理DynamoDB中的数据。 您的课程可能类似于以下内容:
public class Employee {
private final String name;
private final Region region;
public Employee(String empl, Region dynamo) {
this.name = empl;
this.region = dynamo;
}
public Integer salary() {
return Integer.parseInt(
this.region
.table("employees")
.frame()
.where("name", this.name)
.iterator()
.next()
.get("salary")
.getN()
);
}
}
您可以想象,例如使用Mockito对此类进行单元测试将有多困难。 首先,我们必须模拟Region
接口。 然后,我们必须模拟一个Table
接口,并确保它由table()
方法返回。 然后,我们必须模拟一个Frame
接口,等等。
单元测试将比类本身更长。 除此之外,它的真正目的(即测试雇员薪水的收回)对读者而言并不明显。
而且,当我们需要测试相似类的相似方法时,我们将需要从头开始重新进行此模拟。 同样,多行代码看起来与我们已经编写的代码非常相似。
假班
解决方案是创建伪造的类并将其与真实的类一起运送。 这就是jcabi-dynamo所做的。 只需查看其JavaDoc即可 。 有一个名为com.jcabi.dynamo.mock
的软件包,其中仅包含伪类,仅适用于单元测试。
即使它们的唯一目的是优化单元测试,我们也将它们与生产代码一起放在同一JAR软件包中。
使用伪类MkRegion
时,测试结果如下所示:
public class EmployeeTest {
public void canFetchSalaryFromDynamoDb() {
Region region = new MkRegion(
new H2Data().with(
"employees", new String[] {"name"},
new String[] {"salary"}
)
);
region.table("employees").put(
new Attributes()
.with("name", "Jeff")
.with("salary", new AttributeValue().withN(50000))
);
Employee emp = new Employee("Jeff", region);
assertThat(emp.salary(), equalTo(50000))
}
}
这个测试对我来说很明显。 首先,我们创建一个伪造的DynamoDB区域,该区域可在H2Data
存储(内存中的H2数据库)之上H2Data
。 该存储将准备好用于具有哈希键name
和单个salary
属性的单个employees
表。
然后,我们在表中放入记录,其中包含哈希Jeff
和薪水50000
。
最后,我们创建一个Employee
类的实例,并检查它如何从DynamoDB获取薪水。
我目前正在与几乎所有正在使用的开源库中进行相同的操作。 我正在创建一组伪造的类,以简化库内部及其用户的测试。
相关文章
您可能还会发现以下有趣的帖子:
翻译自: https://www.javacodegeeks.com/2014/09/built-in-fake-objects.html