通过测试进行开发:提高技能

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

图片

通过测试开发,让我们在JavaScript中实现堆栈的简单实现。

堆栈-根据LIFO原则组织的数据结构:后进先出。 堆栈上有三个主要操作:

推送 :添加项目
pop :删除项目
窥视 :添加头元素

创建一个类,并将其称为Stack。 为了使任务复杂化,假设堆栈具有固定的容量。 以下是我们堆栈的属性和实现功能:

items :堆栈中的项目。 我们将使用数组来实现堆栈。
容量 :堆栈容量。
isEmpty() :如果堆栈为空,则返回true,否则返回false。
isFull() :如果堆栈达到最大容量,即无法添加其他元素,则返回true。 否则,返回false。
push(元素) :添加一个元素。 如果堆栈已满,则返回Full。
pop :删除项目。 如果堆栈为空,则返回Empty。
peek() :添加一个head元素。

我们将创建两个文件stack.jsstack.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', () => { // By default, the stack is empty expect(stack.pop()).toBe('Empty') }) }) describe('Peek', () => { it('Should returns the element at the top of the stack', () => { stack.items = [1, 2, 3] let element = stack.peek() expect(element).toBe(3) }) it('Should return Empty if one tries to peek an empty stack', () => { // By default, the stack is empty expect(stack.peek()).toBe('Empty') }) }) }) 

如果查看这些文件,您会发现我在pop和peek中使用了三重条件。 这就是我重构的东西。 旧的实现如下所示:

 if (this.isEmpty()) { return 'Empty' } return this.items.pop() 

通过测试进行开发使我们能够在编写测试后重构代码,我发现了一个较短的实现,而不必担心测试的行为。

再次运行测试:


图片

测试可以提高代码质量。 希望您现在了解测试的全部好处,并会更频繁地使用TDD。

我们是否使您相信测试至关重要并且可以提高代码质量? 而是,阅读有关通过测试进行开发的文章的第2部分。 如果谁没有读第一本书,请点击链接

Source: https://habr.com/ru/post/zh-CN430876/


All Articles