请选择 进入手机版 | 继续访问电脑版

设为首页 收藏本站
思科社区 关注
思科社区

   思科 CCO 登录 推荐
 找回密码
 立即注册

搜索
热搜: 邮件服务器
查看: 799|回复: 2

【原创翻译】有效的代码重构(3)

[复制链接]
发表于 2019-6-12 17:18:47 | 显示全部楼层 |阅读模式
第3部分:测试的重要作用

如果连您都不喜欢测试自己的产品,那么很可能您的客户也不会乐意试用它。



下面我将引导您来建立一个有效的测试架构。考虑到方便搭建与运行,我使用了Jest(https://facebook.github.io/jest/)和Enzyme(http://airbnb.io/enzyme/)。您可以根据如下链接的指引,将这两个库配置到React/Redux的应用之中。
·        使用Jest和Enzyme实施基本组件测试(https://hackernoon.com/implementing-basic-component-tests-using-jest-and-enzyme-d1d8788d627a
·        如何用Jest测试React的各种组件(https://www.sitepoint.com/test-react-components-jest/
设置模拟数据
如果测试的是现有应用,那么您应该了解现有数据的形态和使用方式。在大多数情况下,随着新功能的添加,API会被微调与改进。对于我的重构应用项目,我创建了两个带有数据的文件:一个带有API的各种响应,而另一个则带有Redux的状态。您可能需要通过稍许的修改和大量的复制/粘贴来创建这两个文件,不过这些工作都是一次性的。
产生这两个数据文件目的有二:
·        首先(也是最明显的),您需要测试的许多元素都会以某种方式来显示或操纵数据。
·        其次,通过快速地参考数据的形态,以便有效地确定如何撰写测试程序,并分析代码可能出现的问题。
如果所测数据过于敏感的话,有时候存储API的响应与状态并不太可行。在此情况下,您可以使用带有fakcer的json-schema-faker库(请参考https://www.npmjs.com/package/json-schema-faker),或chance库(请参考https://www.npmjs.com/package/chance)来生成随机数据。我建议您一次性生成数据并存储到库中,而不是每次在运行测试时都使用其“种子”来生成新的数据。我将自己的文件存放在__fixtures__文件夹中。其目录结构如下所示:
/src
  /components
  /constants
  /containers
  /redux
   /__fixtures__
     /state.js
     /responses.js
    /app
     /appActions.js
     /appReducer.js
     /appSelectors.js
  /...


获取Redux整体状态的最简便方法是使用Redux DevTools的扩展(请参考https://chrome.google.com/websto ... ibfkpmmfibljd?hl=en)。您可以在状态视图中选择Raw选项卡,复制所有内容,并将其粘贴到一个带有module.exports声明的JavaScript文件中。我建议您只从状态和API的响应中获取小部分的记录,以减少Jest整体快照的大小。而具体保留多少条记录则完全取决于您自己的判断。例如:某个API的响应返回了一个包含着400条记录的数组,那么您肯定需要去除其中的绝大部分,以提高测试效率。
值得注意的是:使用有效的数据对于防止回归错误的产生是至关重要的。如果你用到的数据并不能代表应用中真实环境所用到的内容,那么测试的效果是无法保证重构质量的。
标准化您的测试
您在重构项目中往往需要编写大量的测试。在刚开始写测试的时候,我经常会出现命名不统一的情况。例如:在测试两个非常相似的组件时,我所用到的describe时常不尽相同。同时,标准化您的测试将有利于减少团队花费在理解测试程序上的时间。
确定测试文件的位置
大部分人喜欢复制整个src/目录,并将测试文件放在那里。无论您喜欢用.spec.js还是用.test.js作为测试文件的扩展名,都要注意一致性。Jest的默认配置会指定将测试文件放置在__tests__目录,并以.test.js作为扩展名。一旦您将此作为了标准,请务必添加到自述文件(README)之中,以便将来使用该应用的程序员能够籍此遵守下去。
建立格式
您应当为每个正在测试的上下文(如:React组件、Redux的selectors等)建立起“格式/结构”(format/structure)的关系。例如,我创建的每个React组件和容器的测试文件都会在其顶部有一个如下所示的setup函数:
const setup = (propOverrides, renderFn = shallow) => {
  const props = {
    propA: 'Some Value',
    propB: false,
    onClick:jest.fn(),
    ...propOverrides,
  };
  const wrapper = renderFn(<AppComponent {...props} />);
  return { props, wrapper };
};


这会使得我在测试组件时非常轻松,而无需编写大量额外的样板引用。同时,我还为React组件建立了一个特定的describe块结构。
describe('Component A', () => {
  describe('Snapshot validation', () => {
    it('matches its snapshot with valid props', () => {
      const { wrapper } = setup();
      expect(wrapper).toMatchSnapshot();
    });
  });
  describe('Event validation', () => {
    it('fires props.onClick when button is clicked', () => {
      const { wrapper, props } = setup();
      wrapper.find('button').simulate('click');
      expect(props.onClick).toHaveBeenCalled();
    });
  });
  // Note: This is only for connected components.
  describe('Redux validation', () => {
    const store = {
     getState: () => state,
     dispatch: jest.fn(),
     subscribe: () => {},
    };
    it('renders when connected to Redux state', () => {
      const wrapper = shallow(<ComponentA store={store} />);
      expect(wrapper).toHaveLength(1);
    });
  });
});


对于Redux的actions、reducers、和selectors,我进行了同样的操作。根据具体的测试环境,我还会使用WebStorm的文件模板(请参考:https://www.jetbrains.com/help/w ... file-templates.html)功能,以快速地创建各种测试文件。如果您使用的程序编辑器能够支持代码片段或文件模板的话,我建议您事先创建好相应的模板,以保持格式上的规范。同样,请记得在自述文件中留下简要的概述说明。
编写测试
假设您正在着手开始重构Redux的actions、reducer、和selectors的UI状态。由于您已经掌握了相应的状态数据,因此编写测试相对来说会比较简单。您只需要使用类似redux-mock-store的库(请参考:https://github.com/arnaudbenard/redux-mock-store)来模拟出用于测试actions的状态即可。
请务必在更改任何代码之前编写好了所有的测试。通过对重构之后的代码进行测试,我们能够发现一些人为的或无意犯下的错误。而事先保存好快照,则有助于我们发现那些字段或对象关键字上的拼写错误。
您应当对完成了重构的代码部分,和任何直接受到变更影响的代码编写测试程序。虽说深挖程序间的依赖关系、并编写出涉及到应用各个方面的测试,会是一项比较繁琐的工程,但是这会给您提供对于整个代码库的深入解析,并为代码的重构提供更多的整改机会。
应该测试什么?
确定测试内容的最简单和可靠的方式是:代码覆盖率。Jest具有内置的代码覆盖率检查功能,您可以用来生成带有覆盖率百分比的HTML报告,以显示代码中的哪些部分目前未被测试所覆盖到。虽然行覆盖(Line coverage,查看是否每一行都执行了?)会让您颇有成就感,但是分支覆盖(branch coverage,查看是否每个if代码块都执行了?)才是您需要去关注的地方。更多有关不同覆盖类型的概念,请参考:http://jasonrudolph.com/blog/200 ... ode-coverage-types/
如何知道自己已经完成了?
如前面所述,覆盖率是评估代码的某个部分是否通过了测试的最好工具。如果您的某个函数中包含一个if的声明,而它的else条件却没被测试所覆盖到,那么就会被覆盖率报告所指出。
我经常会习惯性地多看几次函数代码,并理解其逻辑关系,然后编写出能够故意破坏它的测试用例,包括:如果API的响应中缺少某个字段会怎么样?如果响应为空又会发生什么?
例如,假设有个selector能够加总某个特定区域里每个销售员所分配到的预算。而销售经理则拥有该地区所有可用的预算总和。显然,所有可用预算的总和应当始终大于或等于分配出去的总预算。那么,如果小于的话,会发生什么?代码中是否有if的声明来涉及这方面呢?通过阅读代码和编写测试,您可考虑到更多的极端情况。可见,代码覆盖率(function coverage)可以反映出每个函数是否被测试所调用到的情况。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
平均得分0 (0 评价)
发表于 2019-6-12 18:03:22 | 显示全部楼层
感谢版主分享,谢谢~
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
平均得分0 (0 评价)
发表于 2019-6-14 09:31:31 | 显示全部楼层
很好的测试分享,点赞
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
平均得分0 (0 评价)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver | 思科社区  

GMT+8, 2019-10-17 04:18 , Processed in 0.091218 second(s), 37 queries .

京ICP备09041801号-187

版权所有 :copyright:1992-2019 思科系统  重要声明 | 保密声明 | 隐私权政策 | 商标 |

快速回复 返回顶部 返回列表