TDD(单元测试驱动开发)的Frequency Number练习

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

划分任务

Frequency Number.PNG
任务 输入 输出
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

jasmine.PNG

step3:根据jasmine.json里的要求,建一个测试JS文件(WFtestSpec)
json.PNG

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

jasmine.PNG

文件夹(WF)的变化:
jasmine2.PNG

  • 笔者推荐初始化jasmine(方法二),因为只需几句命令行即可将测试的框架搭建起来,不需自己新建文件夹、新建JS文件等复杂步骤。

初始化Git库

git初始化.PNG

在WebStorm打开项目

打开web.PNG

我们可见,利用初始化创建jasmine (方法二),已搭好5个测试,测试连接好两个JS文件。由于本题笔者只用到一个JS文件(如有需要也可自己多连接几个),所以笔者在测试文件中将Song.js注释掉。

每次任务的代码、测试情况

-** test1**
Player

 Player.main = function(words)
 {
        return '';
 }

PlayerSpec

    it ("should return ''when given ''", function(){
        expect ( Player.main('')).toEqual('');
    });

测试结果及提交:


test1.PNG
提交1.PNG
  • 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');
    });

测试结果及提交:


test2.PNG
提交2.PNG
  • 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;
}

测试结果及提交:


test3.PNG
提交3.PNG
  • 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是一个错误。)

测试结果及提交:


test4.PNG
提交4.PNG
  • 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:可选。规定排序顺序。必须是函数)

测试结果及提交:


test5.PNG
提交5.PNG
  • 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查找非空白字符

测试结果及提交:


test6.PNG
提交6.PNG
  • 最终代码
    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日志:

日志.PNG

总结

(1)每当写完一个测试之后,为了让测试通过,可以先用最简单的方法直接让测试通过,不必想太久。因为最简单的方法已经将每个测试的主要任务的框架已经构建出来了,然后一步步再建起来。
(2)先列好测试的具体项并且每个测试要有明确的存在目的。测试项的内容最好从简单到复杂。在测试的时候,想到有遗漏的测试,可以先记录下来,别急着解决。在解决当前测试后,再进行调整。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • 1.在C/C++中实现本地方法 生成C/C++头文件之后,你就需要写头文件对应的本地方法。注意:所有的本地方法的第...
    JayQiu阅读 2,352评论 0 3
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,743评论 0 33
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,608评论 18 399
  • 写给自己的十句话! 01失败不可怕,可怕的是从来没有努力过,还怡然自得地安慰自己,连一点点的懊悔都被麻木所掩盖下去...
    创一学习吧阅读 615评论 0 0