在上一篇文章中,我们研究了理论方面。 现在该开始练习了。

通过测试开发,让我们在JavaScript中实现堆栈的简单实现。
堆栈-根据LIFO原则组织的数据结构:后进先出。 堆栈上有三个主要操作:
推送 :添加项目
pop :删除项目
窥视 :添加头元素
创建一个类,并将其称为Stack。 为了使任务复杂化,假设堆栈具有固定的容量。 以下是我们堆栈的属性和实现功能:
items :堆栈中的项目。 我们将使用数组来实现堆栈。
容量 :堆栈容量。
isEmpty() :如果堆栈为空,则返回true,否则返回false。
isFull() :如果堆栈达到最大容量,即无法添加其他元素,则返回true。 否则,返回false。
push(元素) :添加一个元素。 如果堆栈已满,则返回Full。
pop :删除项目。 如果堆栈为空,则返回Empty。
peek() :添加一个head元素。
我们将创建两个文件
stack.js和
stack.spec.js 。 我使用
.spec.js扩展名是因为我已经习惯了它,但是您可以使用.test.js或给它一个不同的名称并将其移至
__tests__ 。
由于我们通过测试进行开发实践,因此我们将编写失败的测试。
首先,检查构造函数。 要测试文件,您需要导入堆栈文件:
const Stack = require('./stack')
对于那些对我为什么不在此处使用
导入感兴趣的人,Node.js的最新稳定版本今天不支持此功能。 我可以添加Babel,但我不想让您超负荷。
测试类或函数时,请运行测试并描述要测试的文件或类。 这是关于堆栈的:
describe('Stack', () => { })
然后,我们需要检查初始化堆栈时是否创建了一个空数组,并设置了正确的容量。 因此,我们编写以下测试:
it('Should constructs the stack with a given capacity', () => { let stack = new Stack(3) expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) })
请注意,对于
stack.items ,我们使用
toEqual而不使用
toBe ,因为它们没有引用相同的数组。
现在运行
yarn test stack.spec.js 。 我们在特定文件中运行
Jest ,因为我们不想破坏其他测试。 结果如下:
堆栈不是构造函数 。 当然可以 我们仍未创建堆栈,也未创建构造函数。
在
stack.js中,创建您的类,构造函数并导出该类:
class Stack { constructor() { } } module.exports = Stack
再次运行测试:

由于我们没有在构造函数中设置元素,因此
Jest期望数组中的元素等于[],但未定义它们。 然后,您应该初始化元素:
constructor() { this.items = [] }
如果再次运行测试,那么您将获得与Capacity相同的错误,因此您还需要设置Capacity:
constructor(capacity) { this.items = [] this.capacity = capacity }
运行测试:

是的
测试通过 。 这是TDD。 我希望现在进行测试对您来说更有意义! 继续吗
isEmpty
为了测试
isEmpty ,我们将初始化一个空栈,测试isEmpty是否返回true,添加一个元素并再次检查它。
it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { let stack = new Stack(3) expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) })
运行测试时,您将得到以下错误:
TypeError: stack.isEmpty is not a function
为了解决这个问题,我们需要在
Stack类内部创建
isEmpty :
isEmpty () { }
如果运行测试,应该会收到另一个错误:
Expected: true Received: undefined
没有向
isEmpty添加任何内容。 如果
堆栈中没有元素,则该
堆栈为空:
isEmpty () { return this.items.length === 0 }
isFull
这与
isEmpty相同,因为本练习使用TDD测试此功能。 您将在本文的最后找到解决方案。
推入
这里我们需要测试
三件事:
- 必须将新项目添加到堆栈的顶部。
- 如果堆栈已满,则push返回“ Full”;
- 必须返回最近添加的项目。
使用
describe for
push创建另一个块。 我们将此块放在主块中。
describe('Stack.push', () => { })
新增项目
创建一个新的堆栈并添加一个元素。
items数组中的最后一个项目应该是您刚刚添加的项目。
describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { let stack = new Stack(3) stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) })
如果运行测试,您将看到未定义
推送 ,这是正常的。
push将需要一个参数来添加一些东西到堆栈中:
push (element) { this.items.push(element) }
测试再次通过。 你有注意到吗? 我们保留此行的副本:
let stack = new Stack(3)
这很烦人。 幸运的是,我们有一个
beforeEach方法,使您可以在每次测试运行之前进行一些自定义。 为什么不利用这个?
let stack beforeEach(() => { stack = new Stack(3) })
重要说明:
必须在
beforeEach之前声明堆栈。 实际上,如果您在
beforeEach方法中定义了堆栈变量,则不会在所有测试中都定义堆栈变量,因为它不在正确的区域中。
比重要更重要:我们还必须创建一个
afterEach方法。 现在,堆栈实例将用于所有测试。 这可能会造成一些困难。 在
beforeEach之后
立即添加此方法:
afterEach(() => { stack.items = [] })
现在,您可以在所有测试中删除堆栈初始化。 这是完整的测试文件:
const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should constructs the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) })
回报值测试
有一个测试:
it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) })
运行测试时,您将获得:
Expected: 2 Received: undefined
推内什么也没有返回! 我们必须解决此问题:
push (element) { this.items.push(element) return element }
如果堆栈已满,则返回已满
在此测试中,我们首先需要填充堆栈,添加一个元素,确保没有多余的东西被添加到堆栈中,并且返回值是
Full 。
it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') })
运行测试时,您将看到此错误:
Expected: 3 Received: 4
这样就添加了项目。 这就是我们想要的。 首先,您需要在添加内容之前检查堆栈是否已满:
push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element }
测试通过。练习:流行和偷看该练习了。 测试并实现
pop and
peek 。
温馨提示:
- 流行与推非常相似
- 偷看也像推
- 到目前为止,我们尚未重构代码,因为这不是必需的。 在编写测试并通过测试之后,这些函数中可能会有一种重构代码的方法。 不用担心,更改代码需要为此进行测试。
请勿自行尝试查看以下解决方案。 进步的唯一途径是尝试,试验和练习。
解决方案
好吧,锻炼身体如何? 你做了吗 如果没有,不要气disc。 测试需要时间和精力。
class Stack { constructor (capacity) { this.items = [] this.capacity = capacity } isEmpty () { return this.items.length === 0 } isFull () { return this.items.length === this.capacity } push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } pop () { return this.isEmpty() ? 'Empty' : this.items.pop() } peek () { return this.isEmpty() ? 'Empty' : this.items[this.items.length - 1] } } module.exports = Stack const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should construct the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) it('Should have an isFull function that returns true if the stack is full and false otherwise', () => { expect(stack.isFull()).toBe(false) stack.items = [4, 5, 6] expect(stack.isFull()).toBe(true) }) describe('Push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) }) describe('Pop', () => { it('Should removes the last element at the top of a stack', () => { stack.items = [1, 2, 3] stack.pop() expect(stack.items).toEqual([1, 2]) }) it('Should returns the element that have been just removed', () => { stack.items = [1, 2, 3] let element = stack.pop() expect(element).toBe(3) }) it('Should return Empty if one tries to pop an empty stack', () => {
如果查看这些文件,您会发现我在pop和peek中使用了三重条件。 这就是我重构的东西。 旧的实现如下所示:
if (this.isEmpty()) { return 'Empty' } return this.items.pop()
通过测试进行开发使我们能够在编写测试后重构代码,我发现了一个较短的实现,而不必担心测试的行为。
再次运行测试:

测试可以提高代码质量。 希望您现在了解测试的全部好处,并会更频繁地使用TDD。
我们是否使您相信测试至关重要并且可以提高代码质量? 而是,阅读有关通过测试进行开发的文章的第2部分。 如果谁没有读第一本书,请
点击链接 。