运用Labrador 0.4构建组件化自动化测试微信小顺序

Labrador 是一个专为微信小顺序开辟的组件化开辟框架。

特征

  • 运用Labrador框架能够使微信开辟者东西支撑加载海量NPM包

  • 支撑ES6/7规范代码,运用async/await能够有用防止回调地狱

  • 组件重用,对微信小顺序框架举行了二次封装,完成了组件重用和嵌套

  • 自动化测试,非常轻易编写单元测试剧本,不经任何分外设置即可自动化测试

  • 运用Editor Config及ESLint规范化代码作风,轻易团队合作

装置

起首您的体系中装置Node.js和npm v3 下载Node.js,然后运转下面的敕令将全局装置Labrador敕令行东西。

npm install -g labrador-cli

初始化项目

mkdir demo           # 新建目次
cd demo              # 跳转目次
npm init             # 初始化npm包
labrador init        # 初始化labrador项目

项目目次构造

demo                 # 项目根目次
├── .labrador        # Labrador项目设置文件
├── .babelrc         # babel设置文件
├── .editorconfig    # Editor Config
├── .eslintignore    # ESLint 疏忽设置
├── .eslintrc        # ESLint 语法搜检设置
├── package.json
├── dist/            # 目标目次
├── node_modules/
└── src/             # 源码目次
    ├── app.js
    ├── app.json
    ├── app.less
    ├── components/  # 通用组件目次
    ├── pages/       # 页面目次
    └── utils/

注重 dist目次中的一切文件是由labrador敕令编译天生,请勿直接修正

设置开辟东西

项目初始化后运用WebStorm或Sublime等你习气的IDE翻开项目根目次。然后翻开 微信web开辟者东西 新建项目,当地开辟目次挑选 dist 目标目次。

开辟流程

在WebStorm或Sublime等IDE中编辑 src 目次下的源码,然后在项目根目次中运转labrador build 敕令构建项目,然后在 微信web开辟者东西 的调试界面中点击左边菜单的 重启 按钮即可检察效果。

我们在开辟中, 微信web开辟者东西 仅仅用来做调试和预览,不要在 微信web开辟者东西 的编辑界面修正代码。

微信web开辟者东西 会偶然失足,表现为点击 重启 按钮没有反应,调试控制台输出大批的没法require文件的毛病,编辑 界面中代码文件不显现。这是因为 labrador build 敕令会更新悉数 dist 目次,而 微信web开辟者东西 在监测代码转变时会涌现非常,碰到这类状况只须要关掉 微信web开辟者东西 再启动即可。

我们还能够运用 labrador watch 敕令来监控 src 目次下的代码,当发作转变后自动构建,没必要每一次编辑代码后手动运转 labrador build

所以最好的姿态是:

  1. 在项目中运转 labrador watch

  2. 在WebStorm中编码,保留

  3. 切换到 微信web开辟者东西 中调试、预览

  4. 再回到WebStorm中编码

labrador 敕令

labrador init 初始化项目敕令

注重此敕令会初始化当前的目次为项目目次。

labrador build 构建当前项目

  Usage: labrador build [options]

  Options:

    -h, --help     output usage information
    -V, --version  output the version number
    -c, --catch    在载入时自动catch一切JS剧本的毛病
    -t, --test     运转测试剧本
    -d, --debug    DEBUG形式
    -m, --minify   uglify紧缩代码

labrador watch 监测文件变化

  Usage: labrador watch [options]

  Options:

    -h, --help     output usage information
    -V, --version  output the version number
    -c, --catch    在载入时自动catch一切JS剧本的毛病
    -t, --test     运转测试剧本
    -d, --debug    DEBUG形式
    -m, --minify   uglify紧缩代码

labrador 库

labrador 库对全局的 wx 变量举行了封装,将大部分 wx 对象中的要领举行了Promise支撑, 除了以 on* 开首或以 *Sync 末端的要领。在以下代码中运用 labrador 库。

import wx from 'labrador';

console.log(wx.version);

wx.app;         // 和全局的 getApp() 函数效果一样,代码作风不发起粗犷地接见全局对象和要领
wx.Component;   // Labrador 自定义组件基类
wx.Types;       // Labrador 数据范例校验器鸠合

wx.login;       // 封装后的微信登录接口
wx.getStorage;  // 封装后的读取缓存接口
//... 更多请拜见 https://mp.weixin.qq.com/debug/wxadoc/dev/api/

我们发起不要再运用 wx.getStorageSync() 等同步壅塞要领,而在 async 函数中运用 await wx.getStorage() 异步非壅塞要领进步机能,除非碰到特殊状况。

app.js

src/app.js 示例代码以下:

import wx from 'labrador';
import {sleep} from './utils/util';

export default class {
  globalData = {
    userInfo: null
  };

  async onLaunch() {
    //挪用API从当地缓存中猎取数据
    let res = await wx.getStorage({ key: 'logs' });
    let logs = res.data || [];
    logs.unshift(Date.now());
    await wx.setStorage({ key: 'logs', data: logs });
    this.timer();
  }

  async timer() {
    while (true) {
      console.log('hello');
      await sleep(10000);
    }
  }

  async getUserInfo() {
    if (this.globalData.userInfo) {
      return this.globalData.userInfo;
    }
    await wx.login();
    let res = await wx.getUserInfo();
    this.globalData.userInfo = res.userInfo;
    return res.userInfo;
  }
}

代码中悉数运用ES6/7规范语法。代码没必要声明 use strict ,因为在编译时,一切代码都邑强迫运用严厉形式。

代码中并未挪用全局的 App() 要领,而是运用 export 语法默许导出了一个类,在编译后,Labrador会自动增添 App() 要领挪用,一切请勿手动挪用 App() 要领。如许做是因为代码作风不发起粗犷地接见全局对象和要领。

自定义组件

Labrador的自定义组件,是基于微信小顺序框架的组件之上,进一步自定义组合,具有逻辑处置惩罚和款式。如许做的目标请拜见 微信小顺序开辟三宗罪和处理计划

项目中通用自定义组件寄存在 src/compontents 目次,一个组件平常由三个文件构成,*.js*.xml*.less 离别对应微信小顺序框架的 jswxmlwxss 文件。在Labardor项目源码中,我们特地采纳了 xmlless 后缀以示辨别。假如组件包括单元测试,那末在组件目次下会存在一个 *.test.js 的测试剧本文件。

自定义组件示例

下面是一个简朴的自定义组件代码实例:

逻辑 src/compontents/title/title.js
import wx from 'labrador';
import randomColor  from '../../utils/random-color';

const { string } = wx.Types;

export default class Title extends wx.Component {

  propTypes = {
    text: string
  };

  props = {
    text: ''
  };

  data = {
    text: '',
    color: randomColor()
  };

  onUpdate(props) {
    this.setData('text', props.text);
  }

  handleTap() {
    this.setData({
      color: randomColor()
    });
  }
}

自定义组件的逻辑代码和微信框架中的page很相似,最大的辨别是在js逻辑代码中,没有挪用全局的 Page() 函数声明页面,而是用 export 语法导出了一个默许的类,这个类必需继承于 labrador.Component 组件基类。

相对于微信框架中的page,Labrador自定义组件扩大了 propTypespropschildren 选项及 onUpdate 性命周期函数。children 选项代表当前组件中的子组件鸠合,此选项将在下文中叙说。

Labrador的目标是构建一个能够重用、嵌套的自定义组件计划,在现实状况中,当多个组件相互嵌套组合,就一定会碰到父子组件件的数据和音讯通报。因为一切的组件都完成了 setData 要领,所以我们能够运用 this.children.foobar.setData(data)this.parent.setData(data) 如许的代码挪用来处理父子组件间的数据通报题目,然则,假如项目中涌现大批如许的代码,那末数据流将变得非常杂沓。

我们自创了 React.js 的头脑,为组件增添了 props 机制。子组件经由过程 this.props 获得父组件给本身转达的参数数据。父组件如何将数据通报给子组件,我们下文中叙说。

onUpdate 性命周期函数是当组件的 props 发作变化后被挪用,相似React.js中的 componentWillReceiveProps 所以我们能够在此函数体内监测 props 的变化。

组件定义时的 propTypes 选项是对当前组件的props参数数据范例的定义。 props 选项代表的是当前组件默许的各项参数值。propTypesprops 选项都能够省略,然则强烈发起定义 propTypes,因为如许能够使得代码更清楚易懂,别的还能够经由过程Labrador自动检测props值范例,以削减BUG。为优化机能,只要在DEBUG形式下才会自动检测props值范例。

编译时加上 -d 参数时即可进入DEBUG形式,在代码中任何地方都能够运用把戏变量 __DEBUG__ 来推断是不是是DEBUG形式。

别的,Labrador自定义组件的 setData 要领,支撑两种传参体式格局,第一种像微信框架一样吸收一个 object 范例的对象参数,第二种体式格局吸收作为KV对的两个参数,setData 要领将自动将其转为 object

注重 组件中事宜相应要领必需以 handle 开首!比方上文中的 handleTap,不然子组件将没法与模板绑定。如许做也是为了代码作风一致,轻易团队合作。发起事宜相应要领定名采纳 handle + 组件名 + 事宜名 比方:handleUsernameChange handleLoginButtonTap ,如许我们很轻易辨别是模板上哪个组件发作了什么事宜,假如省略中心的名词,如 handleTap ,则代表当前悉数自定义组件发作了 tap 事宜。

计划 src/compontents/title/title.xml
<view class="text-view">
  <text class="title-text" catchtap="handleTap" style="color:{{color}};">{{text}}</text>
</view>

XML计划文件和微信WXML文件语法完全一致,只是扩大了一个自定义标签 <component/>,下文中细致叙说。

款式 src/compontents/title/title.less
.title-text {
  font-weight: bold;
  font-size: 2em;
}

虽然我们采纳了LESS文件,然则因为微信小顺序框架的限定,不能运用LESS的层级挑选及嵌套语法。然则我们能够运用LESS的变量、mixin、函数等功能轻易开辟。

页面

我们请求一切的页面必需寄存在 pages 目次中,每一个页面的子目次中的文件花样和自定义组件一致,只是能够多出一个 *.json 设置文件。

页面示例

下面是默许首页的示例代码:

逻辑 src/pages/index/index.js
import wx from 'labrador';
import List from '../../components/list/list';
import Title from '../../components/title/title';
import Counter from '../../components/counter/counter';

export default class Index extends wx.Component {
  data = {
    userInfo: {},
    mottoTitle: 'Hello World',
    count: 0
  };

  get children() {
    return {
      list: new List(),
      motto: new Title({ text: '@mottoTitle' }),
      counter: new Counter({ count: '@count', onChange: this.handleCountChange })
    };
  }

  async onLoad() {
    try {
      //挪用运用实例的要领猎取全局数据
      let userInfo = await wx.app.getUserInfo();
      //更新数据
      this.setData({ userInfo });
      this.update();
    } catch (error) {
      console.error(error.stack);
    }
  }

  onReady() {
    this.setData('mottoTitle', 'Labrador');
  }

  handleCountChange(count) {
    this.setData({ count });
  }

  //事宜处置惩罚函数
  handleViewTap() {
    wx.navigateTo({
      url: '../logs/logs'
    });
  }
}

页面代码的花样和自定义组件的花样如出一辙,我们的头脑是 页面也是组件

js逻辑代码中一样运用 export default 语句导出了一个默许类,也不能手动挪用 Page() 要领,因为在编译后,pages 目次下的一切js文件悉数会自动挪用 Page() 要领声明页面。

我们看到组件类中,有一个对象属性 children ,这个属性定义了该组件依靠、包括的其他自定义组件,在上面的代码中页面包括了三个自定义组件 listtitlecounter ,这个三个自定义组件的 key 离别为 listmottocounter

自定义组件类在实例化时吸收一个范例为 object 的参数,这个参数就是父组件要传给子组件的props数据。平常状况下,父组件通报给子组件的props属性在其性命周期中是稳定的,这是因为JS的语法和小顺序框架的限定,没有React.js的JSX天真。然则我们能够通报一个以 @ 开首的属性值,如许我们就能够把子组建的 props 属性值绑定到父组件的 data 上来,当父组件的 data 发作变化后,Labrador将自动更新子组件的 props。比方上边代码中,将子组件 mottotext 属性绑定到了 @mottoTitle。那末在 onReady 要领中,将父组件的 mottoTitle 设置为 Labrador,那末子组件 mottotext 属性就会自动变成 Labrador

页面也是组件,一切的组件都具有一样的性命周期函数onLoad, onReady, onShow, onHide, onUnload,onUpdate 以及setData函数。

componetspages 两个目次的辨别在于,componets 中寄存的组件能够被智能加载,pages 目次中的组件在编译时自动加上 Page() 挪用,所以,pages 目次中的组件不能被其他组件挪用,不然将涌现屡次挪用Page()的毛病。假如某个组件须要重用,请寄存在 componets 目次或打包成NPM包。

计划 src/pages/index/index.xml
<view class="container">
  <view class="userinfo" catchtap="handleViewTap">
    <image class="userinfo-avatar" src="{{ userInfo.avatarUrl }}" background-size="cover"/>
    <text class="userinfo-nickname">{{ userInfo.nickName }}</text>
  </view>
  <view class="usermotto">
    <component key="motto" name="title"/>
  </view>
  <component key="list"/>
  <component key="counter"/>
</view>

XML计划代码中,运用了Labrador供应的 <component/> 标签,此标签的作用是导入一个自定义子组件的计划文件,标签有两个属性,离别为 key (必选)和 name (可选,默许为key的值)。key 与js逻辑代码中的组件 key 对应,name 是组件的目次名。key 用来绑定组件JS逻辑对象的 children 中对应的数据, name 用于在src/componetsnode_modules 目次中寻觅子组件模板。

款式 src/pages/index/index.less
@import 'list';
@import 'title';
@import 'counter';

.motto-title-text {
  font-size: 3em;
  padding-bottom: 1rem;
}

/* ... */

LESS款式文件中,我们运用了 @import 语句加载一切子组件款式,这里的 @import 'list' 语句根据LESS的语法,会起首寻觅当前目次 src/pages/index/ 中的 list.less 文件,假如找不到就会根据Labrador的划定规矩智能地尝试寻觅 src/componetsnode_modules 目次中的组件款式。

接下来,我们定义了 .motto-title-text 款式,如许做是因为 motto key 代表的title组件的模板中(src/compontents/title/title.xml)有一个view 属于 title-text 类,编译时,Labrador将自动为其增添一个前缀 motto- ,所以编译后这个view所属的类为 title-text motto-title-text (能够检察 dist/pages/index/index.xml)。那末我们就能够在父组件的款式代码中运用 .motto-title-text 来从新定义子组件的款式。

Labrador支撑多层组件嵌套,在上述的实例中,index 包括子组件 listtitlelist 包括子组件 title,所以在终究显现时,index 页面上回显现两个 title 组件。

细致代码请参阅 labrador init 敕令天生的示例项目。

自动化测试

我们划定项目中一切后缀为 *.test.js 的文件为测试剧本文件。每一个测试剧本文件对应一个待测试的JS模块文件。比方 src/utils/util.jssrc/utils/utils.test.js 。如许,项目中一切模块和其测试文件就悉数寄存在一起,轻易查找和模块分别。如许计划主如果受到了GO言语的启示,也相符微信小顺序一向的目次构造作风。

在编译时,加上 -t 参数即可自动挪用测试剧本完成项目测试,假如不加 -t 参数,则一切测试剧本不会被编译到 dist 目次,所以没必要忧郁项目会肥胖。

一般JS模块测试

测试剧本中运用 export 语句导出多个称号以 test* 开首的函数,这些函数在运转后会被逐一挪用完成测试。假如test测试函数在运转时抛出非常,则视为测试失利,比方代码:

// src/util.js
// 一般项目模块文件中的代码片断,导出了一个通用的add函数
export function add(a, b) {
  return a + b;
}
// src/util.test.js
// 测试剧本文件代码片断

import assert from 'assert';

//测试 util.add() 函数
export function testAdd(exports) {
  assert(exports.add(1, 1) === 2);
}

代码中 testAdd 即为一个test测试函数,特地用来测试 add() 函数,在test函数实行时,会将目标模块作为参数传进来,即会将 util.js 中的 exports 传进来。

自定义组件测试

自定义组件的测试剧本中能够导出两类测试函数。第三类和一般测试剧本一样,也为 test* 函数,然则参数不是 exports 而是运转中的、实例化后的组件对象。那末我们就能够在test函数中挪用组件的要领或则接见组件的propsdata 属性,来测试行动。别的,一般模块测试剧本是启动后就最先逐一运转 test* 函数,而组件测试剧本是当组件 onReady 今后才会最先测试。

自定义组件的第二类测试函数是以 on* 开首,和组件的性命周期函数称号如出一辙,这一类测试函数不是比及组件 onReady 今后最先运转,而是当组件性命周期函数运转时被触发。函数吸收两个参数,第一个为组件的对象援用,第二个为run 函数。比方某个组件有一个 onLoad 测试函数,那末当组件将要运转 onLoad 性命周期函数时,先触发 onLoad 测试函数,在测试函数内部挪用 run() 函数,继承实行组件的性命周期函数,run() 函数返回的数据就是性命周期函数返回的数据,假如返回的是Promise,则代表性命周期函数是一个异步函数,测试函数也能够写为async 异步函数,守候性命周期函数完毕。如许我们就能够猎取run()前后两个状况数据,末了对照,来测试性命周期函数的运转是不是准确。

第三类测试函数与性命周期测试函数相似,是以 handle* 开首,用以测试事宜处置惩罚函数是不是准确,是在对应事宜发作时运转测试。比方:

// src/components/counter/counter.test.js

export function handleTap(c, run) {
  let num = c.data.num;
  run();
  let step = c.data.num - num;
  if (step !== 1) {
    throw new Error('计数器点击一次应该自增1,然则自增了' + step);
  }
}

性命周期测试函数和事宜测试函数只会实行一次,自动化测试的效果将会输出到Console控制台。

项目设置文件

labrador init 敕令在初始化项目时,会在项目根目次中建立一个 .labrador 项目设置文件,假如你的项目是运用 labrador-cli 0.3 版本建立的,能够手动增添此文件。

设置文件为JSON花样,默许设置为:

{
  "npmMap":{
  },
  "uglify":{
    "mangle": [],
    "compress": {
      "warnings": false
    }
  }
}

npmMap 属性为NPM包映照设置,比方 {"underscore":"lodash"} 设置,假如你的源码中有require('underscore') 那末编译后将成为 require('lodash')。如许做是为了处理小顺序的环境限定致使一些NPM包没法运用的题目。比方我们的代码必需依靠于包A,A又依靠于B,假如B和小顺序不兼容,将致使A也没法运用。在这总状况下,我们能够Fork一份B,起名为C,将C中与小顺序不兼容的代码调解下,末了在项目设置文件中将B映照为C,那末在编译后就会绕过B而加载C,从而处理这个题目。

uglify 属性为 UglifyJs2 的紧缩设置,在编译时附加 -m 参数即可对项目中的一切文件举行紧缩处置惩罚。

ChangeLog

2016-10-09

labrador 0.3.0

  • 重构自定义组件支撑绑定子组件数据和事宜

2016-10-12

labrador 0.4.0

  • 增添自定义组件props机制

  • 自动化测试

  • UglifyJS紧缩集成

  • NPM包映照

  • 增添.labrador项目设置文件

贡献者

郑州脉冲软件科技有限公司

梁兴臣

开源协定

本项目根据MIT开源协定宣布,许可任何构造和个人免费运用。

    原文作者:脉冲云_梁兴臣
    原文地址: https://segmentfault.com/a/1190000007138074
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞