热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过JavaScript中基于属性的TDD的钻石方块

在上一篇文章中,我介绍了基于属性的测试背后的基本思想。在这里,我将使用该技术来TDD钻石kata。这篇文章受到很大的启发(即公然复制&#x

在上一篇文章中,我介绍了基于属性的测试背后的基本思想。 在这里,我将使用该技术来TDD钻石kata。

这篇文章受到很大的启发(即公然复制)。 因此,请务必对Nat Pryce和Mark Seemann做同样的练习[1] [2](参考文献底部的链接)打个招呼。 幸运的是,我将使用Javascript和JSVerify 。 这样,我可以将自己隐藏在“但我使用其他堆栈”的背后。

另外,我将代码片段降至最低。 如果您有兴趣了解更多详细信息,请随时检查回购 。

钻石方

正如Seb Rose所描述的那样 ,问题陈述如下:

给定字母,打印以'A'开头的菱形,并在最宽处打印所提供的字母。

一些例子是

Input: A
Output: A

Input: B
Output: A
BB
A

Input: C
Output: A
BB
CC
BB
A

准备,设定,摇滚

init提交中,我要检查接线。 这就是为什么我使用始终返回5的生成器来检查isFive属性的原因。

// index.test.js

const jsc = require('jsverify')
const mocha = require('mocha')
const isFive = require('./index')

describe('TODO', () => {
jsc.property('TODO', jsc.constant(5), isFive)
})

// index.js

const isFive = number => number === 5
module.exports = isFive

当然是绿色的

$ mocha index.test.js

TODO
✓ TODO

1 passing (12ms)

✨ Done in 0.52s.

发电机

一切正常,因此我可以为菱形kata创建发生器。 特别是,我需要生成A..Z范围内的字符。

由于我不确定使用什么,因此决定检查jsc.asciichar生成器返回的内容

const debug = x => {
console.log(x)
return true
}

describe('diamond', () => {
jsc.property('TODO', jsc.asciichar, debug)
})

注意return true 。 这样,“属性” debug永远不会失败,我可以检查所有生成的asciichar。 由于默认情况下JSVerify通过从生成器中生成100个输入来检查属性100次,因此我看到

$ mocha index.test.js

diamond
T
K
.
E

B
<
// ... up to 100 asciichars

✓ TODO

1 passing (16ms)

✨ Done in 0.52s.

不太正确&#xff0c;实际上&#xff0c;我只需要生成A..Z范围内的字符。 不幸的是&#xff0c;JSVerify并没有提供任何符合该约束的生成器。 因此&#xff0c;我创建了一个自定义的

const alphabet &#61; &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZ&#39;.split(&#39;&#39;)
const char &#61; jsc.suchthat(jsc.asciichar, c &#61;> alphabet.includes(c))

describe(&#39;diamond&#39;, () &#61;> {
jsc.property(&#39;TODO&#39;, char, debug)
})

这次我们得到适当的值

$ mocha index.test.js

diamond
B
L
X
B
Q
V
X
B
J
S
C
P
I
// ... up to 100 chars in A..Z

✓ TODO

1 passing (19ms)

✨ Done in 0.52s.

注意&#xff0c;我可以将支票移到属性内

const property &#61; c &#61;> {
if (!alphabet.includes(c)) return true
// ... test the property
}

describe(&#39;diamond&#39;, () &#61;> {
jsc.property(&#39;TODO&#39;, jsc.asciichar, property)
})

但我会犯一个错误。 实际上&#xff0c;在这种情况下&#xff0c;JSVerify将使用随机的jsc.asciichar调用property 100次。 因此&#xff0c;仅生成的输入的一部分会超过if 。 换句话说&#xff0c;我会失去测试范围。

属性&#xff1a;钻石不为空

开始练习的属性只是检查钻石的长度是否大于0&#xff08;对于任何字符&#xff09;。

jsc.property(&#39;is not empty&#39;, char, c &#61;> make(c).length !&#61;&#61; 0)

我用它使绿色

const make &#61; char &#61;> &#39;whatever&#39;

来自REPL

make(c) // for any c
// &#61;> &#39;whatever&#39;

属性&#xff1a;第一行包含A

jsc.property(
&#39;first row contains A&#39;,
char,
c &#61;> firstRow(make(c)).trim() &#61;&#61;&#61; &#39;A&#39;
)

我用它使绿色

const make &#61; char &#61;> &#39; A &#39; // padding is asymmetric

来自REPL

make(c) // for any c
// &#61;> &#39; A &#39;

属性&#xff1a;最后一行包含A

jsc.property(
&#39;last row contains A&#39;,
char,
c &#61;> lastRow(make(c)).trim() &#61;&#61;&#61; &#39;A&#39;
)

这已经是绿色的。

属性&#xff1a;第一排具有对称轮廓

const firstRowHasSymmetricalContour &#61; diamond &#61;> {
const leadingElements &#61; leading(&#39;A&#39;, firstRow(diamond)).length
const trailingElements &#61; trailing(&#39;A&#39;, firstRow(diamond)).length
return leadingElements &#61;&#61;&#61; trailingElements
}

jsc.property(
‘first row has symmetrical contour’,
char,
c &#61;> firstRowHasSymmetricalContour(make(c))
)

我用它使绿色

const make &#61; char &#61;> &#39; A &#39; // padding is symmetric

来自REPL

make(c) // for any c
// &#61;> &#39; A &#39;

属性&#xff1a;行具有对称轮廓

好吧&#xff0c;不仅第一行具有对称轮廓。 让我们修改属性&#xff0c;以便检查所有行

const rowsHaveSymmetricalContour &#61; diamond &#61;>
diamond
.split(&#39;\n&#39;)
.map(rowHasSymmetricalContour)
.reduce((acc, x) &#61;> acc && x) // [].every would be better here

jsc.property(
&#39;rows have symmetrical contour&#39;,
char,
c &#61;> rowsHaveSymmetricalContour(make(c))
)

这已经是绿色的。

属性&#xff1a;行包含正确的字母

const rowsContainsCorrectLetters &#61; (char, diamond) &#61;> {
const pre &#61; alphabetUntilBefore(char)
const post &#61; pre.slice().reverse()
const expected &#61; pre.concat([char]).concat(post)
const actual &#61; diamond.split(&#39;\n&#39;).map(row &#61;> row.trim())
return expected.join() &#61;&#61;&#61; actual.join()
}

jsc.property(
‘rows contains the correct letters’,
char,
c &#61;> rowsContainsCorrectLetters(c, make(c))
)

我用它使绿色

const make &#61; char &#61;> {
const pre &#61; alphabetUntilBefore(char)
const post &#61; pre.slice().reverse()
const chars &#61; pre.concat([char]).concat(post)
return chars.join(&#39;\n&#39;)
}

测试代码和生产代码之间的重复是难闻的气味。 但是我决定把它留在那里。

来自REPL

make(&#39;C&#39;)
// &#61;> &#39;A\nB\nC\nB\nA&#39;

属性&#xff1a;行的宽度和高度一样高

const rowsAreAsWideAsHigh &#61; diamond &#61;> {
const height &#61; rows(diamond).length
return all(rows(diamond).map(hasLength(height)))
}

jsc.property(
&#39;rows are as wide as high&#39;,
char,
c &#61;> rowsAreAsWideAsHigh(make(c))
)

我用它使绿色

const makeRow &#61; width &#61;> char &#61;> {
if (char &#61;&#61;&#61; &#39;A&#39;) {
const padding &#61; &#39; &#39;.repeat(width / 2)
return &#96;${padding}A${padding}&#96;
} else {
return char.repeat(width)
}
}

const make &#61; char &#61;> {
const pre &#61; alphabetUntilBefore(char)
const post &#61; pre.slice().reverse()
const chars &#61; pre.concat([char]).concat(post)
return chars.map(makeRow(chars.length)).join(&#39;\n&#39;)
}

和REPL

make(&#39;C&#39;)
// &#61;> &#39; A \nBBBBB\nCCCCC\nBBBBB\n A &#39;

属性&#xff1a;除顶部和底部之外的行具有两个相同的字母

jsc.property(
&#39;rows except top and bottom have two identical letters&#39;,
char,
c &#61;> internalRowsHaveTwoIdenticalLetters(make(c))
)

我用它使绿色

const makeRow &#61; width &#61;> char &#61;> {
if (char &#61;&#61;&#61; &#39;A&#39;) {
const padding &#61; &#39; &#39;.repeat(width / 2)
return &#96;${padding}A${padding}&#96;
} else {
const padding &#61; &#39; &#39;.repeat(width - 2)
return &#96;${char}${padding}${char}&#96;
}
}

const make &#61; char &#61;> {
const pre &#61; alphabetUntilBefore(char)
const post &#61; pre.slice().reverse()
const chars &#61; pre.concat([char]).concat(post)
return chars.map(makeRow(chars.length)).join(&#39;\n&#39;)
}

和REPL

make(&#39;C&#39;)
// &#61;> &#39; A \nB B\nC C\nB B\n A &#39;

属性&#xff1a;行具有正确的内部空间量

jsc.property(
&#39;rows have the correct amount of internal spaces&#39;,
char,
c &#61;> rowsHaveCorrectAmountOfInternalSpaces(make(c))
)

我用它使绿色

const internalPaddingFor &#61; char &#61;> {
const index &#61; alphabet.indexOf(char)
return Math.max((index * 2) - 1, 0)
}

const makeRow &#61; width &#61;> char &#61;> {
if (char &#61;&#61;&#61; &#39;A&#39;) {
const padding &#61; &#39; &#39;.repeat(width / 2)
return &#96;${padding}A${padding}&#96;
} else {
const internalSpaces &#61; internalPaddingFor(char)
const internalPadding &#61; &#39; &#39;.repeat(internalSpaces)
const externalSpaces &#61; width - 2 - internalSpaces
const externalPadding &#61; &#39; &#39;.repeat(externalSpaces / 2)
return &#96;${externalPadding}${char}${internalPadding}${char}${externalPadding}&#96;
}
}

const make &#61; char &#61;> {
const pre &#61; alphabetUntilBefore(char)
const post &#61; pre.slice().reverse()
const chars &#61; pre.concat([char]).concat(post)
return chars.map(makeRow(chars.length)).join(&#39;\n&#39;)
}

和REPL

make(&#39;C&#39;)
&#39; A \n B B \nC C\n B B \n A &#39;

不幸的是&#xff0c; rowsHaveCorrectAmountOfInternalSpaces中的rowsHaveCorrectAmountOfInternalSpaces使用以下内容

const index &#61; alphabet.indexOf(char)
return Math.max((index * 2) - 1, 0)

我不喜欢这种重复。 因此&#xff0c;我决定测试外部空间&#xff08;而不是内部空间&#xff09;。

属性&#xff1a;行具有正确的外部空间量

jsc.property(
&#39;rows have the correct amount of external spaces&#39;,
char,
c &#61;> rowsHaveCorrectAmountOfExternalSpaces(make(c))
)

这次&#xff0c; rowsHaveCorrectAmountOfExternalSpaces内部使用了不同的计算方法&#xff1a;

const index &#61; alphabet.indexOf(char)
return ((width - 1) / 2 - index) * 2

这意味着我已经删除了重复项。 另外&#xff0c;由于内部空间的生产代码也照顾外部环境&#xff0c;因此测试已经是绿色的。

并且..我们完成了

如上所示&#xff0c;最后一次REPL测试给了我们

make(&#39;C&#39;)
// &#61;> &#39; A \n B B \nC C\n B B \n A &#39;

意思是

A
B B
C C
B B
A

这些是我发现的所有属性&#xff1a;

  • is not empty
  • first row contains A
  • last row contains A
  • rows have symmetrical contour
  • rows contain the correct letters
  • rows are as wide as high
  • rows except top and bottom have two identical letters
  • rows have the correct amount of external spaces

奥托罗

我注意到的第一件事是基于属性的TDD如何使您思考。 实际上&#xff0c;为这个kata举个例子真的很容易。 但是对于不变式不能说相同。

同时&#xff0c;了解您的问题空间的性质意味着对它有深入的了解。 对于基于属性的TDD&#xff0c;有必要在编写实际的生产代码之前发现它们。

不仅如此&#xff0c;我发现自己编写的属性与以前的属性冲突。 实际上&#xff0c;使它变成绿色的代码也使现有的一些变成红色。 菱形卡塔是一个简单的练习&#xff0c;但是在我们日常工作中经常会出现这种情况。

另外&#xff0c;我先从通用属性开始构建&#xff0c;然后再从专用属性构建&#xff08;即diamond is not empty &#xff0c;而rows have the correct amount of external spaces &#xff09;。 这与基于示例的TDD中发生的情况相反&#xff1a;从特定到泛型[3]。

不幸的是&#xff0c;因为我还没有尝试过这种方法&#xff0c;所以我无法与基于示例的TDD进行比较。 如果您对此感兴趣&#xff0c;请查阅参考资料。

参考文献

  1. 马克· 西曼 &#xff08;Mark Seemann&#xff09;的FsCheck钻石方盒
  2. 钻石卡塔&#xff08;Diamond Kata&#xff09;— TDD仅包含基于性能的测试 &#xff0c; 作者是Nat Pryce
  3. 钻石卡塔-纳特·普莱斯&#xff08;Nat Pryce&#xff09; 关于增量发展的思考

更多指针

  • Nat Pryce 在SPA 2013上基于属性的TDD

如果你喜欢的职位&#xff0c;并希望帮助流传着一句话&#xff0c;请考虑啁啾 &#xff0c;拍手或分享这些。 但前提是您真的很喜欢它。 否则&#xff0c;请随时发表评论或向我发送任何建议或反馈。

From: https://hackernoon.com/diamond-kata-via-property-based-tdd-in-Javascript-5fa99acd3e62



推荐阅读
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了最长上升子序列问题的一个变种解法,通过记录拐点的位置,将问题拆分为左右两个LIS问题。详细讲解了算法的实现过程,并给出了相应的代码。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 先看看ElementUI里关于el-table的template数据结构:<template><el-table:datatableData><e ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • Node.js学习笔记(一)package.json及cnpm
    本文介绍了Node.js中包的概念,以及如何使用包来统一管理具有相互依赖关系的模块。同时还介绍了NPM(Node Package Manager)的基本介绍和使用方法,以及如何通过NPM下载第三方模块。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
author-avatar
手机用户2502906317
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有