What is TDD?
TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。
利用TDD的完成Frequency Number练习
问题描述
它可以帮我处理一段字符串信息,这段字符串信息是由英文单词组成,每两个单词之间有空格,处理结果也为一段字符串,这个字符串应该每行只显示一个单词和它的数量,并且按出现频率倒序排列。
example:
input:
“it was the age of wisdom it was the age of foolishness it is”
output:
it 3
was 2
the 2
age 2
of 2
wisdom 1
foolishness 1
is 1
划分任务
任务 | 输入 | 输出 |
---|---|---|
test1 | "" | "" |
test2 | "he" | "he 1" |
test3 | "he is" | "he 1\nis 1" |
test4 | "he he is" | "he 2\nis 1" |
test5 | "he is is" | "is 2\nhe 1" |
test6 | "he is" | "he 1\nis 1" |
初始工作
初始化jasmine(方法一)
step1:先建好文件夹(WF),并在文件夹内新建一个JS文件(WordFrequency)
step2:在Git Bash里初始化jasmine
step3:根据jasmine.json里的要求,建一个测试JS文件(WFtestSpec)
spec_dir:指定扫描测试文件的根目录
spec_files:匹配测试文件的表达式
helpers:Helper 文件会在所有的 spec 之前预先执行
stopSpecOnExpectationFailure:当有错误出现时是否终止所有测试
random:是否打乱测试顺序
"**/*[sS]pec.js"
:写测试的JS的所在位置及命名格式。在测试文件的根目录里新建一个文件夹(名字随意),在新建的文件夹里建以spec或Spec为结尾命名的JS文件。
初始化jasmine(方法二)
step1:先建好文件夹(WF),右键-》Git Bash Here
step2:在Git Bash里搭建jasmine examples ,并初始化jasmine
文件夹(WF)的变化:
- 笔者推荐初始化jasmine(方法二),因为只需几句命令行即可将测试的框架搭建起来,不需自己新建文件夹、新建JS文件等复杂步骤。
初始化Git库
在WebStorm打开项目
我们可见,利用初始化创建jasmine (方法二),已搭好5个测试,测试连接好两个JS文件。由于本题笔者只用到一个JS文件(如有需要也可自己多连接几个),所以笔者在测试文件中将Song.js注释掉。
每次任务的代码、测试情况
-** test1**
Player
Player.main = function(words)
{
return '';
}
PlayerSpec
it ("should return ''when given ''", function(){
expect ( Player.main('')).toEqual('');
});
测试结果及提交:
-
test2
Player
var format = function(words ,count)
{
return words + ' '+ count;
}
Player.prototype.main = function(words)
{
if(words !== '')
{
return format(words,1);
}
return '';
}
PlayerSpec
it ("should return 'is 1'when given 'is'", function(){
expect ( Player.main('is')).toEqual('is 1');
});
测试结果及提交:
-
test3
Player
Player.prototype.main = function(words)
{
if(words !== '')
{
var wordArray = words.split(' ');
return wordArray.map((w) => format(w,1)).join( '\n');
}
return '';
}
PlayerSpec
it ("should return 'he 1 \n is 1'when given 'he is'",function(){
expect (Player.main('he is')).toEqual('he 1\nis 1');
});
解释
split() 方法用于把一个字符串分割成字符串数组。(返回值为
一个字符串数组)
语法:stringObject.split(separator,howmany)
(separator:必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。
howmany :可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。)
join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。(返回值为返回一个字符串)
语法:arrayObject.join(separator)
(separator:可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。)
map()方法用于创建一个新的数组,此数组中的每个元素是使用调用上所提供的函数的结果。返回值为创建新的数组。
语法:array.map(callback[, thisArg ]);
(callback : 产生新数组元素的函数。
thisArg:可选的。在执行回调时使用的值。)
JS中的=>符号:是ECMAScript 6语法中的arrow function。
举例:(x) => x + 6
相当于
function(x){
return x + 6;
}
测试结果及提交:
-
test4
Player
var group = function(wordArray)
{
return wordArray.reduce((array ,word) =>{
var entry = array.find((e) => e.word ===word);
if(entry) {
entry.count++;
}
else {
array.push({word :word , count :1})
}
return array;
},[]);
}
Player.prototype.main = function(words)
{
if(words !== '')
{
var wordArray = words.split(' ');
var groupedWords = group(wordArray);
return groupedWords.map((e) => format(e.word,e.count)).join('\n');
}
return '';
}
PlayerSpec
it("should return 'he 2 \n is 1'when given 'he hs is'",function(){
expect (Player.main('he he is')).toEqual('he 2\nis 1');
});
解释
push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
语法:rrayObject.push(newelement1,newelement2,....,newelementX)
(newelement1:必需。要添加到数组的第一个元素。
newelement2:可选。要添加到数组的第二个元素。
newelementX:可选。可添加多个元素。)
提示:
(1)要想数组的开头添加一个或多个元素,请使用 unshift() 方法。
(2)想要删除数组的最后一个元素并返回数组的最后一个元素,请使用pop() 方法
find()方法返回数组中满足提供的测试功能的第一个元素的值。
语法:arr .find(callback [,thisArg ])
(callback:在数组中的每个值上执行的函数
thisArg:对象在执行回调时使用)
reduce()方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。
语法:arr .reduce(callback,[ initialValue])
(callback:函数在数组中的每个元素上执行
initialValue:可选。值用作回调第一个调用的第一个参数。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce是一个错误。)
测试结果及提交:
-
test5
Player
Player.prototype.main = function(words)
{
if(words !== '')
{
var wordArray = words.split(' ');
var groupedWords = group(wordArray);
groupedWords.sort((x,y) => y.count - x.count);
return groupedWords.map((e) => format(e.word, e.count)).join('\n');
}
return '';
}
PlayerSpec
it("should return 'is 2 \n he 1'when given 'he is is'",function(){
expect (Player.main('he is is')).toEqual('is 2\nhe 1');
});
解释
sort() 方法用于对数组的元素进行排序。数组在原数组上进行排序,不生成副本
语法:arrayObject.sort(sortby)
(sortby:可选。规定排序顺序。必须是函数)
测试结果及提交:
-
test6
Player
将
var wordArray = words.split(' ');
改为
var wordArray = words.split(/\s+/);
PlayerSpec
it ("should return 'he 1 \n is 1'when given 'he is'",function(){
expect (Player.main('he is')).toEqual('he 1\nis 1');
});
解释
\s+查找包含至少一个空白字符
\s查找空白字符
\S查找非空白字符
测试结果及提交:
-
最终代码
Player
function Player() {
}
Player.prototype.main = function(words) {
if (words !== '')
{
var wordArray = words.split(/\s+/);
var groupedWords = group(wordArray);
groupedWords.sort((x,y) => y.count - x.count);
return groupedWords.map((e) => format(e.word, e.count)).join( '\n');
}
return '';
};
var format = function(words,count)
{
return words + ' ' + count;
}
var group = function(wordArray)
{
return wordArray.reduce((array ,word) =>{
var entry = array.find((e) => e.word ===word);
if(entry)
{
entry.count++;
}
else
{
array.push({word :word ,count :1})
}
return array;
},[]);
}
module.exports = Player;
PlayerSpec
describe("Player", function() {
var Player = require('../../lib/jasmine_examples/Player');
//var Song = require('../../lib/jasmine_examples/Song');
var player;
// var song;
beforeEach(function() {
player = new Player();
// song = new Song();
});
it("should return ''when given ''", function() {
expect(player.main('')).toEqual('');
//demonstrates use of custom matcher
// expect(player).toBePlaying(song);
});
it("should return 'is 1'when given 'is" , function() {
expect(player.main('is')).toEqual('is 1');
//demonstrates use of custom matcher
// expect(player).toBePlaying(song);
});
it("should return 'he 1\n is 1'when given 'he is" , function() {
expect(player.main('he is')).toEqual('he 1\nis 1');
//demonstrates use of custom matcher
// expect(player).toBePlaying(song);
})
it("should return 'he 2\n is 1'when given 'he he is" , function() {
expect(player.main('he he is')).toEqual('he 2\nis 1');
//demonstrates use of custom matcher
// expect(player).toBePlaying(song);
});
it("should return 'is 2\n he 1'when given 'he is is" , function() {
expect(player.main('he is is')).toEqual('is 2\nhe 1');
//demonstrates use of custom matcher
// expect(player).toBePlaying(song);
});
it("should return 'he 1 \n is 1'when given 'he is'" , function() {
expect(player.main('he is')).toEqual('he 1\nis 1');
//demonstrates use of custom matcher
// expect(player).toBePlaying(song);
});
});
Git日志:
总结
(1)每当写完一个测试之后,为了让测试通过,可以先用最简单的方法直接让测试通过,不必想太久。因为最简单的方法已经将每个测试的主要任务的框架已经构建出来了,然后一步步再建起来。
(2)先列好测试的具体项并且每个测试要有明确的存在目的。测试项的内容最好从简单到复杂。在测试的时候,想到有遗漏的测试,可以先记录下来,别急着解决。在解决当前测试后,再进行调整。