如何测试Gulp任务

32

自从我开始使用Gulp,我的项目变得越来越复杂。现在我有一些相当不错的任务,所以我在想也许我应该构建一些单元测试来保持正常吗?

有没有一种好而简单的方法来加载Gulpfile并确保我的任务正在执行我想要它们做的事情?

有人测试过他们的脚本吗,还是这完全是浪费时间?


1
我从未想过构建脚本是适合进行单元测试的,因为你无法断言其输入和输出。不过我很想看看你如何处理这个问题! - Benjamin Hodgson
1
我也是这样认为的,正如我所说 - 脚本变得越来越重,有些任务变得繁琐。我想正确的做法应该是将一些任务扩展到单独的 gulp 插件中,并且这些插件可以和应该进行测试。 - iLemming
4个回答

13

我的方法是创建一个测试实例,使用 execyeoman-assert。虽然感觉更像是集成测试,但我发现这样有助于确保任务正常运行(我的用例是一个yeoman-generator)。一些(mocha)示例如下:

'use strict';
var path = require('path');
var helpers = require('yeoman-generator').test;
var assert = require('yeoman-generator').assert;
var exec = require('child_process').exec;
var fs = require('fs');
var injectStyles = require('../.test-instance/tasks/dev');


describe('gulp inject', function ()
{
    var instancePath = path.join(__dirname, '../.test-instance');
    var mainScss = path.join(instancePath, 'app/styles/main.scss');
    var gulpfile = path.join(instancePath, 'gulpfile.js');
    var gulp = '$(which gulp)';
    var injectStylesCmd = gulp + ' injectStyles';

    describe('scss partials in styles folder', function ()
    {
        var expectedContent = [
            [mainScss, /_variables/],
            [mainScss, /base\/_buttons\.scss/],
            [mainScss, /base\/_fonts\.scss/],
            [mainScss, /base\/_forms\.scss/],
            [mainScss, /base\/_icons\.scss/],
            [mainScss, /base\/_lists\.scss/],
            [mainScss, /base\/_page\.scss/],
            [mainScss, /base\/_tables\.scss/],
            [mainScss, /base\/_typography\.scss/],
            [mainScss, /functions\/_some-function\.scss/],
            [mainScss, /mixins\/_some-mixin\.scss/],
            [mainScss, /placeholders\/_some-placeholder\.scss/]
        ];
        var expected = [
            mainScss
        ];


        beforeEach(function (done)
        {
            this.timeout(10000);
            fs.truncateSync(mainScss);
            fs.writeFileSync(mainScss, '// inject:sass\n\n// endinject');
            exec(injectStylesCmd + ' injectStyles', {
                cwd: instancePath
            }, function (err, stdout)
            {
                done();
            });
        });

        it('creates expected files', function ()
        {
            assert.file([].concat(
                expected
            ));
            assert.fileContent([].concat(
                expectedContent
            ));
        });
    });
});
当然,你需要确保你已经设置了一个测试实例。你可以通过使用fs.writeFileSync创建你的测试文件。在大多数情况下,你需要确保该实例具有相同的目录结构,并且至少存在gulpfile。

@hugoderhungrige,感谢您的提示,看起来非常有前途!我在想...在运行构建测试之前,您如何处理npm install过程? - teone
我正在使用一个Makefile:https://github.com/johannesjo/generator-modular-angular/blob/master/Makefile - hugo der hungrige
1
有道理...我已经在每个 https://github.com/open-cloud/xos/blob/master/views/ngXosLib/generator-xos/test/build.spec.js#L47-L65 前面添加了一个 exec 语句(它仍然需要使用 promises 进行重构...但是它可以工作)。 - teone

7
我发现查看gulp插件的单元测试(例如:https://github.com/jonkemp/gulp-useref/tree/master/test)是编写自己测试的好起点。

最终对我有效的解决方案(主要关注构建过程),不是单独测试gulp任务,而是有源文件目录,将源文件复制到指定位置,运行gulp(作为shell命令的“gulp build”),并将输出目录和文件与包含正确输出的另一个目录进行比较。-


5

简短回答

我为我的任务创建了测试,其中有两种类型的测试:

  • 单元测试,例如条件、选项等。
  • 集成测试,例如流输入、流输出等。

为了确保易于测试,我将从任务到gulp插件的所有内容模块化。

单元测试

  1. 为每个gulp插件创建适配器。
  2. 在模块化的任务中使用适配器。
  3. 在流上窥视适配器并添加调试属性。
  4. 断言任何条件和流序列。

集成测试

  1. 构建输入。
  2. 运行gulp任务。
  3. 输出到临时位置。

详细回答

够聊了,下面是一个使用jasmine混合单元测试和集成测试的示例:

/* spec/lib/tasks/build_spec.js */
var gulp     = require("gulp");
var through2 = require("through2");
var exec     = require("child_process").exec;
var env      = require("../../../lib/env");
var build    = require("../../../lib/tasks/build");
var gulpif   = require("../../../lib/adapters/gulpif");
var gzip     = require("../../../lib/adapters/gzip");
var uglify   = require("../../../lib/adapters/uglify");

describe("build", function(){
  it("stream to uglify then gzip if environment is production", function(testDone){
    var streamSequence = [];

    // 1. Stub condition and output path
    spyOn(env, "production").and.returnValue(true);
    spyOn(build, "dest").and.returnValue("./tmp");

    // 2. Stub debug message for stream sequence 
    spyOn(gulpif, "stream").and.callFake(function(env, stream){
      if (env.production()) streamSequence.push(stream.debug["stream-name"]);
      return through2.obj();
    });
    spyOn(uglify, "stream").and.callFake(function(){
      var stream = through2.obj();
      stream.debug = { "stream-name": "stream-uglify" };
      return stream;
    });
    spyOn(gzip, "stream").and.callFake(function(){
      var stream = through2.obj();
      stream.debug = { "stream-name": "stream-gzip" };
      return stream;
    });

    var stream = build.run();
    var url = "file://" + process.cwd() + "/tmp/resource.js";
    stream.on("end", function(){

      // 3. Assert stream sequence (unit test)
      expect(streamSequence).toEqual(["stream-uglify", "stream-gzip"]);
      exec("curl " + url, function(error, stdout, stderr){

        // 4. Assert stream output (integration)
        expect(eval.bind(Object.create(null), stdout)).not.toThrow();
        testDone();
      });
    });
  });
});

以下是任务的模块示例:

/* lib/tasks/build.js */
var gulp   = require("gulp");
var env    = require("../env");
var gulpif = require("../adapters/gulpif");
var gzip   = require("../adapters/gzip");
var uglify = require("../adapters/uglify");

var build = {
  dest: function(){
    return "./path/to/output";
  },
  run: function(){
    return gulp.src("./path/to/resource.js")
      .pipe(gulpif.stream(env.production(), uglify.stream()))
      .pipe(gulpif.stream(env.production(), gzip.stream()))
      .pipe(gulp.dest(this.dest()));
  }
};

module.exports = build;

以下是适配器:

/* lib/adapters/gulpif.js */
var gulpif = require("gulp-if");
var adapter = {
  stream: function(){
    return gulpif.apply(Object.create(null), arguments);
  }
};
module.exports = adapter;

/* lib/adapters/gzip.js */
var gzip = require("gulp-gzip");
var adapter = {
  stream: function(){
    return gzip.apply(Object.create(null), arguments);
  }
};
module.exports = adapter;

/* lib/adapters/uglify.js */
var gzip = require("gulp-uglify");
var adapter = {
  stream: function(){
    return uglify.apply(Object.create(null), arguments);
  }
};
module.exports = adapter;

这里是用于演示条件测试的环境:

/* lib/env.js */
var args = require("yargs").argv;
var env = {
  production: function(){
    return (args.environment === "production");
  }
}

最后,这是一个使用任务模块示例的任务:

/* tasks/build.js */
var gulp = require("gulp");
var build = require("./lib/tasks/build");
gulp.task("build", function(){
  build.run();
});

我知道这不是完美的,有一些重复的代码。但我希望这能给你展示我如何测试我的任务。


0
减少复杂性的一种方法可能是将任务模块化并将它们放在单独的文件中。在这种情况下,您可能需要共享 gulp 实例和 gulp 插件。我是这样做的:
在 Gulpfile.coffee 中:
gulp = require 'gulp'
$ = require('gulp-load-plugins')()

require('./src/build/build-task')(gulp, $)

gulp.task "default", ['build']

./src/build/build-task.coffee中:

module.exports = (gulp, $)->    
   gulp.task "build",->
       $.util.log "running build task"

尽管有些人可能会认为这种方法会使它变得更加复杂,也许最好将所有内容保留在 Gulpfile 中,但是对于我来说,它很有效,并且我几乎感觉现在可以不进行测试。

4
这是一个不错的做法,但并没有涉及到测试gulp任务的问题。 - Erik
为什么这是被接受的答案?它并没有回答如何实际测试gulp的问题。Hendra Uzia的答案看起来是一个完整的答案。 - Constablebrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接