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)先列好测试的具体项并且每个测试要有明确的存在目的。测试项的内容最好从简单到复杂。在测试的时候,想到有遗漏的测试,可以先记录下来,别急着解决。在解决当前测试后,再进行调整。

  • GiTHub:https://github.com/NiLingWu/FrequencyNumber

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 104,050评论 12赞 125
  • 1.在C/C++中实现本地方法 生成C/C++头文件之后,你就需要写头文件对应的本地方法。注意:所有的本地方法的第...
    JayQiu阅读 1,328评论 0赞 3
  • 百战程序员_ Java1573题 QQ群:561832648489034603 掌握80%年薪20万掌握50%年薪...
    Albert陈凯阅读 13,107评论 3赞 33
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    张土汪阅读 10,355评论 0赞 31
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 26,035评论 17赞 394