我想对class A
进行单元测试。该类与其他类一起级联,因此class A
创建了class B
的{{1}}和class B
的实例。像这样:
class C
如果现在C类在其构造函数中读取任何外部输入(例如文件或属性),我该如何避免呢? 我看不到如何模拟它的方法,但是在测试时也无法将外部因素排除在其他类之外吗?
答案 0 :(得分:4)
您遇到的问题是,您的类与其他类的特定实现紧密关联,因此您的“单元”被迫测试所有这些类的行为在一起。
避免这种情况的典型方法是使用Dependency Injection,使您的类与接口(而不是特定的类)耦合。然后,您可以mock注入的接口来对单个类的行为进行单元测试。
public class AImpl {
public B b;
public AImpl(B b) {
this.b = b;
}
}
public interface B {
// methods
}
public class BImpl implements B {
public C c;
public BImpl (C c) {
this.c = c;
}
}
在生产代码中创建AImpl
时,现在必须为其提供特定的C
实现。您可以使用new AImpl(new BImpl(new CImpl))
进行此操作,也可以使用像Spring这样的依赖注入框架来为您找出所有这些细节。
在进行单元测试时,可以创建一个模拟或存根,使其具有希望B
在特定测试中展现的行为,然后将该存根传递给构造函数:{{1 }}。
答案 1 :(得分:3)
您不应在类B
中创建A
的实例-而是在创建B
时应提供实例A
-它被称为Inversion of Control。流行的方法是通过依赖注入,例如Spring框架。
然后一切变得简单-当您的类需要读取FileReader
类的文件时,对于测试,您可以创建一个“假”实现,例如,当您调用FileReader.readFile()
时,您的假实现将只需返回硬编码的String或Stream,取决于您想要的内容。
这个概念真的很大,想象一下您的FileReader
实际上是DatabaseReader
还是ExternalServiceCaller
的时候。无需在单元测试中测试真实的数据库(祝您好运),而是创建一个可以在常规Java FakeDatabaseReader
上运行的HashMap
,并且所有内容都易于测试。
或者当您的代码处理时间/日期时,想象一下仅在29th February
处执行某项功能的功能-您可以等待4年对其进行测试,或者改为提供Clock
对象进行测试设置为特定日期。 Clock
类具有提供各种备用时钟的静态方法,以提供fixed moment,时刻adjusted into the future or the past或clock with an altered cadence。
答案 2 :(得分:1)
首先,由于您不能注入B类,因此并非所有类都经过精心测试,请注意,由于类中有新内容,因此与实例的硬编码非常相似。
适当的方法应类似于
public class A {
public B b;
public A(B b) {
this.b = b;
}
}
请注意,您可以通过这种方式实例化
A a = new A(b);
通过这种方式,b可以是模拟或虚拟或存根,也可以是用于测试目的的任何对象。如果您面对的是旧代码,则应对其进行重构以使其能够进行测试。如果是您的设计,则应尽快重构以使其可测试。
控制反转和依赖注入应该有助于存档您想做的事情。
答案 3 :(得分:0)
据我所知,您需要照顾这些外部因素,或者确保在开发A,B或C类本身时,在测试期间不会出现此类问题。
但是,如果您无法控制A,B或C类的源代码,那么我认为您会陷入困境!