最近,我为一个小型个人项目编写了很多Jasmine测试。 我花了一些时间才终于感到正确地完成了测试。 此后,当我切换回JUnit测试时,我总是很难过。 出于某种原因,JUnit测试不再那么好,我想知道是否有可能以类似于Jasmine的方式编写JUnit测试。
Jasmine是受RSpec (Ruby BDD测试框架)启发的流行JavaScript行为驱动开发测试框架。
一个简单的茉莉花测试如下所示:
describe('AudioPlayer tests', function() {var player;beforeEach(function() {player = new AudioPlayer();});it('should not play any track after initialization', function() {expect(player.isPlaying()).toBeFalsy();});...
});
第一行中的describe()函数调用使用Description AudioPlayer tests创建一个新的测试套件。 在测试套件中,我们可以使用it()创建测试(在Jasmine中称为specs)。 在这里,我们检查创建新的AudioPlayer的isPlaying()方法是否返回false。
AudioPlayer实例。
用JUnit编写的相同测试如下所示:
public class AudioPlayerTest {private AudioPlayer audioPlayer;@Before public void before() {audioPlayer = new AudioPlayer();}@Testvoid notPlayingAfterInitialization() {assertFalse(audioPlayer.isPlaying());}...
}
我个人认为Jasmine测试与JUnit版本相比更具可读性。 在Jasmine中,对测试没有任何影响的唯一噪音是括号和function关键字。 其他所有内容都包含一些有用的信息。
在阅读JUnit测试时,我们可以忽略诸如void,访问修饰符(私有,公共,..),注释和不相关的方法名称(如以@Before注释的方法名称)之类的关键字。 除此之外,以驼峰案例方法名称编码的测试描述不太好阅读。
除了提高可读性外,我真的很喜欢Jasmine嵌套测试套件的功能。
让我们来看一个更长的示例:
describe('AudioPlayers tests', function() {var player;beforeEach(function() {player = new AudioPlayer();});describe('when a track is played', function() {var track;beforeEach(function() {track = new Track('foo/bar.mp3')player.play(track);});it('is playing a track', function() {expect(player.isPlaying()).toBeTruthy();});it('returns the track that is currently played', function() {expect(player.getCurrentTrack()).toEqual(track);});});...
});
在这里,我们创建了一个子测试套件,负责测试AudioPlayer播放曲目时的行为。 内部的beforeEach()调用用于为子测试套件内的所有测试设置通用的前提条件。
相反,在JUnit中为多个(但不是全部)测试共享通用的前提条件有时会变得很麻烦。 当然,在测试中复制设置代码是不好的,因此我们为此创建了额外的方法。 为了在设置方法和测试方法之间共享数据(如上面示例中的track变量),我们必须使用成员变量(范围要大得多)。
另外,我们应确保将具有类似前提条件的测试分组在一起,以避免需要阅读整个测试类来查找特定情况下的所有相关测试。 或者我们可以将事情分成多个较小的类。 但是,然后我们可能必须在这些类之间共享设置代码……
如果我们查看Jasmine测试,就会发现该结构是通过调用全局函数(例如describe(),it(),…)并传递描述性字符串和匿名函数来定义的。
有了Java 8,我们有了Lambdas,所以我们可以做同样的事情吗?
是的,我们可以在Java 8中编写如下代码:
public class AudioPlayerTest {private AudioPlayer player;public AudioPlayerTest() {describe("AudioPlayer tests", () -> {beforeEach(() -> {player = new AudioPlayer();});it("should not play any track after initialization", () -> {expect(player.isPlaying()).toBeFalsy();});});}
}
如果我们暂时假设describe(),beforeEach(),it()和Expect()是采用适当参数的静态导入方法,则至少可以编译。 但是,我们应该如何进行这种测试?
出于兴趣,我尝试将其与JUnit集成,结果发现这实际上非常简单(我将在以后进行介绍)。 到目前为止,结果是一个名为Oleaster的小型图书馆。
用Oleaster编写的测试如下所示:
import static com.mscharhag.oleaster.runner.StaticRunnerSupport.*;
...@RunWith(OleasterRunner.class)
public class AudioPlayerTest {private AudioPlayer player;{describe("AudioPlayer tests", () -> {beforeEach(() -> {player = new AudioPlayer();});it("should not play any track after initialization", () -> {assertFalse(player.isPlaying());});});}
}
与前面的示例相比,只有几处发生了变化。 在这里,测试类使用JUnit @RunWith注释进行注释。 这告诉JUnit在运行此测试类时使用Oleaster。 通过静态导入StaticRunnerSupport。*,可以直接访问静态Oleaster方法,例如describe()或it()。 还要注意,构造函数已由实例初始化程序替换,而Jasmine like matcher被标准JUnit断言替换。
与原始的茉莉花测试相比,实际上有一件事情并不那么出色。 实际上,在Java中,变量必须有效地最终确定才能在lambda表达式中使用。 这意味着以下代码段无法编译:
describe("AudioPlayer tests", () -> {AudioPlayer player;beforeEach(() -> {player = new AudioPlayer();});...
});
在beforeEach()lambda表达式内对玩家的赋值将不会编译(因为玩家实际上不是最终的)。 在Java中,我们必须在这种情况下使用实例字段(如上例所示)。
万一您担心要报告:Oleaster仅负责收集和运行测试用例。 整个报告仍由JUnit完成。 因此,Oleaster应该不会对使用JUnit报告的工具和库造成任何问题。
例如,以下屏幕截图显示了IntelliJ IDEA中Oleaster测试失败的结果:
如果您想知道Oleaster测试在实践中的外观,可以看看Oleaster的测试(这些测试是用Oleaster本身编写的)。 您可以在此处找到GitHub测试目录。
通过评论此帖子或创建GitHub问题,随时添加任何类型的反馈。
翻译自: https://www.javacodegeeks.com/2014/07/an-alternative-approach-of-writing-junit-tests-the-jasmine-way.html