您的位置:新葡亰496net > 新葡亰官网 > 新葡亰496net干什么要写测量试验用例,的部分提

新葡亰496net干什么要写测量试验用例,的部分提

发布时间:2019-12-12 07:59编辑:新葡亰官网浏览(170)

    在 2017 年读书 React Redux 的局部提议(下篇)

    2017/09/11 · JavaScript · React, Redux

    原稿出处: 郭永峰   

    在那地说一下前端开荒的贰个表征是越多的会波及顾客界面,当开垦规模达到自然水准期,差相当少决定了其复杂度会加倍的滋长。

    站在产品的角度学习前端知识

    前言

    随着Web业务的日渐复杂化和多元化,前端开采也可以有了前面一个工程化的定义,前端工程化成为近来前端结构中关键的风流罗曼蒂克环,本质上也是软件工程的黄金年代种,由此大家要求从软件工程的角度来商讨前端工程,而自动化测量检验则是软件工程中器重的风度翩翩环。本文就钻研一下前端领域中的自动化测验,以致怎么样实行。

    Web 前端单元测量试验到底要怎么写?看这黄金年代篇就够了

    2018/08/16 · 底工才具 · 单元测量试验

    原版的书文出处: deepfunc   

    乘胜 Web 应用的复杂程度越来越高,非常多集团越来越讲究前面一个单元测量检验。大家看看的大部学科都会讲单元测量试验的重要性、一些有代表性的测量检验框架 api 怎么利用,但在实际上项目中单元测量试验要怎么出手?测量检验用例应该富含哪些具体内容呢?

    本文从壹个真真的行使场景出发,从设计情势、代码构造来解析单元测量检验应该包括怎样内容,具体育项目检测试用例怎么写,希望看见的童鞋都能具有收获。

    新葡亰496net 1关于测量试验的生机勃勃部分学习建议

    咱俩能够组成使用部分测量检验工具来协理测验 JS 代码,日常选拔 Mocha/Chai 或是 Karma/Jasmine 。而只要当您想测验 angular 的代码时,你会发觉还会有更加多的测验工具。可是对于 React 应用的测量检验,相比较推荐应用 Airbnb 共青团和少先队出品的 anzyme 来展开构件的测量检验,以保住组件的安居可信,这段时间接收非常广泛;而另后生可畏种办法是行使 Facebook 的 jest 来张开测量试验。

    恐怕过多同室都觉着应该选拔上述的某叁个测量检验库来开展测验职业,不过,你也得以将 anzymejest 结合起来合营利用。特别是在举行一些 snapshot 快速照相测量试验的时候,三种都以增补的,它们曾经是 React 应用测量试验中山大学家公众认同的标准库了。

    sinon 也是个非常美貌的测验援助理工科程师具,能够扶植我们在 spy、stub、mock 等测试阶段提供对应的工具帮助测验。借使对那多少个概念不太清晰,能够看看这里。

    其它,在这里地给你隆重的给你推荐大器晚成篇 A. Sharif 写的 Some Thoughts On Testing React/Redux Applications,满满的干货分享哦。

    不管在代码的起来搭建进程中,还是未来难以制止的重商谈改良bug进度中,平常会沦为逻辑难以梳理、无法左右全局关联的地步。

    何以做测量试验?小编的急需是如何?能够用其余措施消除呢?

    怎么样是单元测量检验

    单元测验(unit testing),是指对软件中的最小可测验单元实行反省和表达。对于单元测量检验中单元的意义,经常的话,要依赖实情去剖断其现实意思,如C语言中单元指二个函数,Java里单元指四个类,图形化的软件中得以指二个窗口或叁个菜系等。不问可见,单元便是人为规定的超级小的被测作用模块。单元测验是在软件开荒进程中要开展的最低端别的测量试验活动,软件的单独单元将要与程序的任何部分相隔绝的情状下实行测量试验。——百度宏观

    花色用到的工夫框架

    该品种应用 react 手艺栈,用到的第风流倜傥框架富含:reactreduxreact-reduxredux-actionsreselectredux-sagaseamless-immutableantd

    多一些组件的单元测量试验,少一些集成测验

    Enzyme 能够帮忙我们兑现组件的单元测量试验和合併测量检验。这里我们可以透过二种格局来渲染组件:

    • shallow()
    • mount()
    • render()

    shallow() 只好用来渲染不含有 children 的组件,mount() 则能够渲染全部的子组件。所以单元测量试验的时候能够动用 shallow()mount() 则平时用于集成测验。集成测验往往是相当轻巧被砍断的,因为她须要测验由后生可畏组或是多个构件树组合的景色,所以集成测量试验日常维护资金是相比较高的。据此大家能够多做一些玲珑剔透的单元测量试验,少做一些非常重要的购并测量检验。

    其二种测验的方法是采取 render() 方法,具备相仿 mount()方式的作用,可是 mount() 能够访谈到构件的生命周期方法,例如 componentDidUpdate等。

    正如那个 issue 中建议的 API differences between render and mount/shallow,能够计算出:

    • 使用 shallow 早先测量检验用例
    • 如果 componentDidMount or componentDidUpdate 等办法也必要测量检验,那么使用 mount 方法吧
    • 倘若要测量检验组件生命周期方法、子组件的表现等,使用 mount 方法吧
    • 假使想越来越高质量的测验子组件,而且对组件的生命周期等方法有个别关注,那么使用 render 方法吧

    新葡亰496net 2

    1、对象 (What)——什么专门的学问

    透过TDD测验驱动,检测代码的材料,提升代码可用性,明确工作成果;

    用代码检查代码,代替用测验用例文书档案检查代码,裁减人工开支,检查评定代码的材质,提升代码可用性,让开荒人士更懂需求;

    为何要测验

    以前还未编写制定和保险测验用例的习贯,在项目标忐忑开采周期中也没时间去做这一个专门的学业,相信有不少开垦人士都不会尊重单元测量试验那项事业。在真的写了大器晚成段时间底工零部件后,才察觉自动化测验有超级多益处:

    1. 进步代码品质。虽无法说100%无bug,但至少表明测量试验用例覆盖到的情况是绝非难点的。
    2. 能高效反馈,能分明UI组件职业情景是还是不是适合自身预期。
    3. 开荒者会愈加信赖友好的代码,也不会失色将代码交给外人维护。后人接手生机勃勃段有测验用例的代码,修改起来也会进一步从容。测试用例里特别清楚的论述了开辟者和使用者对于这段代码的想望和要求,也要命实惠代码的世袭。

    理之当然由于爱抚测量试验用例也是一大笔花销,仍旧要依赖投入产出比来做单元测量检验。对于像根基零器件、根基模型之类的一时更动且复用很多的一些,能够思忖写测验用例来保障品质,但对于迭代异常快的作业逻辑及生活时间超短的有个别就没供给浪费时间了。

    就此github上看看的star非常多的牛逼开源前端项目基本上都以有测量检验代码的,看来产业界大腕们都以相比注重单元测量检验那块的。

    采取场景介绍

    新葡亰496net 3

    以此应用项景从 UI 层来说至关心重视要由五个部分组成:

    • 工具栏,富含刷新开关、关键字找出框
    • 报表显示,选择分页的款式浏览

    总的来看此间有的童鞋大概会说:切!这么轻便的分界面和业务逻辑,照旧真正场景吧,还索要写神马单元测验吗?

    别急,为了确认保障作品的阅读经验和长度适中,能讲理解难题的精简场景正是好光景不是啊?慢慢往下看。

    确定保证测验用例轻巧、最小颗粒度

    不然的话你须求为此付出非常高的保障资金。

    承认各种组件是或不是都有实施过单元测验,确认各样 props 和 callbacks 都在合龙测量试验的时候传递给了相应的子组件。

    为了保障组件测量检验用例的小颗粒度和轻松化,你供给熟识一下 selectorsEnzyme 提供了拉长的 selector 去浓重组件树。

    别的,建议采纳 sinon 来测验 callbacks 回调函数,不要在组件中测量试验职业逻辑,那真不是个好注意。而是应当将职业逻辑从组件中解耦并对其开展测量检验。

    末尾,推特 出品的 Jest 也能在最初扶持大家进一层轻量的试行测验,你能够特别轻巧就设置好 snapshot test,那样当组件的出口更动的话测量检验用例会自动的报出退步的结果,而且能够收获到错误消息。

    而单元测验作为生机勃勃种“提纲挈领、遮风避雨”的根基手段,为支付提供了“围墙和脚手架”,能够使得的校勘那几个难题。

    2、场面 (Where)——什么地点

    新葡亰496net干什么要写测量试验用例,的部分提出。连锁概念

    设计情势与布局深入解析

    在这里个情景设计开荒中,大家严刻信守 redux 单向数据流 与 react-redux 的超级实施,并应用 redux-saga 来管理业务流,reselect 来处理状态缓存,通过 fetch 来调用后台接口,与真实的品种还没不相同。

    支行设计与代码协会如下所示:
    新葡亰496net 4

    中间 store 中的内容都以 redux 相关的,看名称应当都能分晓意思了。

    现实的代码请看 这里。

    拥抱 TDD(测量检验驱动开发)

    有着的人都或许会对你说:你应当按测验驱动的形式来拓打开采。不过,差十分少没几人会那样,项目须求如山的积压,上线的台本等比不上,测量试验驱动?玩吧?!恐怕大多数小同伙都以那般的心声。

    可是,借使您可见清晰的在 React Redux 的行使中动用相应的测量检验方案对各种部分都进展测验,你就能够比较轻巧的兑现 TDD。就算你会发觉 reducer 的测验和组件的测量检验是非常不生机勃勃致的,但实际每一种等级次序(reducer、component、…. )的测量试验方式其实都是相通的。

    就拿 reducer 的测量试验为例吧,日常是期望 reducer(state, action) === newState,其实这种方式和 (input) => output 的情势是如出风流浪漫辙的。如若您要测量试验 state 的不可变性的话,提议你可以使用 deep-freeze,能够看下以下示例代码:

    JavaScript

    import deepFreeze from 'deep-freeze' const initialState = { ... }; const action = { type: ..., payload: ... }; const expectedState = { ... }; deepFreeze(initialState); expect(reducer(initialState, action)).to.equal(expectedState);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import deepFreeze from 'deep-freeze'
     
    const initialState = { ... };
    const action = { type: ..., payload: ... };
    const expectedState = { ... };
     
    deepFreeze(initialState);
     
    expect(reducer(initialState, action)).to.equal(expectedState);

    倘使你能够很清晰的知情哪些测验应用中的每三个某个,那就最佳利用 TDD。

    作为后生可畏种杰出的支付和重构花招,单元测验在软件开辟领域被广大确认和动用;前端领域也逐年储存起了丰硕的测验框架和最好实施。

    3、时间和程序 (When)——何时

    带写代码之前经过先写测量试验代码再写作业代码来产生
    写完代码之后通过代码测量检验,或许通过测试文书档案来完结

    TDD

    TDD是Test Driven Development 的缩写,也正是测验驱动开垦。

    平凡古板软件工程将测量试验描述为软件生命周期的贰个环节,并且是在编码之后。但快速开垦大师KentBeck在二零零四年问世了 Test Driven Development By Example 意气风发书,从而营造了测量试验驱动开荒那一个世界。

    TDD须要依照如下准则:

    • 写一个单元测量试验去叙述程序的八个方面。
    • 运作它应该会退步,因为程序还贫乏这一个脾性。
    • 为这么些程序加多一些尽量简单的代码保障测量检验通过。
    • 重构那风姿洒脱部分代码,直到代码未有再一次、代码义务明晰而且组织轻巧。
    • 连绵不断重复这么做,积攒代码。

    TDD具备很强的针对性,在直接结果的引导下开拓分娩代码,然后不断缠绕这几个目的去修正代码,其优势是相当的慢和去冗余的。所以其特色应该是由必要得出测验,由测验代码得出分娩代码。打个若是就像是自行车的七个轮子,即便都以在向同三个趋势转动,可是后轮是施力的,带高铁子前进,而前轮是受力的,被向前的自行车推动而转。

    单元测量试验部分介绍

    先讲一下用到了什么测量试验框架和工具,主要内容囊括:

    • jest ,测验框架
    • enzyme ,专测 react ui 层
    • sinon ,具备独自的 fakes、spies、stubs、mocks 功用库
    • nock ,模拟 HTTP Server

    固然有童鞋对地点这么些使用和安顿不熟的话,直接看官方文档吧,比此外学科都写的好。

    接下去,我们就起来编制具体的测量试验用例代码了,上面会指向各样层面给出代码片段和深入分析。那么大家先从 actions 开始吧。

    为使小说尽量轻易、清晰,上边包车型大巴代码片段不是每一种文件的风流倜傥体化内容,完整内容在 这里 。

    多组件测量检验

    新葡亰496net 5

    4、人员 (Who)——责任人

    程序猿:通过TDD测量试验驱动,在写作业代码在此之前,先把测验代码写完;在写完测量试验代码之后,在写后生可畏段单元测量检验来检查实验所写的代码(相比较鸡肋);
    体验师:在驾驭必要后,编写测量检验用例文书档案,待程序猿写完代码后,人工通过测量检验用例测量试验成果;

    BDD

    所谓的BDD行为使得开拓,即Behaviour Driven Development,是生机勃勃种新的赶快开荒方法。它更趋向于要求,需求联合利润者的参预,重申客户传说(User Story)和展现。二〇〇四年,在London发布的“敏捷规格,BDD和尖峰测验调换”中,Dan North对BDD给出了之类概念:

    BDD是第二代的、由外及内的、基于拉(pull卡塔尔的、多方收益相关者的(stakeholder卡塔尔(قطر‎、多种可扩大的、高自动化的飞快方法。它陈说了二个互相循环,能够具有带有杰出定义的输出(即专业中付出的结果):已测量检验过的软件。

    它对TDD的视角进行了扩大,在TDD中侧珍视偏侧开垦,通过测量试验用例来标准约束开荒者编写出质量更加高、bug更加少的代码。而BDD越发注重设计,其供给在安顿测验用例的时候对系统进行定义,倡导使用通用的语言将系统的行事描述出来,将系统规划和测量试验用例结合起来,进而以此为驱动进行开垦工作。

    大约进程:

    1. 从事情的角度定义具体的,以致可权衡的靶子

    2. 找到风流罗曼蒂克种能够高达设定目的的、对事情最重大的那多少个功效的不二秘技

    3. 然后像传说相仿描述出二个个具体可施行的表现。其陈诉方法基于一些通用词汇,那个语汇具有标准科学的表明本事和大器晚成致的意义。举例,expect, should, assert

    4. 寻找合适语言及措施,对作为开展落到实处

    5. 测验人士核实付加物运行结果是或不是合乎预期行为。最大程度的交给出相符客户期望的产品,防止表明不平等带给的难点

    actions

    作业里面我动用了 redux-actions 来产生 action,这里用工具栏做示范,先看风华正茂段职业代码:

    import { createAction } from 'redux-actions'; import * as type from '../types/bizToolbar'; export const updateKeywords = createAction(type.BIZ_TOOLBAR_KEYWORDS_UPDATE); // ...

    1
    2
    3
    4
    5
    6
    import { createAction } from 'redux-actions';
    import * as type from '../types/bizToolbar';
     
    export const updateKeywords = createAction(type.BIZ_TOOLBAR_KEYWORDS_UPDATE);
     
    // ...

    对于 actions 测量检验,大家第一是表明发生的 action 对象是或不是准确:

    import * as type from '@/store/types/bizToolbar'; import * as actions from '@/store/actions/bizToolbar'; /* 测试 bizToolbar 相关 actions */ describe('bizToolbar actions', () => { /* 测验立异探究关键字 */ test('should create an action for update keywords', (卡塔尔(قطر‎ => { // 构建目的 action const keywords = 'some keywords'; const expectedAction = { type: type.BIZ_TOOLBAR_KEYWORDS_UPDATE, payload: keywords }; // 断言 redux-actions 产生的 action 是还是不是准确expect(actions.updateKeywords(keywords卡塔尔国卡塔尔.toEqual(expectedAction卡塔尔(قطر‎; }卡塔尔国; // ... }卡塔尔(قطر‎;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import * as type from '@/store/types/bizToolbar';
    import * as actions from '@/store/actions/bizToolbar';
     
    /* 测试 bizToolbar 相关 actions */
    describe('bizToolbar actions', () => {
      
        /* 测试更新搜索关键字 */
        test('should create an action for update keywords', () => {
            // 构建目标 action
            const keywords = 'some keywords';
            const expectedAction = {
                type: type.BIZ_TOOLBAR_KEYWORDS_UPDATE,
                payload: keywords
            };
     
            // 断言 redux-actions 产生的 action 是否正确
            expect(actions.updateKeywords(keywords)).toEqual(expectedAction);
        });
     
        // ...
    });

    本条测量试验用例的逻辑很简短,首先创设贰个大家意在的结果,然后调用业务代码,最终证实专门的学业代码的周转结果与期望是或不是相像。这便是写测验用例的为主套路。

    我们在写测量试验用例时尽量保证用例的纯粹职务,不要覆盖太多不相同的业务范围。测量试验用例数量得以有成都百货上千个,但每一种都不该很复杂。

    有关财富加载的精选

    React 即使是个 library,不过它的生态圈非常的丰裕,会有非常多的可扩张框架或类库能够投入使用,然则千万别太快的参与那一个扩张方案。而且每便新步向贰个模块的时候,要在集体内部分明各样人都是精通精晓的。非常是对此 Redux 本人的局地生态扩大,会有不菲的局地小模块,举个例子上边那一个:

    • 在贵族尚未起来写 action creatorsreducers 在此之前,就不用增添 redux-actions
    • 在大家尚未写出第一个和谐的 form 表单和表单验证的时候,就无须步入 redux-form
    • 在我们还未伊始写本人的 selectors 早前,就毫无步入 reselect
    • 在贵宗还么起头写第一个高阶组件 HOC 以前,就毫无进入 recompose
    • …..

    还要,关心一些大牌的精品实行,况且建构你协调的超级施行。不过得保障集体中其余同伙也能领会。定义清晰的命名法则和目录布局,况兼在类型做一些升官的时候得把这个约定提前研讨清楚。

    正文将按如下顺序举行表达:

    5、为什么(Why)——原因

    1. 节省费用,提升复用性:
      原先程序猿每修正贰遍代码,就必要测量试验职员依据测验用例文书档案,通首至尾的测验,以往技师每矫正叁回代码,只要求实施二回测量检验用例,就能够到位测量检验。
      原先多个人做的事,今后一位做完了,原来三个人都要熟识需要,多叁个单位纯熟须要就能够冒出不足预测的主题素材,引致延误项目工期;
      于今一人熟知完必要,通过测量检验驱动的秘籍就足以确认保证代码品质;

    2. 扶植成品迭代:
      再就是能够援救付加物周详须求文书档案:在写测量检验用例的时候,开掘成要求不显眼的,已支出的维度(寻思周详)援助产物经营补足未有假造到的方面;

    3. 无需"业余"的测验人士:
      本来测验职员基本是不理解代码逻辑的,也不清楚哪些地方达成是不是是有标题。(集团总会忽悠,说用一个不通晓代码的人,技术模拟出客户供给,放屁把,普通测验职员根本不知底客商在情景当即的需求,除非是高级测验人士,那就牛逼哥了,真正能模拟客户供给的独有顾客)
      由此程序猿严峻的思维,本事扶助产物设计考虑到各样气象下的难乎为继。

    4. 增值:
      升高程序猿的代码品质,通过事情发生早前构思,帮助程序员剖判如何功能是能够复用的,哪些逻辑是足以简化的。

    覆盖率

    什么样权衡测量检验脚本的质量呢?在那之中一个参照目标正是代码覆盖率(coverage)。

    何以是代码覆盖率?一言以蔽之便是测试中运转到的代码占全体代码的比值。当中又有什么不可分为行数覆盖率,分支覆盖率等。具体的意义不再细说,有意思味的能够自行查阅资料。

    固然并非说代码覆盖率越高,测量检验的本子写得越好,可是代码覆盖率对创作测试脚本依然有料定的指引意义的。

    reducers

    接着是 reducers,照旧选择 redux-actionshandleActions 来编写 reducer,这里用表格的来做示范:

    import { handleActions } from 'redux-actions'; import Immutable from 'seamless-immutable'; import * as type from '../types/bizTable'; /* 暗中认可状态 */ export const defaultState = Immutable({ loading: false, pagination: { current: 1, pageSize: 15, total: 0 }, data: [] }); export default handleActions( { // ... /* 管理拿到数量成功 *新葡亰496net干什么要写测量试验用例,的部分提出。/ [type.BIZ_TABLE_GET_RES_SUCCESS]: (state, {payload}) => { return state.merge( { loading: false, pagination: {total: payload.total}, data: payload.items }, {deep: true} ); }, // ... }, defaultState );

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    import { handleActions } from 'redux-actions';
    import Immutable from 'seamless-immutable';
    import * as type from '../types/bizTable';
     
    /* 默认状态 */
    export const defaultState = Immutable({
        loading: false,
        pagination: {
            current: 1,
            pageSize: 15,
            total: 0
        },
        data: []
    });
     
    export default handleActions(
        {
            // ...
     
            /* 处理获得数据成功 */
            [type.BIZ_TABLE_GET_RES_SUCCESS]: (state, {payload}) => {
                return state.merge(
                    {
                        loading: false,
                        pagination: {total: payload.total},
                        data: payload.items
                    },
                    {deep: true}
                );
            },
            
            // ...
        },
        defaultState
    );

    此间的景况对象使用了 seamless-immutable

    对于 reducer,我们入眼测验三个地方:

    1. 对此未知的 action.type ,是还是不是能回到当前景况。
    2. 对于各种职业 type ,是还是不是都回去了经过正确处理的气象。

    上面是本着以上两点的测量检验代码:

    import * as type from '@/store/types/bizTable'; import reducer, { defaultState } from '@/store/reducers/bizTable'; /* 测试 bizTable reducer */ describe('bizTable reducer', () => { /* 测验未内定 state 参数处境下回到当前缺省 state */ test('should return the default state', () => { expect(reducer(undefined, {type: 'UNKNOWN'})).toEqual(defaultState); }); // ... /* 测量检验管理符合规律数据结果 */ test('should handle successful data response', () => { /* 模拟再次来到数据结果 */ const payload = { items: [ {id: 1, code: '1'}, {id: 2, code: '2'} ], total: 2 }; /* 期望重返的气象 */ const expectedState = defaultState .setIn(['pagination', 'total'], payload.total) .set('data', payload.items) .set('loading', false); expect( reducer(defaultState, { type: type.BIZ_TABLE_GET_RES_SUCCESS, payload }) ).toEqual(expectedState); }); // ... });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    import * as type from '@/store/types/bizTable';
    import reducer, { defaultState } from '@/store/reducers/bizTable';
     
    /* 测试 bizTable reducer */
    describe('bizTable reducer', () => {
        
        /* 测试未指定 state 参数情况下返回当前缺省 state */
        test('should return the default state', () => {
            expect(reducer(undefined, {type: 'UNKNOWN'})).toEqual(defaultState);
        });
        
        // ...
        
        /* 测试处理正常数据结果 */
        test('should handle successful data response', () => {
            /* 模拟返回数据结果 */
            const payload = {
                items: [
                    {id: 1, code: '1'},
                    {id: 2, code: '2'}
                ],
                total: 2
            };
            /* 期望返回的状态 */
            const expectedState = defaultState
                .setIn(['pagination', 'total'], payload.total)
                .set('data', payload.items)
                .set('loading', false);
     
            expect(
                reducer(defaultState, {
                    type: type.BIZ_TABLE_GET_RES_SUCCESS,
                    payload
                })
            ).toEqual(expectedState);
        });
        
        // ...
    });

    这里的测验用例逻辑也很简单,依然是上面断言期望结果的覆辙。上面是 selectors 的有个别。

    维持不住的本领学习热情

    • 体贴入妙手艺社区的新取向,例如在使用中使用 ramda.js.,看看怎么着在React中高贵的写代码
    • 读书怎么采用 React Native 营造你的运动使用
    • 学习应用 Electron 营造你的桌面应用
    • 兴许你能够关怀哪些行使 Mobx 来扩充利用状态管理
    • React 仅仅只是 UI 层的一个 library ,你能够利用PREACT 和 inferno 等贴近 React 的库来替代,他们的体量能加轻量,渲染更高效,可能是个不利的选项。
    • Airbnb 的 React/JSX 规范 小编也提出您抽时间看看,对于组织豆蔻梢头致化开拓特别有救助。同时,也能够运用 ESLint 来张开代码准绳检查。
    • I. 单元测验简要介绍
    • II. React 单元测量检验中用到的工具
    • III. 用测验驱动 React 组件重构
    • IV. React 单元测验比比皆已案例

    6、方式 (How)——如何

    后边一个单测工具栈

    selectors

    selector 的成效是赢得对应业务的动静,这里运用了 reselect 来做缓存,幸免 state 未改变的情状下再度总结,先看一下表格的 selector 代码:

    import { createSelector } from 'reselect'; import * as defaultSettings from '@/utils/defaultSettingsUtil'; // ... const getBizTableState = (state) => state.bizTable; export const getBizTable = createSelector(getBizTableState, (bizTable) => { return bizTable.merge({ pagination: defaultSettings.pagination }, {deep: true}); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import { createSelector } from 'reselect';
    import * as defaultSettings from '@/utils/defaultSettingsUtil';
     
    // ...
     
    const getBizTableState = (state) => state.bizTable;
     
    export const getBizTable = createSelector(getBizTableState, (bizTable) => {
        return bizTable.merge({
            pagination: defaultSettings.pagination
        }, {deep: true});
    });

    此间的分页器部分参数在品种中是联合设置,所以 reselect 很好的成功了那几个专门的学业:假设专门的学问意况不改变,直接回到上次的缓存。分页器私下认可设置如下:

    export const pagination = { size: 'small', showTotal: (total, range) => `${range[0]}-${range[1]} / ${total}`, pageSizeOptions: ['15', '25', '40', '60'], showSizeChanger: true, showQuickJumper: true };

    1
    2
    3
    4
    5
    6
    7
    export const pagination = {
        size: 'small',
        showTotal: (total, range) => `${range[0]}-${range[1]} / ${total}`,
        pageSizeOptions: ['15', '25', '40', '60'],
        showSizeChanger: true,
        showQuickJumper: true
    };

    那正是说大家的测验也入眼是三个方面:

    1. 对于职业 selector ,是或不是重回了不利的剧情。
    2. 缓存功效是不是不荒谬。

    测量检验代码如下:

    import Immutable from 'seamless-immutable'; import { getBizTable } from '@/store/selectors'; import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil'; /* 测试 bizTable selector */ describe('bizTable selector', () => { let state; beforeEach(() => { state = createState(); /* 各类用例实行前重新载入参数缓存总计次数 */ getBizTable.resetRecomputations(); }); function createState() { return Immutable({ bizTable: { loading: false, pagination: { current: 1, pageSize: 15, total: 0 }, data: [] } }); } /* 测量试验重返正确的 bizTable state */ test('should return bizTable state', () => { /* 业务情况ok 的 */ expect(getBizTable(state)).toMatchObject(state.bizTable); /* 分页私下认可参数设置 ok 的 */ expect(getBizTable(state)).toMatchObject({ pagination: defaultSettingsUtil.pagination }); }); /* 测量试验 selector 缓存是或不是有效 */ test('check memoization', () => { getBizTable(state); /* 第一遍计算,缓存总计次数为 1 */ expect(getBizTable.recomputations()).toBe(1); getBizTable(state); /* 业务意况不改变的气象下,缓存总计次数应当照旧 1 */ expect(getBizTable.recomputations()).toBe(1); const newState = state.setIn(['bizTable', 'loading'], true); getBizTable(newState); /* 业务情状更动了,缓存总结次数应当是 2 了 */ expect(getBizTable.recomputations()).toBe(2); }); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    import Immutable from 'seamless-immutable';
    import { getBizTable } from '@/store/selectors';
    import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil';
     
    /* 测试 bizTable selector */
    describe('bizTable selector', () => {
        
        let state;
     
        beforeEach(() => {
            state = createState();
            /* 每个用例执行前重置缓存计算次数 */
            getBizTable.resetRecomputations();
        });
     
        function createState() {
            return Immutable({
                bizTable: {
                    loading: false,
                    pagination: {
                        current: 1,
                        pageSize: 15,
                        total: 0
                    },
                    data: []
                }
            });
        }
     
        /* 测试返回正确的 bizTable state */
        test('should return bizTable state', () => {
            /* 业务状态 ok 的 */
            expect(getBizTable(state)).toMatchObject(state.bizTable);
            
            /* 分页默认参数设置 ok 的 */
            expect(getBizTable(state)).toMatchObject({
                pagination: defaultSettingsUtil.pagination
            });
        });
     
        /* 测试 selector 缓存是否有效 */
        test('check memoization', () => {
            getBizTable(state);
            /* 第一次计算,缓存计算次数为 1 */
            expect(getBizTable.recomputations()).toBe(1);
            
            getBizTable(state);
            /* 业务状态不变的情况下,缓存计算次数应该还是 1 */
            expect(getBizTable.recomputations()).toBe(1);
            
            const newState = state.setIn(['bizTable', 'loading'], true);
            getBizTable(newState);
            /* 业务状态改变了,缓存计算次数应该是 2 了 */
            expect(getBizTable.recomputations()).toBe(2);
        });
    });

    测量检验用例依旧很简短有木有?保持那些点子就对了。上边来讲下多稀有一点复杂的地点,sagas部分。

    结束语

    全文完毕,谢谢您的读书,希望全种类列的稿子对你之后的上学抱有利于。

    比方你想系统学习 React Redux 本领栈的持有剧情,请点自身前往

    2 赞 1 收藏 评论

    新葡亰496net 6

    单元测量试验(unit testing),是指对软件中的最小可测量检验单元进行检讨和证明。

    工具介绍
    • karma:
      • Karma是多少个遵照Node.js的JavaScript测验执行进度管理工科具
      • 该工具可用于测量试验全数主流Web浏览器,也可集成到CI(Continuous integration)工具,也可和其余代码编辑器一齐利用
      • 其风华正茂测验工具的叁个无敌特性正是,它能够监督(沃特ch卡塔尔(英语:State of Qatar)文件的转换,然后自行推行,通过console.log展现测量检验结果
    • Jasmine:
      • Jasmine (茉莉)是朝气蓬勃款 JavaScript BDD(行为使得开垦)测量检验框架,它不注重于任何任何 JavaScript 组件。它有根本清晰的语法,让您能够很简短的写出测量检验代码
    • Mocha:测量检验框架。提供describe,it,beforeEach等函数管理你的 testcase
      • 简单、易上手
      • 辅助TDD、BDD等各个接口
      • 支撑同步和异步的测验
      • 支撑两种措施导出结果
      • 支撑直接在浏览器上跑JavaScript代码
    • Chai:
      • BDD(行为使得开辟)和TDD(测量试验驱动开采)双测验风格的断言库
    • enzyme:
      • React测量试验工具,能够贴近 jquery 风格的 api 操作react 节点
    • Sinon:
      • 新葡亰496net,提供 fake 数据, 替换函数调用等效率
    • Jest
      • Twitter 官方援救
      • 适应性:Jest是模块化、可扩张和可配置的。
      • 沙箱和高效:Jest虚构化了JavaScript的境况,能模仿浏览器,何况并行实践
      • 快速照相测量试验:Jest能够对React 树实行快照或别的连串化数值快捷编写测量检验,提供便捷更新的客户体验。
      • 支撑异步代码测验:扶植promises和async/await

    测量试验框架

    尤为重要提供了清晰简明的语法来呈报测量试验用例,以致对测量试验用例分组,测量试验框架会抓取到代码抛出的AssertionError,并追加第一次全国代表大会堆附加音信,举例非常用例挂了,为啥挂等等。最近可比盛行的测量检验框架有:

    • Jasmine: 自带断言(assert),mock效率
    • Mocha: 框架不带断言和mock作用,必要结合其余工具,由tj大神开垦
    • Jest: 由推非凡品的测量检验框架,在Jasmine测试框架上演化开拓而来

    sagas

    此处笔者用了 redux-saga 管理业务流,这里具体也正是异步调用 api 要求数据,管理成功结果和谬误结果等。

    或是有的童鞋感到搞这么复杂干嘛,异步乞请用个 redux-thunk 不就完了了呢?别急,耐烦看完你就领悟了。

    此处有供给差相当少介绍下 redux-saga 的办事章程。saga 是大器晚成种 es6 的生成器函数 – Generator ,咱们应用他来爆发各个注脚式的 effects ,由 redux-saga 引擎来消化摄取处理,拉动职业张开。

    那边我们来走访获取表格数据的事务代码:

    import { all, takeLatest, put, select, call } from 'redux-saga/effects'; import * as type from '../types/bizTable'; import * as actions from '../actions/bizTable'; import { getBizToolbar, getBizTable } from '../selectors'; import * as api from '@/services/bizApi'; // ... export function* onGetBizTableData() { /* 先获取 api 调用须求的参数:关键字、分页音信等 */ const {keywords} = yield select(getBizToolbar); const {pagination} = yield select(getBizTable); const payload = { keywords, paging: { skip: (pagination.current - 1) * pagination.pageSize, max: pagination.pageSize } }; try { /* 调用 api */ const result = yield call(api.getBizTableData, payload); /* 不荒谬再次来到 */ yield put(actions.putBizTableDataSuccessResult(result)); } catch (err) { /* 错误重返 */ yield put(actions.putBizTableDataFailResult()); } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import { all, takeLatest, put, select, call } from 'redux-saga/effects';
    import * as type from '../types/bizTable';
    import * as actions from '../actions/bizTable';
    import { getBizToolbar, getBizTable } from '../selectors';
    import * as api from '@/services/bizApi';
     
    // ...
     
    export function* onGetBizTableData() {
        /* 先获取 api 调用需要的参数:关键字、分页信息等 */
        const {keywords} = yield select(getBizToolbar);
        const {pagination} = yield select(getBizTable);
     
        const payload = {
            keywords,
            paging: {
                skip: (pagination.current - 1) * pagination.pageSize, max: pagination.pageSize
            }
        };
     
        try {
            /* 调用 api */
            const result = yield call(api.getBizTableData, payload);
            /* 正常返回 */
            yield put(actions.putBizTableDataSuccessResult(result));
        } catch (err) {
            /* 错误返回 */
            yield put(actions.putBizTableDataFailResult());
        }
    }

    不熟悉 redux-saga 的童鞋也毫不太留意代码的求实写法,看注释应该能通晓那一个业务的具体步骤:

    1. 从对应的 state 里取到调用 api 时供给的参数部分(寻找关键字、分页),这里调用了刚刚的 selector。
    2. 组成好参数并调用对应的 api 层。
    3. 如若不荒谬重临结果,则发送成功 action 文告 reducer 更新景况。
    4. 后生可畏旦不当重回,则发送错误 action 公告 reducer。

    那么具体的测量试验用例应该怎么写啊?大家都掌握这种事情代码涉及到了 api 或别的层的调用,借使要写单元测量试验必得做一些 mock 之类来严防真正调用 api 层,上面大家来看一下 怎么针对那几个 saga 来写测量检验用例:

    import { put, select } from 'redux-saga/effects'; // ... /* 测量试验获取数据 */ test('request data, check success and fail', () => { /* 当前的事体情形 */ const state = { bizToolbar: { keywords: 'some keywords' }, bizTable: { pagination: { current: 1, pageSize: 15 } } }; const gen = cloneableGenerator(saga.onGetBizTableData)(); /* 1. 是还是不是调用了不错的 selector 来得到央浼时要发送的参数 */ expect(gen.next().value).toEqual(select(getBizToolbar)); expect(gen.next(state.bizToolbar).value).toEqual(select(getBizTable)); /* 2. 是还是不是调用了 api 层 */ const callEffect = gen.next(state.bizTable).value; expect(callEffect['CALL'].fn).toBe(api.getBizTableData); /* 调用 api 层参数是或不是传递精确 */ expect(callEffect['CALL'].args[0]).toEqual({ keywords: 'some keywords', paging: {skip: 0, max: 15} }); /* 3. 模拟正确重回分支 */ const successBranch = gen.clone(); const successRes = { items: [ {id: 1, code: '1'}, {id: 2, code: '2'} ], total: 2 }; expect(successBranch.next(successRes).value).toEqual( put(actions.putBizTableDataSuccessResult(successRes))); expect(successBranch.next().done).toBe(true); /* 4. 模拟错误重临分支 */ const failBranch = gen.clone(卡塔尔(قطر‎; expect(failBranch.throw(new Error('模拟发生十三分'卡塔尔卡塔尔.value卡塔尔.toEqual( put(actions.putBizTableDataFailResult(卡塔尔国卡塔尔(قطر‎卡塔尔(英语:State of Qatar); expect(failBranch.next(卡塔尔.done卡塔尔.toBe(true卡塔尔(قطر‎; }卡塔尔国;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    import { put, select } from 'redux-saga/effects';
     
    // ...
     
    /* 测试获取数据 */
    test('request data, check success and fail', () => {
        /* 当前的业务状态 */
        const state = {
            bizToolbar: {
                keywords: 'some keywords'
            },
            bizTable: {
                pagination: {
                    current: 1,
                    pageSize: 15
                }
            }
        };
        const gen = cloneableGenerator(saga.onGetBizTableData)();
     
        /* 1. 是否调用了正确的 selector 来获得请求时要发送的参数 */
        expect(gen.next().value).toEqual(select(getBizToolbar));
        expect(gen.next(state.bizToolbar).value).toEqual(select(getBizTable));
     
        /* 2. 是否调用了 api 层 */
        const callEffect = gen.next(state.bizTable).value;
        expect(callEffect['CALL'].fn).toBe(api.getBizTableData);
        /* 调用 api 层参数是否传递正确 */
        expect(callEffect['CALL'].args[0]).toEqual({
            keywords: 'some keywords',
            paging: {skip: 0, max: 15}
        });
     
        /* 3. 模拟正确返回分支 */
        const successBranch = gen.clone();
        const successRes = {
            items: [
                {id: 1, code: '1'},
                {id: 2, code: '2'}
            ],
            total: 2
        };
        expect(successBranch.next(successRes).value).toEqual(
            put(actions.putBizTableDataSuccessResult(successRes)));
        expect(successBranch.next().done).toBe(true);
     
        /* 4. 模拟错误返回分支 */
        const failBranch = gen.clone();
        expect(failBranch.throw(new Error('模拟产生异常')).value).toEqual(
            put(actions.putBizTableDataFailResult()));
        expect(failBranch.next().done).toBe(true);
    });

    本条测验用例比较前边的纷纷了一些,大家先来讲下测量试验 saga 的规律。前边说过 saga 实际上是回去各样申明式的 effects ,然后由引擎来真正实行。所以大家测量试验的目标就是要看 effects 的发出是不是适合预期。那么effect 到底是个神马东西吗?其实正是字面量对象!

    大家得以用在作业代码肖似的格局来发出这一个字面量对象,对于字面量对象的预感就特别轻巧了,何况未有直接调用 api 层,就不须要做 mock 咯!那些测量检验用例的步调正是使用生成器函数一步步的产生下三个 effect ,然后断言相比较。

    从地方的注释 3、4 能够观察,redux-saga 还提供了部分相助函数来方便的拍卖分支断点。

    那也是本身接纳 redux-saga 的因由:强盛並且有助于测量试验。

    粗略来讲,单元就是人为规定的一丝一毫的被测作用模块。单元测量检验是在软件开荒进度中要开展的最低端别的测量检验活动,软件的独门单元将要与程序的其余部分相隔绝的事态下张开测验。

    组合:

    karma Jasmine VS Mocha Chai Sinon VS Jest Enzyme

    karma Jasmine:尽管比相当流行,可是在react连串中不引入;更适用于AngularJS;

    Mocha Chai SinonJest Enzyme 齐驱并驾,都能够先参数看看,心仪用哪些选哪些;

    • redux中文文书档案援用使用Jest Enzyme Redux中文文书档案-编写测验
    • react官方也引用使用 Jest

    早先时代入门多读书学习,多品尝,没坏处;能够对照二种测量检验用例的差异;选拔本身爱怜的写法;

    断言库

    断言库提供了超多语义化的艺术来对值做丰富多彩的论断。

    • chai: 近些日子可比盛行的断言库,帮助TDD(assert),BDD(expect、should)二种风格
    • should.js:也是tj大神所写

    api 和 fetch 工具库

    接下去便是api 层相关的了。前边讲过调用后台央求是用的 fetch ,小编封装了七个方法来简化调用和结果管理:getJSON()postJSON() ,分别对应 GET 、POST 乞请。先来探访 api 层代码:

    import { fetcher } from '@/utils/fetcher'; export function getBizTableData(payload) { return fetcher.postJSON('/api/biz/get-table', payload); }

    1
    2
    3
    4
    5
    import { fetcher } from '@/utils/fetcher';
     
    export function getBizTableData(payload) {
        return fetcher.postJSON('/api/biz/get-table', payload);
    }

    事情代码很简短,那么测量检验用例也很简短:

    import sinon from 'sinon'; import { fetcher } from '@/utils/fetcher'; import * as api from '@/services/bizApi'; /* 测试 bizApi */ describe('bizApi', () => { let fetcherStub; beforeAll(() => { fetcherStub = sinon.stub(fetcher); }); // ... /* getBizTableData api 应该调用正确的 method 和传递准确的参数 */ test('getBizTableData api should call postJSON with right params of fetcher', () => { /* 模拟参数 */ const payload = {a: 1, b: 2}; api.getBizTableData(payload); /* 检查是或不是调用了工具库 */ expect(fetcherStub.postJSON.callCount).toBe(1); /* 检查调用参数是或不是科学 */ expect(fetcherStub.postJSON.lastCall.calledWith('/api/biz/get-table', payload)).toBe(true); }); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import sinon from 'sinon';
    import { fetcher } from '@/utils/fetcher';
    import * as api from '@/services/bizApi';
     
    /* 测试 bizApi */
    describe('bizApi', () => {
        
        let fetcherStub;
     
        beforeAll(() => {
            fetcherStub = sinon.stub(fetcher);
        });
     
        // ...
     
        /* getBizTableData api 应该调用正确的 method 和传递正确的参数 */
        test('getBizTableData api should call postJSON with right params of fetcher', () => {
            /* 模拟参数 */
            const payload = {a: 1, b: 2};
            api.getBizTableData(payload);
     
            /* 检查是否调用了工具库 */
            expect(fetcherStub.postJSON.callCount).toBe(1);
            /* 检查调用参数是否正确 */
            expect(fetcherStub.postJSON.lastCall.calledWith('/api/biz/get-table', payload)).toBe(true);
        });
    });

    鉴于 api 层直接调用了工具库,所以这里用 sinon.stub() 来替换工具库到达测量试验指标。

    接着就是测验本人包装的 fetch 工具库了,这里 fetch 小编是用的 isomorphic-fetch ,所以选拔了 nock 来模拟 Server 进行测验,首若是测验平常访问归来结果和模拟服务器相当等,示例片段如下:

    import nock from 'nock'; import { fetcher, FetchError } from '@/utils/fetcher'; /* 测试 fetcher */ describe('fetcher', () => { afterEach(() => { nock.cleanAll(); }); afterAll(() => { nock.restore(); }); /* 测量试验 getJSON 获得平日数据 */ test('should get success result', () => { nock('') .get('/test') .reply(200, {success: true, result: 'hello, world'}); return expect(fetcher.getJSON('); }); // ... /* 测量检验 getJSON 捕获 server 大于 400 的不得了情形 */ test('should catch server status: 400 ', (done) => { const status = 500; nock('') .get('/test') .reply(status); fetcher.getJSON(') => { expect(error).toEqual(expect.any(FetchError)); expect(error).toHaveProperty('detail'); expect(error.detail.status).toBe(status); done(); }); }); /* 测试getJSON 传递精确的 headers 和 query strings */ test('check headers and query string of getJSON()', () => { nock('', { reqheaders: { 'Accept': 'application/json', 'authorization': 'Basic Auth' } }) .get('/test') .query({a: '123', b: 456}) .reply(200, {success: true, result: true}); const headers = new Headers(); headers.append('authorization', 'Basic Auth'); return expect(fetcher.getJSON( '', {a: '123', b: 456}, headers)).resolves.toBe(true); }); // ... });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    import nock from 'nock';
    import { fetcher, FetchError } from '@/utils/fetcher';
     
    /* 测试 fetcher */
    describe('fetcher', () => {
     
        afterEach(() => {
            nock.cleanAll();
        });
     
        afterAll(() => {
            nock.restore();
        });
     
        /* 测试 getJSON 获得正常数据 */
        test('should get success result', () => {
            nock('http://some')
                .get('/test')
                .reply(200, {success: true, result: 'hello, world'});
     
            return expect(fetcher.getJSON('http://some/test')).resolves.toMatch(/^hello. $/);
        });
     
        // ...
     
        /* 测试 getJSON 捕获 server 大于 400 的异常状态 */
        test('should catch server status: 400 ', (done) => {
            const status = 500;
            nock('http://some')
                .get('/test')
                .reply(status);
     
            fetcher.getJSON('http://some/test').catch((error) => {
                expect(error).toEqual(expect.any(FetchError));
                expect(error).toHaveProperty('detail');
                expect(error.detail.status).toBe(status);
                done();
            });
        });
     
       /* 测试 getJSON 传递正确的 headers 和 query strings */
        test('check headers and query string of getJSON()', () => {
            nock('http://some', {
                reqheaders: {
                    'Accept': 'application/json',
                    'authorization': 'Basic Auth'
                }
            })
                .get('/test')
                .query({a: '123', b: 456})
                .reply(200, {success: true, result: true});
     
            const headers = new Headers();
            headers.append('authorization', 'Basic Auth');
            return expect(fetcher.getJSON(
                'http://some/test', {a: '123', b: 456}, headers)).resolves.toBe(true);
        });
        
        // ...
    });

    骨干也没怎么复杂的,首要注意 fetch 是 promise 再次来到,jest 的各个异步测量试验方案都能很好满意。

    余下的风度翩翩部分正是跟 UI 相关的了。

    测量试验框架

    测试框架的作用是提供部分有益的语法来说述测量试验用例,以至对用例实行分组。

    参照他事他说加以考察资料

    • 阮大大 - React 测量检验入门教程
    • 测量试验驱动编写 React 简易计算器
    • React Redux单元测量试验半个小时入门
    • React 测验驱动教程
    • 使用 AVA 和 Enzyme 测试 React 组件(三)
    • React汉语文书档案 - 测量试验工具集
    • 测验框架 Mocha 实例教程
    • 使用Mocha Chai Sinon 测试React Redux的web应用

    mock库

    • sinon.js:使用Sinon,我们能够把其它JavaScript函数替换到叁个测量试验替身。通过安顿,测量检验替身能够完毕琳琅满指标天职来让测量检验复杂代码变得轻巧。扶植spies, stub, fake XMLHttpRequest, Fake server, Fake time,很强盛

    容器组件

    容器组件的显要指标是传递 state 和 actions,看下工具栏的容器组件代码:

    import { connect } from 'react-redux'; import { getBizToolbar } from '@/store/selectors'; import * as actions from '@/store/actions/bizToolbar'; import BizToolbar from '@/components/BizToolbar'; const mapStateToProps = (state) => ({ ...getBizToolbar(state) }); const mapDispatchToProps = { reload: actions.reload, updateKeywords: actions.updateKeywords }; export default connect(mapStateToProps, mapDispatchToProps)(BizToolbar);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { connect } from 'react-redux';
    import { getBizToolbar } from '@/store/selectors';
    import * as actions from '@/store/actions/bizToolbar';
    import BizToolbar from '@/components/BizToolbar';
     
    const mapStateToProps = (state) => ({
        ...getBizToolbar(state)
    });
     
    const mapDispatchToProps = {
        reload: actions.reload,
        updateKeywords: actions.updateKeywords
    };
     
    export default connect(mapStateToProps, mapDispatchToProps)(BizToolbar);

    那正是说测验用例的目标也是检查那一个,这里运用了 redux-mock-store 来模拟 redux 的 store :

    import React from 'react'; import { shallow } from 'enzyme'; import configureStore from 'redux-mock-store'; import BizToolbar from '@/containers/BizToolbar'; /* 测量检验容器组件 BizToolbar */ describe('BizToolbar container', () => { const initialState = { bizToolbar: { keywords: 'some keywords' } }; const mockStore = configureStore(); let store; let container; beforeEach(() => { store = mockStore(initialState); container = shallow(); }); /* 测验 state 到 props 的映照是或不是无误 */ test('should pass state to props', () => { const props = container.props(); expect(props).toHaveProperty('keywords', initialState.bizToolbar.keywords); }); /* 测验 actions 到 props 的照射是还是不是科学 */ test('should pass actions to props', () => { const props = container.props(); expect(props).toHaveProperty('reload', expect.any(Function)); expect(props).toHaveProperty('updateKeywords', expect.any(Function)); }); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    import React from 'react';
    import { shallow } from 'enzyme';
    import configureStore from 'redux-mock-store';
    import BizToolbar from '@/containers/BizToolbar';
     
    /* 测试容器组件 BizToolbar */
    describe('BizToolbar container', () => {
        
        const initialState = {
            bizToolbar: {
                keywords: 'some keywords'
            }
        };
        const mockStore = configureStore();
        let store;
        let container;
     
        beforeEach(() => {
            store = mockStore(initialState);
            container = shallow();
        });
     
        /* 测试 state 到 props 的映射是否正确 */
        test('should pass state to props', () => {
            const props = container.props();
     
            expect(props).toHaveProperty('keywords', initialState.bizToolbar.keywords);
        });
     
        /* 测试 actions 到 props 的映射是否正确 */
        test('should pass actions to props', () => {
            const props = container.props();
     
            expect(props).toHaveProperty('reload', expect.any(Function));
            expect(props).toHaveProperty('updateKeywords', expect.any(Function));
        });
    });

    超轻易有木有,所以也没啥可说的了。

    断言(assertions)

    预感是单元测量试验框架中挑钱塘的局地,断言失利会导致测量试验不通过,或报告错误消息。

    对此大规模的断言,举一些事举例下:

    • 同等性断言 Equality Asserts

      • expect.toEqual
      • expect.not.toEqual
    • 相比较性断言 Comparison Asserts

      • expect.toBeGreaterThan
      • expect.toBeLessThanOrEqual
    • 类型性断言 Type Asserts

      • expect.toBeInstanceOf
    • 条件性测量检验 Condition Test

      • expect.toBeTruthy()
      • expect.toBeFalsy()
      • expect.toBeDefined()

    测验集成管理工科具

    • karma:GoogleAngular 共青团和少先队写的,成效很苍劲,有众多插件。能够三回九转真实的浏览器跑测量检验。能够用部分测验覆盖率总括的工具计算一下覆盖率;或是能够插手持续集成,提交代码后自行跑测验用例。

    UI 组件

    那边以表格组件作为示范,大家将直接来看测量试验用例是怎么写。平日的话 UI 组件大家根本测量试验以下多少个方面:

    • 是不是渲染了理所当然的 DOM 构造
    • 体制是还是不是准确
    • 作业逻辑触发是或不是精确

    下边是测量试验用例代码:

    JavaScript

    import React from 'react'; import { mount } from 'enzyme'; import sinon from 'sinon'; import { Table } from 'antd'; import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil'; import BizTable from '@/components/BizTable'; /* 测试 UI 组件 BizTable */ describe('BizTable component', () => { const defaultProps = { loading: false, pagination: Object.assign({}, { current: 1, pageSize: 15, total: 2 }, defaultSettingsUtil.pagination), data: [{id: 1}, {id: 2}], getData: sinon.fake(), updateParams: sinon.fake() }; let defaultWrapper; beforeEach(() => { defaultWrapper = mount(<BizTable {...defaultProps}/>); }); // ... /* 测量试验是不是渲染了科学的意义子组件 */ test('should render table and pagination', () => { /* 是或不是渲染了 Table 组件 */ expect(defaultWrapper.find(Table).exists()).toBe(true); /* 是还是不是渲染了 分页器 组件,样式是不是准确(mini) */ expect(defaultWrapper.find('.ant-table-pagination.mini').exists()).toBe(true); }); /* 测验第叁遍加载时数据列表为空是或不是发起加载数据央浼 */ test('when componentDidMount and data is empty, should getData', () => { sinon.spy(BizTable.prototype, 'componentDidMount'); const props = Object.assign({}, defaultProps, { pagination: Object.assign({}, { current: 1, pageSize: 15, total: 0 }, defaultSettingsUtil.pagination), data: [] }); const wrapper = mount(<BizTable {...props}/>); expect(BizTable.prototype.componentDidMount.calledOnce).toBe(true); expect(props.getData.calledOnce).toBe(true); BizTable.prototype.componentDidMount.restore(); }); /* 测量试验 table 翻页后是还是不是科学触发 updateParams */ test('when change pagination of table, should updateParams', () => { const table = defaultWrapper.find(Table); table.props().onChange({current: 2, pageSize: 25}); expect(defaultProps.updateParams.lastCall.args[0]) .toEqual({paging: {current: 2, pageSize: 25}}); }); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    import React from 'react';
    import { mount } from 'enzyme';
    import sinon from 'sinon';
    import { Table } from 'antd';
    import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil';
    import BizTable from '@/components/BizTable';
     
    /* 测试 UI 组件 BizTable */
    describe('BizTable component', () => {
        
        const defaultProps = {
            loading: false,
            pagination: Object.assign({}, {
                current: 1,
                pageSize: 15,
                total: 2
            }, defaultSettingsUtil.pagination),
            data: [{id: 1}, {id: 2}],
            getData: sinon.fake(),
            updateParams: sinon.fake()
        };
        let defaultWrapper;
     
        beforeEach(() => {
            defaultWrapper = mount(<BizTable {...defaultProps}/>);
        });
     
        // ...
     
        /* 测试是否渲染了正确的功能子组件 */
        test('should render table and pagination', () => {
            /* 是否渲染了 Table 组件 */
            expect(defaultWrapper.find(Table).exists()).toBe(true);
            /* 是否渲染了 分页器 组件,样式是否正确(mini) */
            expect(defaultWrapper.find('.ant-table-pagination.mini').exists()).toBe(true);
        });
     
        /* 测试首次加载时数据列表为空是否发起加载数据请求 */
        test('when componentDidMount and data is empty, should getData', () => {
            sinon.spy(BizTable.prototype, 'componentDidMount');
            const props = Object.assign({}, defaultProps, {
                pagination: Object.assign({}, {
                    current: 1,
                    pageSize: 15,
                    total: 0
                }, defaultSettingsUtil.pagination),
                data: []
            });
            const wrapper = mount(<BizTable {...props}/>);
     
            expect(BizTable.prototype.componentDidMount.calledOnce).toBe(true);
            expect(props.getData.calledOnce).toBe(true);
            BizTable.prototype.componentDidMount.restore();
        });
     
        /* 测试 table 翻页后是否正确触发 updateParams */
        test('when change pagination of table, should updateParams', () => {
            const table = defaultWrapper.find(Table);
            table.props().onChange({current: 2, pageSize: 25});
            expect(defaultProps.updateParams.lastCall.args[0])
                .toEqual({paging: {current: 2, pageSize: 25}});
        });
    });

    得益于设计分层的创制,大家比较轻易选取构造 props 来到达测量检验目标,结合 enzymesinon ,测量检验用比方故保持轻易的音频。

    断言库

    断言库首要提供上述断言的语义化方法,用于对涉足测量试验的值做各式各样的推断。那些语义化方法会重回测量试验的结果,要么成功、要么退步。见惯司空的断言库有 Should.js, Chai.js 等。

    测验脚本的写法

    兴致索然,测量试验脚本与所要测验的源码脚本同名,可是后缀名字为.test.js(表示测验)也许.spec.js(表示原则)。

    // add.test.js
    var add = require('./add.js');
    var expect = require('chai').expect;
    
    describe('加法函数的测试', function() {
      it('1 加 1 应该等于 2', function() {
        expect(add(1, 1)).to.be.equal(2);
      });
    });
    

    上面这段代码,就是测量试验脚本,它能够独自实施。测量试验脚本里面应该包含二个或多个describe块,每一个describe块应该包蕴八个或八个it块。

    describe块称为"测量检验套件"(test suite),表示生机勃勃组有关的测量试验。它是二个函数,第八个参数是测量试验套件的称号("加法函数的测量检验"),第三个参数是八个实际上推行的函数。

    describe干的事情正是给测量试验用例分组。为了尽量多的隐瞒各类情状,测验用例往往会有过多。那个时候通过分组就足以相比低价的保管(这里提一句,describe是足以嵌套的,也正是说外层分组了今后,内部仍然为能够分子组)。此外还可能有三个那二个首要的特色,正是各种分组都能够展开预管理(before、beforeEach)和后管理(after, afterEach)。

    it块称为"测验用例"(test case),表示叁个独自的测量检验,是测量试验的细小单位。它也是一个函数,第二个参数是测量检验用例的称呼("1 加 1 应该对等 2"),第叁个参数是三个实际上试行的函数。

    大型项目有那多少个测量检验用例。不时,大家意在只运转当中的几个,当时能够用only方法。describe块和it块都同意调用only方法,表示只运营有个别测验套件或测验用例。别的,还恐怕有skip方法,表示跳过钦点的测量检验套件或测验用例。

    describe.only('something', function() {
      // 只会跑包在里面的测试
    })
    
    it.only('do do', () => {
      // 只会跑这一个测试
    })
    

    总结

    如上便是其一场合完整的测验用例编写思路和示范代码,文中谈到的思路方法也全然能够用在 VueAngular 项目上。完整的代码内容在 这里 (重要的事务多说四遍,各位童鞋以为好救助去给个 哈)。

    末尾我们能够行使覆盖率来看下用例的隐蔽程度是不是丰硕(经常的话不要特意追求 100%,依据实际情况来定):
    新葡亰496net 7

    单元测量试验是 TDD 测验驱动开辟的底工。从以上全体经过能够看出,好的规划分层是相当的轻便编写测验用例的,单元测验不单单只是为了保险代码品质:他会逼着你思考代码设计的创设,推却面条代码

    借用 Clean Code 的甘休语:

    二零零七 年,在参预于西雅图举行的高速大会时,Elisabeth Hedrickson 递给本身一条看似 Lance Armstrong热销的这种灰绿腕带。那条腕带地方写着“沉迷测量检验”(Test Obsessed)的字样。小编欢跃地戴上,并骄傲地直接系着。自从 1998 年从 KentBeck 那儿学到 TDD 以来,我的确迷上了测量检验驱动开垦。

    而是随后就时有产生了些奇事。笔者发掘本人不可能取下腕带。不止是因为腕带很紧,并且那也是条精气神上的管束。那腕带就是自家专业道德的发表,也是自身承诺尽己所能写出最好代码的唤醒。取下它,就像就是违反了这么些发表和承诺似的。

    所以它还在本人的手腕上。在写代码时,我用余光瞟见它。它直接指示本身,笔者做了写出清新代码的允诺。

    1 赞 1 收藏 评论

    新葡亰496net 8

    测量试验用例 test case

    为某些特殊指标而编辑的朝气蓬勃组测验输入、实施尺度以至预期结果,以便测量检验某些程序路线或核算是不是满意某些特定须求。

    貌似的情势为:

    it('should ...', function() { ... expect.toEqual;
    

    react 单测示例后生可畏

    react 单元测量试验框架 demo1

    该框架接收 karma mocha chai sinon 的咬合, 是生龙活虎种选拔工具相当多,同有时间自由度较高的消除方案。即便工具库使用的比较多,但拉动驾驭各类工具库的意义和行使,也助长深化对前面二个单元测量试验的通晓。

    中间React的测量试验库使用 enzyme,React测验必得选拔官方的测量试验工具库,可是它用起来相当不足便利,所以有人做了打包,推出了有的第三方库,在那之中Airbnb集团的Enzyme最轻易上手。

    有关该库的 api 使用可参照:

    合葡萄牙共和国语档

    阮先生React测验入门

    测量检验套件 test suite

    平常性把意气风发组有关的测量试验称为叁个测量试验套件

    貌似的花样为:

    describe('test ...', function() { it('should ...', function() { ... }); it('should ...', function() { ... }); ...});
    

    react 单测示例二

    react 单元测量检验框架 demo2

    该框架只使用了Jest,是比较轻便的方案,同样也选拔了 enzyme。

    Jest 是推特开拓的二个测量检验框架,它集成了测验实行器、断言库、spy、mock、snapshot和测验覆盖率报告等效果。React项目作者也是使用Jest实行单测的,由此它们俩的符合度相当的高。
    事情发生前仅在其内部选用,后开源,何况是在Jasmine测量试验框架上蜕变开荒而来,使用了眼熟的expect(value卡塔尔.toBe(other卡塔尔这种断言格式。

    PS: 前段时间 enzyme 使用时需求投入设置如下:

    import Enzyme from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    
    Enzyme.configure({ adapter: new Adapter() });
    

    地方七个框架方案中都有投入该配置的点子,详见示例。

    spy

    正如 spy 字面包车型大巴意思同样,大家用这种“窥伺者”来“监视”函数的调用途境

    透过对监视的函数进行打包,能够由此它精晓的通晓该函数被调用过几回、传入什么参数、重回什么结果,甚至是抛出的相当情状。

    var spy = sinon.spy(MyComp.prototype, 'componentDidMount');...expect(spy.callCount).toEqual;
    

    参考

    • 聊风华正茂聊前端自动化测验
    • 前面三个自动化单元测量检验初探
    • Javascript的Unit Test
    • 单元测验all in one
    • Sinon指南: 使用Mocks, Spies 和 Stubs编写JavaScript测试
    • 测量试验框架 Mocha 实例教程

    stub

    不经常会选取stub来松手也许直接沟通掉风流倜傥部分代码,来到达隔开的指标

    一个stub能够应用最少的重视方法来效仿该单元测量试验。比方八个艺术恐怕凭仗另一个方式的推行,而后面一个对大家的话是透明的。好的做法是使用stub 对它举办隔开替换。那样就落到实处了越来越准确的单元测量试验。

    var myObj = { prop: function() { return 'foo'; }};sinon.stub(myObj, 'prop').callsFake(function() { return 'bar';});myObj.prop(); // 'bar'
    

    mock

    mock一般指在测验进程中,对于有个别不便于布局大概不便于拿到的靶子,用四个虚构的对象来创造以便测验的测量试验方法

    广义的讲,以上的 spy 和 stub 等,以致部分对模块的比葫芦画瓢,对 ajax 重回值的依葫芦画瓢、对 timer 的模仿,都叫做 mock 。

    测量试验覆盖率(code coverage卡塔尔

    用于总计测验用例对代码的测量试验意况,生成对应的表格,比方 istanbul 是见怪不怪的测量检验覆盖率总计工具

    Jest

    新葡亰496net 9

    分化于"古板的"的 jasmine / Mocha / Chai 等前端测量试验框架 -- Jest的选择更简短,况兼提供了更加高的集成度、更丰硕的功能。

    Jest 是 Instagram出品的一个测试框架,相对其余测量试验框架,其一大特色就是正是松手了常用的测量检验工具,比如自带断言、测量检验覆盖率工具,完结了开箱即用。

    除此以外, Jest 的测验用例是并行施行的,何况只实行产生更换的文本所对应的测量试验,进步了测量试验速度。

    编排单元测量试验的语法平时极其轻松;对于jest来讲,由于其里面使用了 Jasmine 2 来进行测量试验,故其用例语法与 Jasmine 相仿。

    骨子里,只要先记那住多少个单词,就足以应付大超级多测量检验景况了:

    • describe: 定义贰个测量试验套件
    • it:定义多少个测验用例
    • expect:断言的评定标准
    • toEqual:断言的相比结实
    describe('test ...', function() { it('should ...', function() { expect.toEqual; expect(sth.length).toEqual; expect(sth > oth).toEqual;});
    

    Jest 称得上本人是三个 “Zero configuration testing platform”,只需在 npm scripts里头配备了test: jest,就能够运营npm test,自动识别并测量试验符合其准则的(日常是 __test__ 目录下的)用例文件。

    事实上行使中,适当的自定义配置一下,会拿到更切合大家的测验场景:

    //jest.config.jsmodule.exports = { modulePaths: [ "<rootDir>/src/" ], moduleNameMapper: { ".$": '<rootDir>/__test__/NullModule.js' }, collectCoverage: true, coverageDirectory: "<rootDir>/src/", coveragePathIgnorePatterns: [ "<rootDir>/__test__/" ], coverageReporters: ["text"],};
    

    在此个轻易的布署文件中,我们钦命了测量试验的“根目录”,配置了覆盖率(内置的istanbul)的有的格式,并将原本在webpack中对体制文件的援引指向了多少个航空模型块,进而跳过了那风华正茂对测量试验无足挂齿的环节

    //NullModule.jsmodule.exports = {};
    

    其它值得生机勃勃提的是,由于jest.config.js是七个会在npm剧本中被调用的平凡 JS 文件,而非XXX.json.XXXrc的款式,所以 nodejs 的独家操作都能够开展,比方引进 fs 举办预管理读写等,灵活性超高,能够很好的极其各个项目

    鉴于是面向src目录下测量检验其React代码,而且还接纳了ES6语法,所以项目下必要存在一个.babelrc文件:

    { "presets": ["env", "react"]}
    

    以上是主旨的布局,而实乃因为webpack能够编写翻译es6的模块,日常将babel中设为{ "modules": false },当时的构造为:

    //package.json"scripts": { "test": "cross-env NODE_ENV=test jest",},
    
    //.babelrc{ "presets": [ ["es2015", {"modules": false}], "stage-1", "react" ], "plugins": [ "transform-decorators-legacy", 如果对软件测试、接口测试、自动化测试、性能测试、LR脚本开发、面试经验交流。 "react-hot-loader/babel" 感兴趣可以175317069,群内会有不定期的发放免费的资料链接,这些资料都是从各 ], 个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处 "env": { 之后分享给大家。 "test": { "presets": [ "es2015", "stage-1", "react" ], "plugins": [ "transform-decorators-legacy", "react-hot-loader/babel" ] } }}
    

    Enzyme

    Enzyme 来自于活跃在 JavaScript 开源社区的 Airbnb 集团,是对法定测量试验工具库(react-addons-test-utils)的卷入。

    其生机勃勃单词的London读音为 ['enzaɪm],酵素或酶的意思,Airbnb 并未给它安顿一个Logo,猜想纵然想取用它来讲授 React 组件的乐趣啊。

    它模拟了 jQuery 的 API,特别直观而且易于使用和读书,提供了部分异样的接口和多少个方式来减弱测验的标准代码,方便判定、操纵和遍历 React Components 的输出,而且裁减了测验代码和促成代码之间的耦合。

    相似选择 Enzyme 中的 mountshallow 方法,将指标组件转化为多个 ReactWrapper目的,并在测量试验中调用其各样办法:

    import Enzyme,{ mount } from 'enzyme';...describe('test ...', function() { it('should ...', function() { wrapper = mount( <MyComp isDisabled={true} /> ); expect( wrapper.find.exists.toBeTruthy;
    

    sinon

    新葡亰496net 10

    图中那位“作者牵着马”的并非卷帘老将沙师弟...其实图中的传说就是人所皆知的“Troy木马”;大致意思便是希腊(Ελλάδα卡塔尔国人包围了Troy人十多年,久攻不下,心生风华正茂计,把营盘都撤了,只留下叁个受人尊敬的人的木马,甚至那位被扒光还被打得够呛的人,也正是这里要谈的中坚sinon,由她欺骗Troy人 --- 前面包车型大巴故事剧情大家就都听得多了就能说的清楚了。

    进而那个命名的测验工具呢,也多亏各样伪装渗透方法的合集,为单元测量检验提供了单身而增多的 spy, stub 和 mock 方法,包容种种测量检验框架。

    尽管如此 Jest 本人也可能有一点点达成 spy 等的手腕,但 sinon 使用起来更为实惠。

    此间不展开研讨精髓的 “测验驱动开拓”(TDD - test driven development卡塔尔(قطر‎ 理论

    粗略的说,把测验正向加诸开垦,先写用例再逐级完结,就是TDD,那是很好明白的。

    而当大家反过头来,对既有代码补充测量检验用例,使其测量检验覆盖率不断拉长,并在这里进度中改良原有设计,修复潜在难题,同期又保障原有接口不收影响,这种 TDD 行为即便没人称之为“测量检验驱动重构”(test driven refactoring卡塔尔国,但“重构”那几个定义本身就含有了用测验遮风避雨的意趣,是至关重要的题中之意。

    对于一些零零件和共有函数等,康健的测量试验也是后生可畏种最棒的利用表达书。

    失败-编码-通过 三部曲

    出于测量检验结果中,成功的用例会用紫铜色代表,而未果的部分会呈现为革命,所以单元测量试验也平时被称为 “Red/Green Testing” 或 “Red/Green Refactoring” , 那也是 TDD 中的日常性步骤:

    1. 拉长多个测量检验
    2. 运维具备测量检验,看看新加的那么些是否败退了;借使能学有所成则重复步骤1
    3. 依靠战败报错,有指向的编排或改写代码;这一步的独一指标就是经过测验,先不用纠缠细节
    4. 双重运转测量试验;假诺能学有所成则跳到步骤5,不然重复步骤3
    5. 重构已经通过测验的代码,使其更可读、更易维护,且不影响通过测验
    6. 再度步骤1

    新葡亰496net 11新葡亰496net 12

    假诺对软件测验、接口测验、自动化测量试验、质量测量检验、LPAJERO脚本开采、面试经历交换。感兴趣能够175317069,群内会有不依期的发给免费的素材链接,这个资料都以从各类技术网址访问、收拾出来的,如果您有好的求学资料能够私聊发小编,小编会表明出处之后享受给我们。

    解读测量试验覆盖率

    新葡亰496net 13

    这就是 jest 内置的 istanbul 输出的覆盖率结果。

    为此称之为“吉隆坡”,是因为土耳其共和国地毯世界著名,而地毯是用来"覆盖"的臘‍♀️。

    报表中的第2列至第5列,分别对应八个权衡维度:

    • 说话覆盖率(statement coverage):是还是不是每种语句都执行了
    • 分段覆盖率(branch coverage):是或不是每种if代码块都施行了
    • 函数覆盖率(function coverage):是或不是各类函数都调用了
    • 行覆盖率(line coverage):是不是每风流倜傥行都实行了

    测试结果依附覆盖率被分为“米红、血红、金棕”两种,应该视具体情状尽量进步相应模块的测验覆盖率。

    优化重视 让 React 组件变得 testable

    客观编排组件化的 React,并将丰富独立、功效专风姿罗曼蒂克的组件作为测验的单元,将使得单元测量检验变得轻松;

    反过来讲,测量试验的经过让我们更易厘清关系,将本来的机件重构或降解成更客观的构造。分离出的子组件往往也更易于写成stateless的无状态组件,使得质量和关怀点越发优化。

    眼看内定 PropTypes

    对于部分事情发生前定义并不明显的零器件,能够统后生可畏引进 prop-types,分明组件可吸收接纳的props;一方面能够在付出/编写翻译进程中任何时间任何地方开掘错误,别的也足以在集团中其余成员援引组件时产生二个清晰的列表。

    用例的预管理或后管理

    可以用beforeEachafterEach做一些联结的预置和善后专业,在各种用例的先头和现在都会自行调用:

    describe('test components/Comp', function() { let wrapper; let spy; beforeEach(function() { jest.useFakeTimers(); spy = sinon.spy(Comp.prototype, 'componentDidMount'); }); afterEach(function() { jest.useRealTimers(); wrapper && wrapper.unmount(); didMountSpy.restore(); didMountSpy = null; }); it('应该正确显示基本结构', function() { wrapper = mount( <Comp ... /> ); expect(wrapper.find.text.toEqual; }); ...});
    

    调用组件的“私有”方法

    对此一些零件中,倘使指望在测量试验阶段调用到其部分中间方法,又不想对原组件退换过大的,能够用instance()获取组件类实例:

    it('应该正确获取组件类实例', function() { var wrapper = mount( <MultiSelect name="HELLOKITTY" placeholder="select sth..." /> ); var wi = wrapper.instance(); expect( wi.props.name ).toEqual( "HELLOKITTY" ); expect( wi.state.open ).toEqual;});
    

    异步操作的测量试验

    用作UI组件,React组件中一些操作必要延时进行,诸如onscrolloninput那类高频触发动作,要求做函数防抖或节流,比方常用的 lodash 的 debounce 等。

    所谓的异步操作,在不思考和 ajax 整合的购并测量试验的情事下,平日都是指此类操作,只用 setTimeout 是特别的,供给搭配 done 函数使用:

    //组件中const Comp = =>( <input type="text" onChange={ debounce(props.onSearch, 500) } />);
    
    //单元测试中it('应该在输入时触发回调', function { var spy = jest.fn(); var wrapper = mount( <Comp onChange={ spy } /> ); wrapper.find('#searchIpt').simulate; setTimeout=>{ expect.toHaveBeenCalledTimes; done(); }, 550);});
    

    某个大局和单例的比葫芦画瓢

    黄金年代部分模块中或许耦合了对 window.xxx 那类全局对象的引用,而完全去实例化这些指标恐怕又牵涉优秀多任何的题材,难以开展;那时候能够见招拆招,只模拟八个最小化的大局对象,保障测量检验的张开:

    //fakeAppFacade.jsvar facade = { router: { current: function() { return {name:null, params:null}; } }, appData: { symbol: "&yen;" }};window._appFacade = facade;module.exports = facade;
    
    //测试套件中import fakeFak from '../fakeAppFacade';
    

    别的比方 LocalStroage 这类对象,测量试验端景况中并未有原生扶助,也足以大致模拟一下:

    //fakeStorage.jsvar _util = {};var fakeStorage = { "set": function { _util['_fakeSave_' k] = v; }, "get": function { return _util['_fakeSave_' k] || null; }, "remove": function { delete _util['_fakeSave_' k]; }, "has": function { return _util.hasOwnProperty('_fakeSave_' k); }};module.exports = fakeStorage;
    

    棘手的 react-bootstrap/modal

    在二个品种中用到了 react-bootstrap 分界面库,测量试验壹个构件时,由于包含了其 Modal 模态弹窗,而弹窗组件是默许渲染到 document 中的,招致难以用日常的 find 方法等赢得

    减轻的措施是模仿二个渲染到容器组件原处的经常组件:

    //FakeReactBootstrapModal.jsimport React, {Component} from 'react';class FakeReactBootstrapModal extends Component { constructor { super; } render() { //原生的 react-bootstrap/Modal 无法被 enzyme 测试 const { show, bgSize, dialogClassName, children } = this.props; return show ? <div className={ `fakeModal ${bgSize} ${dialogClassName}` }>{children}</div> : null; }}export default FakeReactBootstrapModal;
    

    还要在组件渲染时,参预决断逻辑,使之能够支撑自定义的类替代 Modal 类:

    //ModalComp.jsimport { Modal } from 'react-bootstrap';...render() { const MyModal = this._modalClass || Modal; return (<MyModal bsSize={props.mode>1 ? "large" : "middle"} dialogClassName="custom-modal"> ... </MyModal>;}
    

    而测试套件中,达成一个测量试验专项使用的子类:

    //myModal.spec.jsimport ModalComp from 'components/ModalComp';class TestModalComp extends ModalComp { constructor { super; this._modalClass = FakeReactBootstrapModal; }}
    

    如此那般测量检验就可以顺遂实行,跳过了并不主要的 UI 效果,而各类逻辑都能被隐瞒了

    模拟fetch请求

    风姿浪漫旦对软件测验、接口测量检验、自动化测量检验、质量测量试验、L传祺脚本开拓、面试经历交换。感兴趣可以175317069,群内会有不定时的发放无需付费的材质链接,这一个材质都以从各类才干网址访问、整理出来的,要是你有好的就学质地可以私聊发作者,小编会注明出处之后享受给我们。

    在单元测量检验的长河中,难免境遇一些亟待长途央求数据的情事,比方组件获取早先化数据、提交变化数据等。

    要在意这种测量试验的目标可能考查组件本人的表现,而非入眼关心实际远程数据的融会测量试验,所以我们无需真实的伸手,能够简轻松单的模仿一些伸手的光景。

    sinon 中有一点点模仿 XMLHttpRequest 需要的艺术, jest 也许有部分第三方的库化解 fetch 的测量试验;

    在大家的项目中,根据实际的用法,自身完结一个类来效仿伏乞的响应:

    //FakeFetch.jsimport { noop } from 'lodash';const fakeFetch = (jsonResult, isSuccess=true, callback=noop)=>{ const blob = new Blob( [JSON.stringify(jsonResult)], {type : 'application/json'} ); return =>{ console.log('FAKE FETCH', args); callback.call(null, args); return isSuccess ? Promise.resolve( new Response( blob, {status:200, statusText:"OK"} ) ) : Promise.reject( new Response( blob, {status:400, statusText:"Bad Request"} ) ) }};export default fakeFetch;
    
    //Comp.spec.jsimport fakeFetch from '../FakeFetch';const _fc = window.fetch; //缓存“真实的”fetchdescribe('test components/Comp', function() { let wrapper; afterEach(function() { wrapper && wrapper.unmount(); window.fetch = _fc; //恢复 }); it("应该在远程请求时响应onRemoteData", =>{ window.fetch = fakeFetch({ brand: "GoBelieve", tree: { node: '总部', children: null } }); let spy = jest.fn(); wrapper = mount( <Comp onRemoteData={ spy } /> ); jest.useRealTimers(); _clickTrigger(); //此时应该发起请求 setTimeout=>{ expect(wrapper.html.toMatch; expect.toHaveBeenCalledTimes; done(); }, 500); });}); 
    

    单元测验作为意气风发种卓越的成本和重构花招,在软件开拓领域被分布确认和平运动用;前端领域也日趋积淀起了丰盛的测量试验框架和章程。

    单元测量检验可感到大家的支付和保护提供基本功保险,使大家在思绪清楚、心中有底的情景下做到对代码的搭建和重构;

    亟待注意的是,世上未有手到康复的良药,单元测量检验也不若是万金油,秉持严谨认真担任的情态工夫从根本上保障我们专门的学业的开展。

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:新葡亰496net干什么要写测量试验用例,的部分提

    关键词: