当我开始通过创建相同的对象并准备数据来运行测试来重复使用单元测试方法时,我对设计感到失望。 带有大量代码重复的长时间测试方法看起来并不正确。 为了简化和缩短它们,基本上有两个选项,至少在Java中:1)通过@Before
和@BeforeClass
初始化的私有属性,以及2)私有静态方法。 他们俩对我来说都是反面向对象的,我认为还有另一种选择。 让我解释。
JUnit正式建议测试夹具 :
public final class MetricsTest {
private File temp;
private Folder folder;
@Before
public void prepare() {
this.temp = Files.createTempDirectory("test");
this.folder = new DiscFolder(this.temp);
this.folder.save("first.txt", "Hello, world!");
this.folder.save("second.txt", "Goodbye!");
}
@After
public void clean() {
FileUtils.deleteDirectory(this.temp);
}
@Test
public void calculatesTotalSize() {
assertEquals(22, new Metrics(this.folder).size());
}
@Test
public void countsWordsInFiles() {
assertEquals(4, new Metrics(this.folder).wc());
}
}
我认为该测试正在做什么很明显。 首先,在prepare()
,创建Folder
类型的“测试装置”。 在所有三个测试中,将其用作Metrics
构造函数的参数。 这里要测试的实际类是Metrics
而this.folder
是我们测试它所需要的。
这个测试怎么了? 有一个严重的问题:测试方法之间的耦合 。 测试方法(以及所有常规测试)必须彼此完全隔离。 这意味着更改一项测试不得影响任何其他测试。 在此示例中,情况并非如此。 当我想更改countsWords()
测试时,必须更改before()
的内部结构,这将影响测试“类”中的其他方法。
在充分尊重JUnit的情况下,在@Before
和@After
中创建测试装置的想法是错误的,主要是因为它鼓励开发人员耦合测试方法。
这是我们可以改善测试并隔离测试方法的方法:
public final class MetricsTest {
@Test
public void calculatesTotalSize() {
final File dir = Files.createTempDirectory("test-1");
final Folder folder = MetricsTest.folder(
dir,
"first.txt:Hello, world!",
"second.txt:Goodbye!"
);
try {
assertEquals(22, new Metrics(folder).size());
} finally {
FileUtils.deleteDirectory(dir);
}
}
@Test
public void countsWordsInFiles() {
final File dir = Files.createTempDirectory("test-2");
final Folder folder = MetricsTest.folder(
dir,
"alpha.txt:Three words here",
"beta.txt:two words"
"gamma.txt:one!"
);
try {
assertEquals(6, new Metrics(folder).wc());
} finally {
FileUtils.deleteDirectory(dir);
}
}
private static Folder folder(File dir, String... parts) {
Folder folder = new DiscFolder(dir);
for (final String part : parts) {
final String[] pair = part.split(":", 2);
this.folder.save(pair[0], pair[1]);
}
return folder;
}
}
现在看起来好点了吗? 我们还没有,但现在我们的测试方法已完全隔离。 如果要更改其中一个,则不会影响其他参数,因为我将所有配置参数都传递给私有静态实用程序(!)方法folder()
。
一种实用方法,对吗? 是的, 闻起来 。
尽管比以前的设计要好得多,但这种设计的主要问题是它不能防止测试“类”之间的代码重复。 如果在另一个测试用例中需要类似Folder
类型的测试治具,则必须将该静态方法移到那里。 甚至更糟的是,我将不得不创建一个实用程序类。 是的,在面向对象的编程中没有什么比实用程序类更糟糕的了。
更好的设计是使用“伪”对象而不是私有静态实用程序。 这是怎么回事。 首先,我们创建一个伪造的类并将其放入src/main/java
。 必要时,此类可用于测试以及生产代码中( Fk
表示“伪造”):
public final class FkFolder implements Folder, Closeable {
private final File dir;
private final String[] parts;
public FkFolder(String... prts) {
this(Files.createTempDirectory("test-1"), parts);
}
public FkFolder(File file, String... prts) {
this.dir = file;
this.parts = parts;
}
@Override
public Iterable<File> files() {
final Folder folder = new DiscFolder(this.dir);
for (final String part : this.parts) {
final String[] pair = part.split(":", 2);
folder.save(pair[0], pair[1]);
}
return folder.files();
}
@Override
public void close() {
FileUtils.deleteDirectory(this.dir);
}
}
这是我们的测试现在的外观:
public final class MetricsTest {
@Test
public void calculatesTotalSize() {
final String[] parts = {
"first.txt:Hello, world!",
"second.txt:Goodbye!"
};
try (final Folder folder = new FkFolder(parts)) {
assertEquals(22, new Metrics(folder).size());
}
}
@Test
public void countsWordsInFiles() {
final String[] parts = {
"alpha.txt:Three words here",
"beta.txt:two words"
"gamma.txt:one!"
};
try (final Folder folder = new FkFolder(parts)) {
assertEquals(6, new Metrics(folder).wc());
}
}
}
你怎么看? 它不是比JUnit提供的更好吗? 它是否比实用程序方法更具可重用性和可扩展性?
总而言之,我认为单元测试中的脚手架必须通过与生产代码一起提供的伪造对象来完成。
翻译自: https://www.javacodegeeks.com/2015/05/a-few-thoughts-on-unit-test-scaffolding.html