テストによる開発:スキルの向上

前の記事では、理論的な側面を検討しました。 練習を始めましょう。



画像



テストによる開発を使用して、JavaScriptで簡単なスタック実装を作成しましょう。



スタック-LIFOの原則に従って編成されたデータ構造:後入れ先出し。 スタックには3つの主要な操作があります。



プッシュ :アイテムを追加

pop :アイテムを削除します

ピーク :ヘッド要素を追加



クラスを作成し、Stackと呼びます。 タスクを複雑にするために、スタックの容量が固定されているとします。 スタックのプロパティと実装関数は次のとおりです。



items :スタック上のアイテム。 配列を使用してスタックを実装します。

容量 :スタック容量。

isEmpty() :スタックが空の場合はtrue、そうでない場合はfalseを返します。

isFull() :スタックが最大容量に達した場合、つまり別の要素を追加できない場合にtrueを返します。 それ以外の場合、falseを返します。

push(要素)要素を追加します。 スタックがいっぱいの場合、Fullを返します。

pop :アイテムを削除します。 スタックが空の場合、Emptyを返します。

peek() :head要素を追加します。



stack.jsstack.spec.jsの 2つのファイルを作成します。 慣れているので.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を使用しないことに注意してください。これらは同じ配列を参照しないためです。



糸テストstack.spec.jsを実行します。 他のテストが破損しないようにするため、特定のファイルでJestを実行します。 結果は次のとおりです。



画像



Stackはコンストラクタではありません 。 もちろん。 まだスタックを作成しておらず、コンストラクターも作成していません。



stack.jsで 、クラス、コンストラクターを作成し、クラスをエクスポートします。



 class Stack { constructor() { } } module.exports = Stack
      
      





テストを再度実行します。



画像



コンストラクターで要素を設定しなかったため、 Jestは配列内の要素が[]と等しいことを期待していましたが、定義されていませんでした。 次に、要素を初期化する必要があります。



 constructor() { this.items = [] }
      
      





テストを再度実行すると、 キャパシティについて同じエラーが発生するため、 キャパシティも設定する必要があります。



 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を使用してこの関数をテストします。 解決策は記事の最後にあります。



プッシュ



ここでは、 3つのことをテストする必要があります。





describe for pushを使用して別のブロックを作成します。 このブロックをメインブロック内に配置します。



 describe('Stack.push', () => { })
      
      





アイテムを追加



新しいスタックを作成し、要素を追加します。 アイテム配列の最後のアイテムは、追加したアイテムでなければなりません。



 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内では何も返されません! これを修正する必要があります。



 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およびpeekをテストおよび実装します。



ヒント:





自分でやろうとせずに、以下の解決策を見ないでください。 進歩する唯一の方法は、試して、実験して、練習することです。



解決策



さて、運動はどうですか? やりましたか? そうでない場合、落胆しないでください。 テストには時間と労力がかかります。



 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') }) }) })
      
      





ファイルを見ると、ポップとピークでトリプル条件を使用していることがわかります。 これは私がリファクタリングしたものです。 古い実装は次のようになりました。



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





テストによる開発により、テストの作成後にコードをリファクタリングできます。テストの動作を心配することなく、より短い実装を見つけました。



テストを再度実行します。



画像



テストによりコードの品質が向上します。 テストのすべての利点を理解していただき、TDDをより頻繁に使用していただければ幸いです。



テストは重要であり、コードの品質を向上させると確信しましたか? むしろ、テストによる開発に関する記事のパート2を読んでください。 そして、最初の記事を読んでいない人は、リンクをたどってください



All Articles