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

前言

本文将使用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文件。

先做好约定:shell文件应该以 .sh 结尾

shell文件不以 . 开头

函数为名 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', () => {

// 调用函数,期望它返回值为 true expect(isShellFile('bash.sh')).toBeTruthy()

})

运行 yarn test , 结果如下:

FAIL test/isShellFile.test.js

✕ isShellFile (2ms)

● isShellFile

TypeError: 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)

● isShellFile

expect(received).toBeTruthy()

^^^

Received: undefined

3 | 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)

● isShellFile

expect(received).toBeFalsy()

^^^

Received: true

4 |

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)

● isShellFile

expect(received).toBeFalsy()

^^^

Received: true

5 | 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)

● isShellFile

expect(received).toBeFalsy()

^^^

Received: true

6 | 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)

● isShellFile

expect(received).toBeFalsy()

^^^

Received: true

7 | 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: 优化逻辑'

注意,这个重构示例的重点是:先完成功能,再重构

重构必须要有测试用例,且确保重构后全部测试用例通过

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

结论

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

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

文中 文件初始化 及 第一个用例 的内容,尤其值得回味,它体现了两个思路:总是在有一个失败的单元测试后才开始编码

用必要的最小代码让测试通过

总的来看,TDD总是处于一个循环中:编写用例

测试失败

编写代码

测试成功

提交代码

重复以上

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

更关键的是,完善的测试用例,是开发者的“守护天使”,有了它们,以后在添加新功能时,修改/重构代码都有了可靠的保障,让开发者可以充满信心,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官方工具链的一部分, 同时也可以为后续的组件测试作准备。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/530309.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql sqlstate 08001_关于Toad连接DB2的sqlstate=08001错误

新装的centos6.3db29.7,数据库导入完了的之后用Toad连接访问之的时候出错了。DB2 Database Error: ERROR [08001] [IBM] SQL30081N A communication error has been detected. Communication protocol being used: "TCP/IP". Communication API being use…

mysql 设置主键命令_MySQL常用命令

1、修改MySQL密码方法一:use mysql;update user set passwordPASSWORD(“123456”) where user‘root’;flush privileges;忘记密码:sed -ri 3d skip-grant-tables /etc/my.cnfsystemctl restart mariadbuse mysql&…

python 整除的数组_计算和可被整除的所有子数组

在我学习面试的时候,我在GeeksForGeeks上找到了这个问题和解决方案,但不明白答案。在上面说的是Let there be a subarray (i, j) whose sum is divisible by ksum(i, j) sum(0, j) - sum(0, i-1)Sum for any subarray can be written as q*k rem where…

java ha_java – Haproxy Bad Gateway 502

所以我在Jetty servlet面前使用HAProxy.目前的目标只是在配置完所有内容后进行概念验证,加载和压力测试.但是我在配置haproxy时遇到问题.我知道这不是我的应用程序的问题,因为我有运行nginx(tengine),一切正常.所以它必须与haproxy配置或haproxy工作的方式不适合我的需要.所以我…

java ioutils_java – 无法解析符号’IOUtils’

我使用以下代码在我的Android应用程序中创建一个临时文件:public File streamToFile (InputStream in) throws IOException {File tempFile File.createTempFile("sample", ".tmp");tempFile.deleteOnExit();FileOutputStream out new FileOu…

java const关键字_const关键字:终于拥有真正的常量声明语句

你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 —— const。在说 const 关键字之前,大叔先和你唠唠大叔自己对 const 的感受 —— JavaScript 尼玛终于可以声明真正的常量啦!大叔为啥会发出这样滴感叹?实在是“天下苦秦久矣”呀~…

workerman高并发异步mysql_workerman怎么实现高并发

并发概念太模糊,这里以两种可以量化的指标并发连接数和并发请求数来说明。并发连接数是指服务器当前时刻一共维持了多少TCP连接,而这些连接上是否有数据通讯并不关注。 (推荐学习: workerman教程)例如一台消息推送服务器上可能维持了百万的设…

checkout 撤销修改_Git的4个阶段的撤销更改

虽然git诞生距今已有12年之久,网上各种关于git的介绍文章数不胜数,但是依然有很多人(包括我自己在内)对于它的功能不能完全掌握。以下的介绍只是基于我个人对于git的理解,并且可能生编硬造了一些不完全符合git说法的词语。目的只是为了让git通…

移除Java对象中的属性_在java对象中添加和删除属性

我怎样才能在 java中实现这一点.我有一个具有属性的对象.public class Object {private final Credentials Credentials;private final int PageSize;private final int PageStart;private final int DefaultFilterId;public Object(Credentials Credentials, int PageSize, in…

java软件开发ea介绍_开发说明 — Eacloud 1.0 documentation

PHP 代码示例( Linux 版)解压后,参考 phplinux/v3.4.0.1/文档/PHP版服务器端工具包(Linux版)软件使用手册.pdfDemo 运行1.安装对应版本的 PHP2.安装运行时环境(glibc 库等)3.修改 PHP 的配置文件 php.ini修改 php.ini,使 php 允许加载扩展,并…

java中operationBox_Java使用PDFBox开发包实现对PDF文档内容编辑与保存

pdfbox开发包下载地址:http://pdfbox.apache.org/程序实现了PDF文档的创建,读入,与修改PDF内容并保存。可能有个前提,PDF文档不是加密的,如果加密怎么办,我没研究过!源代码如下:pack…

java访问权限最高_java 访问权限

Java语言中的访问权限修饰符有4种,但是仅有3个关键字,因为不写访问权限,在Java中被称为默认权限,或同包权限,本文中以(default)代替。下面按照权限从小到大的顺序对4中访问权限分别介绍。class我个人,我有很…

java中 queryparam_java – 何时使用@QueryParam和@PathParam

我不是问这里已经问过的问题:What is the difference between PathParam and QueryParam这是一个“最佳实践”或常规问题。什么时候使用PathParam和QueryParam。我可以想到的是,决定可能使用两者来区分信息模式。让我在下面说明我的LTPO – 不完美的观察…

java中fork函数_java中的forkjoin框架的使用

fork join框架是java 7中引入框架,这个框架的引入主要是为了提升并行计算的能力。fork join主要有两个步骤,第一就是fork,将一个大任务分成很多个小任务,第二就是join,将第一个任务的结果join起来,生成最后…

Java h264起始码_h.264 – 使用H264视频的起始码

有两种H.264流格式,有时也称为>附件B(在原始H.264流中找到)> AVCC(在像MP4这样的容器中找到)H.264流由NAL(包装单位)组成(1)附件B:在每个NAL单元的字节[x00] [x00] [x00] [x01]之前有4字节的起始码.[start code]--[NAL]--[start code]--[NAL] etc(2)AVCC&…

java中已定义类型car_Java 8 习惯用语(8):Java 知道您的类型

Java™8是第一个支持类型推断的 Java 版本,而且它仅对 lambda 表达式支持此功能。在 lambda表达式中使用类型推断具有强大的作用,它将帮助您做好准备以应对未来的 Java版本,在今后的版本中还会将类型推断用于变量等更多可能。这里的诀窍在于恰…

ATM柜员机JAVA课程设计_ATM柜员机学年论文设计(Java课程设计)

内容简介:ATM柜员机学年论文设计(Java课程设计),共23页,4599字,附源程序。一. 程序介绍3二. 开发环境搭建31. MyEclipse 5.5.1 GA安装32. MyEclipse Designer 图形设计插件安装33. MySQL数据库安装4三&…

mysql 结果集什么意思_结果集中的mysql“和”逻辑

假设我有一个类似以下的数据集:table fooid | employeeType | employeeID-------------------------1 | Developer | 12 | Developer | 23 | Developer | 34 | Manager | 15 | Manager | 46 | Manager | 57 | CEO | 18 | CEO | 6我想运行一个查询,该查询将返回所有e…

opencv java 去干扰_java - OpenCV Java修补图像格式要求 - 堆栈内存溢出

一直试图让修复工作在Android上进行,int height (int) viewMat.size().height;int width (int) viewMat.size().width;Mat maskMat new Mat();maskMat.create(viewMat.size(), CvType.CV_8U);maskMat.setTo(bColor);Point r1 new Point(width/2-width/10, heigh…

java中 set集合_第8篇 Java中的集合(Set)

Java 集合的 Set 接口Set类型与List类型的区别Set: 无序、不可重复List: 有序、可重复1、HashSetHashSet的存储结构:HashMap特点:HashSet通过比较存放的哈希码(hashCode)来确定对象存放的位置当两个对象的哈希值相等时&#xff0c…