zoukankan      html  css  js  c++  java
  • 使用 Jasmine 进行测试驱动的 JavaScript 开发

    Jasmine 为 JavaScript 提供了 TDD (测试驱动开发)的框架,对于前端软件开发提供了良好的质量保证,这里对 Jasmine 的配置和使用做一个说明。

    目前,Jasmine 的最新版本是 2.3 版,这里以 2.3 版进行说明。网上已经有一些关于 Jasmine 的资料,但是,有些资料比较久远,已经与现有版本不一致。所以,这里特别以最新版进行说明。

    1. 下载

    官网地址:http://jasmine.github.io/

    官网文档地址:http://jasmine.github.io/2.3/introduction.html

    下载地址:https://github.com/jasmine/jasmine/releases

    在 GitHub 上提供了独立版本 jasmine-standalone-2.3.4.zip 和源码版本,如果使用的话,直接使用 standalone 版本即可。

    解压之后,可以得到如下所示的文件结构。

    其中,lib 中是 Jasmine 的实现文件,在 lib/jasmine-2.3.4 文件夹中,可以看到如下的文件。

    打开最外层的 SpecRunner.html ,这是一个 Jasmine 的模板,其中提供了测试的示例,我们可以在使用中直接套用这个模板。其中的内容为:

    复制代码
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>Jasmine Spec Runner v2.3.4</title>
    
      <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.3.4/jasmine_favicon.png">
      <link rel="stylesheet" href="lib/jasmine-2.3.4/jasmine.css">
    
      <script src="lib/jasmine-2.3.4/jasmine.js"></script>
      <script src="lib/jasmine-2.3.4/jasmine-html.js"></script>
      <script src="lib/jasmine-2.3.4/boot.js"></script>
    
      <!-- include source files here... -->
      <script src="src/Player.js"></script>
      <script src="src/Song.js"></script>
    
      <!-- include spec files here... -->
      <script src="spec/SpecHelper.js"></script>
      <script src="spec/PlayerSpec.js"></script>
    
    </head>
    
    <body>
    </body>
    </html>
    复制代码

    可以看到其中引用了 lib/jasmine-2.3.4/jasmine.js, lib/jasmine-2.3.4/jasmine- html.js 和 lib/jasmine-2.3.4/boot.js 三个系统文件,其中 boot.js 是网页情况下的启动文件,在 张丹 的 jasmine行为驱动,测试先行 这篇文章中,要写一个 report.js 的启动脚本,这里已经不用了,直接使用 boot.js 就可以。

    页面下面引用的  src/Player.js 和 src/Song.js 是我们的测试对象,而 spec/SpecHelper.js 和 spec/PlayerSpec.js 则是两个对应的测试文件,测试用例就定义在 spec 中。

    2. 测试的定义

    我们还是直接看示例,

    一个是 Song.js,这里定义了一个 Song 的类,通过原型定义了一个persistFavoriteStatus 实例方法,注意,这里还没有实现,如果调用则会抛出异常。脚本如下。

    复制代码
    function Song() {
    }
    
    Song.prototype.persistFavoriteStatus = function(value) {
      // something complicated
      throw new Error("not yet implemented");
    };
    复制代码

    另外一个是 player.js,定义了 Player 类,定义了一个歌手,通过原型定义了 play, pause, resume 和 makeFavorite 实例方法。对象有一个 isPlaying 的状态,其中 resume 还没有完成。

    复制代码
    function Player() {
    }
    Player.prototype.play = function(song) {
      this.currentlyPlayingSong = song;
      this.isPlaying = true;
    };
    
    Player.prototype.pause = function() {
      this.isPlaying = false;
    };
    
    Player.prototype.resume = function() {
      if (this.isPlaying) {
        throw new Error("song is already playing");
      }
    
      this.isPlaying = true;
    };
    
    Player.prototype.makeFavorite = function() {
      this.currentlyPlayingSong.persistFavoriteStatus(true);
    };
    复制代码

    下面看测试的定义,具体测试的说明,直接加在注释中。

    复制代码
    describe("Player", function() {
      var player;
      var song;
    
      beforeEach(function() {
        player = new Player();
        song = new Song();
      });
    
      // 检测正在歌手进行的歌曲确实是指定的歌曲
      it("should be able to play a Song", function() {
        player.play(song);
        expect(player.currentlyPlayingSong).toEqual(song);
    
        //demonstrates use of custom matcher
        expect(player).toBePlaying(song);
      });
    
      // 进行测试的分组,这里测试暂停状态
      describe("when song has been paused", function() {
        beforeEach(function() {
          player.play(song);
          player.pause();
        });
    
        // isPlaying 的状态检测
        it("should indicate that the song is currently paused", function() {
          expect(player.isPlaying).toBeFalsy();
    
          // demonstrates use of 'not' with a custom matcher
          // 
          expect(player).not.toBePlaying(song);
        });
    
        // 恢复
        it("should be possible to resume", function() {
          player.resume();
          expect(player.isPlaying).toBeTruthy();
          expect(player.currentlyPlayingSong).toEqual(song);
        });
      });
    
      // demonstrates use of spies to intercept and test method calls
      // 使用 spyOn 为对象创建一个 mock 函数
      it("tells the current song if the user has made it a favorite", function() {
        spyOn(song, 'persistFavoriteStatus');
    
        player.play(song);
        player.makeFavorite();
    
        expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);
      });
    
      //demonstrates use of expected exceptions
      // 异常检测
      describe("#resume", function() {
        it("should throw an exception if song is already playing", function() {
          player.play(song);
    
          expect(function() {
            player.resume();
          }).toThrowError("song is already playing");
        });
      });
    });
    复制代码

    使用浏览器直接打开 SpenRunner.html 看到的结果

    可以看到测试都通过了。

    如果我们将第一个测试 expect(player.currentlyPlayingSong).toEqual(song); 改成 expect(player.currentlyPlayingSong).toEqual( 1 );

    测试通不过,显示会变成这样。

     3. 语法

    3.1 describe 和 it

    describe 用来对测试用例进行分组,分组可以嵌套,每个分组可以有一个描述说明,这个说明将会出现在测试结果的页面中。

    describe("Player", function() {
          describe("when song has been paused", function() {

    而 it 就是测试用例,每个测试用例有一个字符串的说明,匿名函数内就是测试内容。

      // 检测正在歌手进行的歌曲确实是指定的歌曲
      it("should be able to play a Song", function() {
        player.play(song);
        expect(player.currentlyPlayingSong).toEqual(song);
      });

    测试结果的断言使用 expect 进行,函数内提供测试的值,toXXX 中则是期望的值。

    上面的测试使用 toEqual 进行相等断言判断。

    3.2 beforeEach 和 afterEach

    示例中还出现了 beforeEach。

    复制代码
      var player;
      var song;
    
      beforeEach(function() {
        player = new Player();
        song = new Song();
      });
    复制代码

    顾名思义,它表示在本组的每个测试之前需要进行的准备工作。在我们这里的测试中,总要用到 player 和 song 这两个对象实例,使用 forEach 保证在每个测试用例执行之前,重新对这两个对象进行了初始化。

    afterEach 会在每一个测试用例执行之后执行。

    3.3 自定义的断言

    除了系统定义的 toEqual 等等断言之外,也可以使用自定义的断言,在上面的示例中就出现了 toBePlaying 断言。

        //demonstrates use of custom matcher
        expect(player).toBePlaying(song);

    这个自定义的断言定义在 SpecHelper.js 文件中。

    复制代码
    beforeEach(function () {
      jasmine.addMatchers({
        toBePlaying: function () {
          return {
            compare: function (actual, expected) {
              var player = actual;
    
              return {
                pass: player.currentlyPlayingSong === expected && player.isPlaying
              };
            }
          };
        }
      });
    });
    复制代码

    其中调用了 jasmine 的 addMatchers 函数进行定义,原来这里不叫断言,称为 matcher ,也就是匹配器。

    断言是一个函数,返回一个对象,其中有一个 compare 的函数,这个函数接收两个参数,第一个是实际值,第二个为期望的值。具体的断言逻辑自己定义,这里比较歌手演唱的对象是否为我们传递的对象,并且歌手的状态为正在表演中。

    断言函数需要返回一个对象,对象的 pass 属性为一个 boolean 值,表示是否通过。

    4. 常用断言

    4.1 toEqual

    深相等,对于对象来说,会比较对象的每个属性。对于数组来说,会比较数组中每个元素。

    复制代码
      describe("The 'toEqual' matcher", function() {
    
        it("works for simple literals and variables", function() {
          var a = 12;
          expect(a).toEqual(12);
        });
    
        it("should work for objects", function() {
          var foo = {
            a: 12,
            b: 34
          };
          var bar = {
            a: 12,
            b: 34
          };
          expect(foo).toEqual(bar);
        });
      });
    复制代码

    4.2 toBe

    对于对象,引用相等。对于值,值相等。

    pass: actual === expected

    例如

      it("and has a positive case", function() {
        expect(true).toBe(true);
      });

    4.3 toBeTruthy

    是否为真。

      it("The 'toBeTruthy' matcher is for boolean casting testing", function() {
        var a, foo = "foo";
    
        expect(foo).toBeTruthy();
        expect(a).not.toBeTruthy();
      });

    4.4 toBeFalsy

    是否为假。

      it("The 'toBeFalsy' matcher is for boolean casting testing", function() {
        var a, foo = "foo";
    
        expect(a).toBeFalsy();
        expect(foo).not.toBeFalsy();
      });

    4.5 toBeDefined

    是否定义过

      it("creates spies for each requested function", function() {
        expect(tape.play).toBeDefined();
        expect(tape.pause).toBeDefined();
        expect(tape.stop).toBeDefined();
        expect(tape.rewind).toBeDefined();
      });

    4.6 toBeUndefined

    没有定义

    复制代码
      it("The `toBeUndefined` matcher compares against `undefined`", function() {
        var a = {
          foo: "foo"
        };
    
        expect(a.foo).not.toBeUndefined();
        expect(a.bar).toBeUndefined();
      });
    复制代码

    4.7 toBeNull

    复制代码
      it("The 'toBeNull' matcher compares against null", function() {
        var a = null;
        var foo = "foo";
    
        expect(null).toBeNull();
        expect(a).toBeNull();
        expect(foo).not.toBeNull();
      });
    复制代码

    4.9 toBeGreaterThan

    复制代码
      it("The 'toBeGreaterThan' matcher is for mathematical comparisons", function() {
        var pi = 3.1415926,
          e = 2.78;
    
        expect(pi).toBeGreaterThan(e);
        expect(e).not.toBeGreaterThan(pi);
      });
    复制代码

    4.10 toBeLessThan

    复制代码
      it("The 'toBeLessThan' matcher is for mathematical comparisons", function() {
        var pi = 3.1415926,
          e = 2.78;
    
        expect(e).toBeLessThan(pi);
        expect(pi).not.toBeLessThan(e);
      });
    复制代码

    4.11 toBeCloseTo

    复制代码
      it("The 'toBeCloseTo' matcher is for precision math comparison", function() {
        var pi = 3.1415926,
          e = 2.78;
    
        expect(pi).not.toBeCloseTo(e, 2);
        expect(pi).toBeCloseTo(e, 0);
      });
    复制代码

    4.12 toContain

    集合中是否包含。

      it("The 'toContain' matcher is for finding an item in an Array", function() {
        var a = ["foo", "bar", "baz"];
    
        expect(a).toContain("bar");
        expect(a).not.toContain("quux");
      });

    4.13 toMatch

    正则表达式的匹配

    复制代码
      it("The 'toMatch' matcher is for regular expressions", function() {
        var message = "foo bar baz";
    
        expect(message).toMatch(/bar/);
        expect(message).toMatch("bar");
        expect(message).not.toMatch(/quux/);
      });
    复制代码

    4.14 toThrow

    检测是否抛出异常

    复制代码
      it("The 'toThrow' matcher is for testing if a function throws an exception", function() {
        var foo = function() {
          return 1 + 2;
        };
        var bar = function() {
          return a + 1;
        };
    
        expect(foo).not.toThrow();
        expect(bar).toThrow();
      });
    复制代码

    4.15 toHaveBeenCalled

    4.16 toHaveBeenCalledWith

    是否调用过。

    复制代码
    describe("A spy", function() {
      var foo, bar = null;
    
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          }
        };
    
        spyOn(foo, 'setBar');
    
        foo.setBar(123);
        foo.setBar(456, 'another param');
      });
    
      it("tracks that the spy was called", function() {
        expect(foo.setBar).toHaveBeenCalled();
      });
    
      it("tracks all the arguments of its calls", function() {
        expect(foo.setBar).toHaveBeenCalledWith(123);
        expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
      });
    
      it("stops all execution on a function", function() {
        expect(bar).toBeNull();
      });
    });
    复制代码

    RequireJS Jasmine 2.0 编写测试

    http://ju.outofmemory.cn/entry/96587

  • 相关阅读:
    关于MapReduce中自定义分区类(四)
    关于MapReduce中自定义分组类(三)
    UiAutomator2.0
    Java_集合框架
    Python爬取指定重量的快递价格
    Java_面向对象
    Java_异常以及处理
    Java_File类
    Java_Scanner和System类
    Java_Runtime&Process&ProcessBuilder
  • 原文地址:https://www.cnblogs.com/koleyang/p/5580862.html
Copyright © 2011-2022 走看看