前言

本文将使用jest进行测试驱动开发的示例,源码在github。重点说明在开发中引入单元测试后开发过程,以及测试先行的开发思路。

本文的重点是过程以及思维方法,框架以及用法不是重点。

本文使用的编程语言是javascript,思路对其他语言也是适用的。

本文主要以函数作为测试对象。

环境搭建

假设项目结构为

.
├── README.md
├── package.json
├── src
├── test
└── yarn.lock

  • 安装依赖
yarn add --dev jest

  • 打开package.json, 修改scripts字段
"scripts": {"test": "jest"
}

之后把测试文件放在test文件夹下,使用yarn test 即可查看测试结果

开发

现在要开发一个函数,根据传入的文件名判断是否为shell文件。

先做好约定:

  1. shell文件应该以 .sh 结尾
  2. shell文件不以 . 开头
  3. 函数为名 isShellFile

下面来看下开发步骤是怎么样的。

文件初始化

在src目录下新建 isShellFile.js

touch isShellFile.js

然后一行代码也不写,在test目录下新建 isShellFile.test.js

可以注意到,测试文件的名与源文件名类似,只是中间多了个 .test

touch isShellFile.test.js

第一个用例

打开测试文件 test/isShellFile.test.js ,编写第一个用例,也是最普通的一个: bash.sh

const isShellFile = require('../src/isShellFile')test('isShellFile', () => {// 调用函数,期望它返回值为 trueexpect(isShellFile('bash.sh')).toBeTruthy()
})

运行 yarn test , 结果如下:

FAIL  test/isShellFile.test.js✕ isShellFile (2ms)● isShellFileTypeError: isShellFile is not a function^^^3 | test('isShellFile', () => {4 |  > 5 |   expect(isShellFile('bash.sh')).toBeTruthy()|          ^6 | })

失败是意料之中的,因为 src/isShellFile.js 一行代码也没写,所以测试代码中第5行 isShellFile 无法进行函数调用。

完善源文件src/isShellFile.js

module.exports = function(filename) {}

这样 isShellFile 就可以作为函数被调用了。

再运行 yarn test

FAIL  test/isShellFile.test.js✕ isShellFile (7ms)● isShellFileexpect(received).toBeTruthy()^^^Received: undefined3 | test('isShellFile', () => {4 |   > 5 |   expect(isShellFile('bash.sh')).toBeTruthy()|                                  ^6 | })

又报错了,但这次报错原因跟上次不同,说明有进步。

这次报错原因是,期望函数调用返回值为真 , 但实际没有返回真 。

这是当然的,因为在源文件中,根本没有写返回语句。

为了让测试通过,修改 src/isShellFile.js

module.exports = function(filename) {+  return true}

运行 yarn test , 测试通过了!

PASS  test/isShellFile.test.js✓ isShellFile (3ms)Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.548s
Ran all test suites.

把上述修改,提交到版本控制系统中。

git add package.json yarn.lock src test
git commit -m 'feat: init jest test case'

第二个用例

观察我们的测试用例,发现太简单了,只有正面的用例,没有反面的、异常的用例

test('isShellFile', () => {expect(isShellFile('bash.sh')).toBeTruthy()
})

test/isShellFile.test.js 添加一个反面的用例

test('isShellFile', () => {expect(isShellFile('bash.sh')).toBeTruthy()
+  expect(isShellFile('bash.txt')).toBeFalsy()})

运行 yarn test

(可以发现,在开发过程中需要反复执行上述命令,有个偷懒的办法,执行yarn test --watch,即可监听文件变化,自动执行测试用例)

FAIL  test/isShellFile.test.js✕ isShellFile (6ms)● isShellFileexpect(received).toBeFalsy()^^^Received: true4 |  5 |   expect(isShellFile('bash.sh')).toBeTruthy()> 6 |   expect(isShellFile('bash.txt')).toBeFalsy()|                                   ^7 | })

报错了,期望返回假,但函数返回的是真。这是因为,源文件中, isShellFile 函数永远返回真!

完善 src/isShellFile.js 逻辑

module.exports = function(filename) {-  return true;
+  return filename.indexOf('.sh') > -1};

测试通过了

PASS  test/isShellFile.test.js✓ isShellFile (4ms)Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.568s
Ran all test suites.

把上述修改提交到版本控制系统

git commit -am 'fix: 函数永远返回真的bug'

第三个用例

我们再添加一个用例,这次考虑特殊情况: .sh 这种文件,不算是shell文件。

修改 test/isShellFile.test.js

expect(isShellFile("bash.sh")).toBeTruthy();expect(isShellFile("bash.txt")).toBeFalsy();
+  expect(isShellFile('.sh')).toBeFalsy()

测试不通过

FAIL  test/isShellFile.test.js✕ isShellFile (8ms)● isShellFileexpect(received).toBeFalsy()^^^Received: true5 |   expect(isShellFile("bash.sh")).toBeTruthy();6 |   expect(isShellFile("bash.txt")).toBeFalsy();> 7 |   expect(isShellFile('.sh')).toBeFalsy()|                              ^8 | });

说明逻辑待完善,修改 src/isShellFile.js

module.exports = function(filename) {-  return filename.indexOf(".sh") > -1;
+  let index = filename.indexOf(".sh");
+  return index > -1 && index != 0;};

测试通过(为精简文章内容,后面不再展示测试通过的输出),提交代码。

git commit -am 'fix: .sh应该返回false'

第四个用例

按照第三个用例的逻辑, .bash.sh 也不应该是shell文件,那么函数是否能正确判断呢,测试便知。

修改 test/isShellFile.test.js

expect(isShellFile('.sh')).toBeFalsy()
+  expect(isShellFile('.bash.sh')).toBeFalsy()

测试不通过

FAIL  test/isShellFile.test.js✕ isShellFile (3ms)● isShellFileexpect(received).toBeFalsy()^^^Received: true6 |   expect(isShellFile("bash.txt")).toBeFalsy();7 |   expect(isShellFile('.sh')).toBeFalsy()>  8 |   expect(isShellFile('.bash.sh')).toBeFalsy()|                                   ^9 | });

说明逻辑待完善,修改 src/isShellFile.js

module.exports = function(filename) {let index = filename.indexOf(".sh");
-  return index > -1 && index != 0;
+  return !filename.startsWith('.')  && index > -1;};

测试通过,提交代码。

git commit -am 'fix: .开头的文件不算sh文件'

第五个用例

再考虑一种情况,如果 .sh 出现在中间呢?如 bash.sh.txt , 它不应该是shell文件,来看看函数是否能通过测试。

修改 test/isShellFile.test.js

expect(isShellFile('.bash.sh')).toBeFalsy()
+  expect(isShellFile('bash.sh.txt')).toBeFalsy()

测试不通过

FAIL  test/isShellFile.test.js✕ isShellFile (5ms)● isShellFileexpect(received).toBeFalsy()^^^Received: true7 |   expect(isShellFile('.sh')).toBeFalsy()8 |   expect(isShellFile('.bash.sh')).toBeFalsy()>  9 |   expect(isShellFile('bash.sh.txt')).toBeFalsy()|                                      ^10 | });

说明逻辑待完善,修改 src/isShellFile.js

module.exports = function(filename) {-  let index = filename.indexOf(".sh");
-  return !filename.startsWith('.')  && index > -1;
+  let index = filename.lastIndexOf(".");
+  return !filename.startsWith('.')  && filename.substr(index) == '.sh';};

测试通过,提交代码。

git commit -am 'fix: .sh必须在结尾'

重构

我们来观察目前 src/isShellFile.js 的函数逻辑

module.exports = function(filename) {let index = filename.lastIndexOf(".");return !filename.startsWith('.')  && filename.substr(index) == '.sh';
};

对于 .bashrc 这样的文件,并不是shell文件,因为它是以 . 开头的。

则通过 filename.startsWith('.') 判断即可,前面的函数调用 filename.lastIndexOf(".") 是多余的。也即,目前的函数判断逻辑不够简明。

下面是一种优化思路:

module.exports = function(filename) {return !filename.startsWith('.')  && filename.substr(filename.lastIndexOf(".")) == '.sh';
};

测试通过,提交代码

git commit -am 'refactor: 优化逻辑'

注意,这个重构示例的重点是:

  1. 先完成功能,再重构
  2. 重构必须要有测试用例,且确保重构后全部测试用例通过

至于其他方面,见仁见智,并不是重点。

结论

本文通过代码实例,践行了测试先行的理念。

文中的代码实现不是重点,而是开发过程。

文中 文件初始化 及 第一个用例 的内容,尤其值得回味,它体现了两个思路:

  • 总是在有一个失败的单元测试后才开始编码
  • 用必要的最小代码让测试通过

总的来看,TDD总是处于一个循环中:

  1. 编写用例
  2. 测试失败
  3. 编写代码
  4. 测试成功
  5. 提交代码
  6. 重复以上

通过这样,功能的实现每次都是最小成本的,功能也是有步骤地、通过迭代完成的,而不是一步登天。

更关键的是,完善的测试用例,是开发者的“守护天使”,有了它们,以后在添加新功能时,修改/重构代码都有了可靠的保障,让开发者可以充满信心,code with confidence !

扩展

使用babel

要想使用import/export语法,需要安装babel相关依赖

  • 安装依赖
yarn add --dev babel-jest @babel/core @babel/preset-env

  • 在项目根路径新增配置文件 babel.config.js
module.exports = {presets: [['@babel/preset-env',{targets: {node: 'current',},},],],
};

  • 重新启动测试
yarn test --watch

为什么使用jest

因为这是vue官方工具链的一部分, 同时也可以为后续的组件测试作准备。

更多请点击查看2019-06-04尤雨溪的vue技术分享

测试驱动开发_?使用jest进行测试驱动开发相关推荐

  1. 测试驱动开发_DevOps之浅谈测试驱动开发

    "测试驱动开发(Test-Driven Development, TDD),以测试作为开发过程的中心,它要求在编写任何产品代码之前,先编写用于定义产品代码行为的测试,而编写的产品代码又要以使 ...

  2. jest java_✅使用jest进行测试驱动开发

    前言 本文将使用jest进行测试驱动开发的示例,源码在github.重点说明在开发中引入单元测试后开发过程,以及测试先行的开发思路. 本文的重点是过程以及思维方法,框架以及用法不是重点. 本文使用的编 ...

  3. 测试驱动开发 测试前移_测试驱动开发:它是什么,什么不是。

    测试驱动开发 测试前移 by Andrea Koutifaris 由Andrea Koutifaris Test driven development has become popular over ...

  4. 如何使用 Django 进行测试驱动开发,我来告诉你

    所谓测试驱动开发(TDD),就是先编写测试用例,然后编写代码来满足测试用例,具体包含以下步骤: 编写测试用例. 编写代码满足测试用例中的需求. 运行测试用例. 如果通过,说明代码满足了测试用例所定义的 ...

  5. 在敏捷中应用测试驱动开发

    在敏捷和DevOps领域,企业越来越关注持续集成和持续部署问题.他们更频繁地更新软件,给软件测试造成额外的时间压力.而测试驱动开发可以成为解决这个问题的一剂良方. \\ 什么是测试驱动开发? \\ 测 ...

  6. 简单的11步在Laravel中实现测试驱动开发

    测试驱动开发(英语:Test-driven development,缩写为TDD)是一种软件开发过程中的应用方法,由极限编程中倡导,以其倡导先写测试程序,然后编码实现其功能得名. 下文是我在Mediu ...

  7. 被高估了的测试驱动开发?

    测试驱动开发(TDD)始于上世纪 90 年代,时至今时今日,依然只有少数的开发者在践行着.本文作者从软件开发者的角度,又一次帮助我们定义了测试驱动开发,解答了众多开发着对 TDD 常见的谬误. 作者 ...

  8. C++ 测试驱动开发

    看到一篇非常好的C++测试驱动开发文章,这里转载下. 测试驱动开发(TDD)背景及综述 测试驱动开发是 Kent 提出的一种新的软件开发流程,现在已广为人知,这种开发方法依赖于极短重复的开发周期,面对 ...

  9. 面向 C++ 的测试驱动开发

    测试驱动开发(TDD)背景及综述 测试驱动开发是 Kent 提出的一种新的软件开发流程,现在已广为人知,这种开发方法依赖于极短重复的开发周期,面对开发需求,开发人员要先开发代码测试用例,这些代码实现的 ...

最新文章

  1. Linux 下Shell脚本删除过期文件
  2. findcontours函数_opencv轮廓findContoursamp;drawContours
  3. protobuf简单序列化反序列化示例
  4. 登录多实例MySQL失败,修改密码临时解决,原因不明
  5. ES6——举个例子理解Promise的原理和使用
  6. 用new关键字对一个String 变量赋值和用literal值直接赋值有什么不同(转)
  7. 《我和他的结婚录像和相册集》的快速传播
  8. 计算机注册表管理,如何打开计算机注册表编辑器
  9. 移动端测试必须具备的技能
  10. String.GetEnumerator 方法的C#例子
  11. perl 教程网站 记录
  12. php imagick 取得psd缩略图,PHP中使用Imagick操作PSD文件实例
  13. 编辑手机pad端调试方法
  14. yoga book android rom,【发帖王】YOGA BOOK Android版玩机技巧
  15. 微信公众号的自定义菜单的创建
  16. 前端实现GIF图片压缩上传
  17. 绘制线性回归和多元线性回归
  18. loadrunner使用sitescope监测监控mysql数据库
  19. 使用ADB工具卸载/停用Android系统应用(无需Root)
  20. 大数据24小时:链家研究院发布地产大数据产品Real Data,上海交大与依图共建AI联合实验室

热门文章

  1. java stringjoiner_java-为什么我们已经拥有StringBuilder时使用StringJoiner?
  2. mysql 5.7.18源码包下载_MYSQL数据库CentOS6.9+Mysql5.7.18源码安装详细教程
  3. cron表达式在线测试
  4. matlab第六章课后答案,matlab作业第6章
  5. linux ora 27125,ORA-27125 unable to create shared memory segment | 信春哥,系统稳,闭眼上线不回滚!...
  6. mui-scroll-wrapper mui-scroll 内容增多不出滚动条
  7. 六:SpringCloud-Config
  8. video标签 api
  9. 『设计模式』之小试牛刀
  10. 只有IE能上网,其他浏览器均不可以!