使用Sassaby对Sass进行单元测试

在Wealthfront我们使用Sass编写所有CSS样式。Sass是一个强大的CSS预处理器,它使得用户可以利用编程语言中的常见特性,但是脱离了原生CSS。使用Sass变量,条件语句,循环和函数,可以让你在大型前端代码库中编写可扩展易维护的CSS。

在Wealthfront我们对待测试这件事非常认真。作为使用所有Sass特性的先驱,尤其是可复用函数,我们需要一个方式来保证Sass代码的独立测试。因此我们开发并开源了Sassaby,一个为Sass编写的单元测试库。

在这篇文章中我将详细描述一些引领我们至此的思考过程并且列举一些Wealthfront代码库中的例子来展示Sassaby的部分特性。

我们何以至此

我们对于Sass测试的需求类似于其他任何测试库的需求:

  • 测试语法必须简洁并且目标用户必须熟悉。
  • 声明必须可以用于所有需要测试的特性。
  • 测试便于整合进现有构建系统。
  • 测试能在构建系统中快速运行。

虽然已经有一些库可用来测试Sass,但它们用Sass编写,这不满足我们第一点需求。Sass对于它本身的设计目的-CSS预处理器来说,是一个很棒的语言。它的语法没有为编写其他类型的脚本优化,比如测试库。另外,由于必须被编译成CSS,用Sass编写测试代码的另一个缺点就是必须被编译成一个包含测试结果的文件(这就需要解析)。我们需要一个能记录测试失败结果并立即退出正在进行中的测试的库。

很快我们就决定使用JavaScript编写Sassaby。使用服务端JavaScript编写Sassaby使得我们可以:

  • 与现有的Node测试框架,Mocha进行交互。
  • 使用任何可用的Node程序库。
  • 方便集成进现有构建系统(因为我们已经在运行Node测试程序)。
  • 使用node-sass来编译。这个是包是libsass库中的节点包装(显然比普通的Ruby Sass编译要快)。
  • 用优秀的Rework CSS包来把编译后的CSS解析为JSON。

Sassaby

我们开发Sassaby来测试框架未知部分。它可以和任何你正在使用的Node测试库配合使用。为了在这篇文章中举例,我们将使用Mocha。Sassaby配置起来很简单,只要设置想要测试的文件:

'use strict';

var path = require('path');
var Sassaby = require('sassaby');
describe('sample.scss', function() {
  var sassaby;

  beforeEach(function() {
    sassaby = new Sassaby(path.resolve(__dirname, 'sample.scss'));
  });
});

Sassaby按照Sass中主要的复用的部分:mixins,functions和imports将自己的特性分解。

Mixins

Mixins会将一段重复的CSS规则声明或一系列CSS样式返回给样式表。Sassaby中有两类mixins:引用mixins(返回一系列CSS样式)和独立mixins(返回完整的CSS声明)。下面是Wealthfronts栅格系统中的一个独立mixins。

@mixin make-align-center($label) {
  .align-center-#{$label} {
    justify-content: center;
  }
}

我们想测试这个mixin中两个主要特性。它把一个给定的参数内嵌入CSS选择器并在网格中声明一个居中对齐项。我们可以通过设置独立mixin来对它进行测试,通过参数来调用,并使用一些内置声明。

describe('make-align-center', function() {
  it('should create the correct class', function() {
    sassaby.standaloneMixin('make-align-center').calledWith('md').createsSelector('.align-center-md');
  });

  it('should make the correct declaration', function() {
    sassaby.standaloneMixin('make-align-center').calledWith('md').declares('justify-content', 'center');
  });
});

Sassaby中calledWithArgs函数输入指定文件,mixin,以及参数并编译成结果CSS和AST。在这一步进行CSS编译可以独立测试每个mixin并且按需使用不同的参数。

独立mixins不同于引用mixins,独立mixins只返回CSS样式声明因此可以在一个已经定义的规则集合里调用。下面是我们引用mixins中的一个,用来给CSS滤镜样式添加浏览器前缀:

@mixin filter-grayscale($percent) {
  -webkit-filter: grayscale($percent);
  -moz-filter: grayscale($percent);
  -ms-filter: grayscale($percent);
  -o-filter: grayscale($percent);
  filter: grayscale($percent);
}

引用mixins的测试接口非常相似。例如我们会像下面那样测试这个mixin:

describe('filter-grayscale', function() {
  var mixin;
  var called;

  beforeEach(function() {
    mixin = sassaby.includedMixin('filter-grayscale');
    called = mixin.calledWith('50%');
  });

  it('should make the correct declarations', function() {
    called.declares('-webkit-filter', 'grayscale(50%)');
    called.declares('-moz-filter', 'grayscale(50%)');
    called.declares('-ms-filter', 'grayscale(50%)');
    called.declares('-o-filter', 'grayscale(50%)');
    called.declares('filter', 'grayscale(50%)');
  });
});

函数

Sass中的函数类似于mixins,但是函数并不处理真正的CSS样式选择器或规则。它们通常用于单位转换,例如下面的函数作用是把像素转换成rems:

@function rems($pxsize, $rembase) {
  @return ($pxsize/$rembase)+rem;
}

Sassaby支持函数测试接口,类似于mixin接口,但声明方式不同。对于这个函数来说,我们想测试划分是否正确并且末尾是否添加了正确单元。我们可以用相同的声明完成这些目标,但是举这个例子的另一个目的是展示不相等声明。

describe('rems', function() {
  it('convert to px units to rem units', function() {
    sassaby.func('rems').calledWithArgs('32px', '16px').equals('2rem');
  });

  it('has the correct output unit', function() {
    sassaby.func('rems').calledWithArgs('32px', '16px').doesNotEqual('2em');
  });
});

Imports

最后一个Sassaby中可以测试的特性是imports,利用这个特性可以把变量,mixins和样式分离成更有组织的文件结构。例如我们的主文件如下:

@import 'variables';
@import 'mixins';
@import 'layout';

显然我们想测试这些引用的文件。Sassaby可以通过如下接口完成:

describe('imports', function() {
  it('should import variables', function() {
    sassaby.imports('variables');
  });

  it('should import mixins', function() {
    sassaby.imports('mixins');
  });

  it('should import layout', function() {
    sassaby.imports('layout');
  });
});

综合运用

开始测试Sass mixins时我们已经注意到了这种方式会强制我们编写更好的功能单一的mixins。下面是之前栅格系统中创建重新排列栏目规则的mixins。

@mixin make-specific-alignments($label) {
  @for $i from 1 through ($grid-columns) {
    .order-#{$label}-#{$i} {
      order: $width - 1;
    }
  }
}

要测试这个mixin似乎太复杂。我们需要测试这个循环是否给每个栏目创建了一个新的类名,每个label是否被正确插值,规则声明是否比栏目数少一。当把这个mixins拆分成两个独立功能mixins时测试就变的简单多了:

@mixin make-order($label, $width) {
  .order-#{$label}-#{$width} {
    order: $width - 1;
  }
}

@mixin make-specific-alignments($label) {
  @for $i from 1 through ($grid-columns) {
    @include make-order($label, $i)
  }
}

这样做之后我们就可以独立测试这些mixins。另外,注意这些mixins依赖额外定义的变量,$grid-columns。Sassaby提供了移除这些额外依赖的方法,下面这些测试展示了如何拆分mixins:

describe('_grid.scss', function() {
  var gridColumns = 12;
  var mixin;
  var compiled;
  var sassaby;

  beforeEach(function() {
    sassaby = new Sassaby('_grid.scss', {
      variables: {
        'grid-columns': gridColumns
      }
    });
  });

  describe('make-order', function() {
    beforeEach(function() {
      mixin = sassaby.standaloneMixin('make-order');
      compiled = mixin.calledWithArgs('lg', 6);
    });

    it('should create the correct selector', function() {
      compiled.createsSelector('.order-lg-6');
    });

    it('should make the correct declaration', function() {
      compiled.declares('order', '5');
    });
  });

  describe('make-specific-alignments', function() {
    beforeEach(function() {
      mixin = sassaby.standaloneMixin('make-specific-alignments');
      compiled = mixin.calledWithArgs('lg');
    });

    it('should call the correct mixins', function() {
      for(var i = 1; i <= gridColumns; i++) {
        compiled.calls('make-order(lg, ' + i + ')');
      }
    });
  });
});

我们希望本文是一篇关于用Sass编写更优雅的CSS并用Sassaby进行测试的优秀指南。例子中使用的声明只是部分可用样例,我们鼓励大家查看完整的文档并从npm上下载这个库。我们也在Github(https://github.com/wealthfront/sassaby)上接受pull requests。

本文根据@Ryan Bahniuk的《Unit Testing for Sass with Sassaby》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://eng.wealthfront.com/2015/07/unit-testing-for-sass-with-sassaby.html

Blueed

现居上海。正在学习前端的道路上,欢迎交流。个人博客:Blueed.me,微博:@Ivan_z3

如需转载,烦请注明出处:http://www.w3cplus.com/preprocessor/unit-testing-for-sass-with-sassaby.html

返回顶部