编写的好玩hangman轻松入门TDD

TDD是敏捷开发极限编程的很有意思的一种技术,因为不符合开发常规思维而有意思。究竟要不要测试驱动开发,能否驱动,在怎样的范围内可采用,是否切实可行?其实这个技术的确已经有许多先驱者使用并且已经品尝到了好处。关于TDD的好处,我放在另一篇来介绍。

其实许多技术都是被人为的神化,接近它就发现并没有那么可怕。这次让我们玩起来,边玩边学TDD。学习之前,先要会玩游戏,玩玩看,你能过几关。

可以先Play Hangman点这里玩游戏​,hangman就是上吊的人。这是一个古老的简单的猜单词的游戏。​

具体内容,可以点这里看百度的解释。​

(这里是以JUnit和Java为框架来学习)​



​任务:我们要用TDD的方法来实现一个小游戏的核心代码。并不关注界面的实现。



TDD(测试驱动开发)是基于黑盒的讨论,甚至可以思考为是一个基于需求的测试,这就使得这种测试技术带有了无穷的魅力!下面让我们一起来揭开它神秘的面纱。

先来看看需求吧,游戏界面一共有两个,一个是主界面,一个是solution界面。solution用来显示游戏结果。主界面是核心。

主界面里用红框已经标示出这段程序的重要核心部分。现在来整理需求:
New:启动游戏。启动游戏完成Tries初始化为12,Length初始化为单词长度,Used初始化为“AEIOU”
Play:玩游戏,其实就是玩着尝试字母,会有两种情况:
1)输入正确
used增加一个字母;单词上增加相应的字母。
2)输入错误
used中没有的,就used增加该字母;Tries减1;
Result:
Game Win, 猜的字和原单词相同
Game Over, Tries<0

TDD的过程:写个会出错的测试(红色)->编写代码(绿色)-->代码重构(蓝色)简洁代码。循环的过程。




接下来,我们针对需求从测试代码开始。

让我们来感受,从测试入手,从需求入手。

我们先来完成第一个需求:

启动游戏

启动游戏的需求是:Tries初始化为12,Length初始化为单词长度,Used初始化为“AEIOU”

先完成测试代码:

我们假定第一个试猜的是 "HELLO"

public class startGame {

@Test

public void testLength() {

HangMan hangMan=new HangMan("HELLO");

assertEquals(5,hangMan.length());

}

@Test

public void testTries() {

HangMan hangMan=new HangMan("HELLO");

assertEquals(12,hangMan.tries());

}

@Test

public void testUsed() {

HangMan hangMan=new HangMan("HELLO");

assertEquals("AEIOU",hangMan.used());

}

}

写的过程,你可能就开始抱怨,hangMan根本没有嘛。没关系,先写,然后让IDE自己来查错,生成,少了我们写的步骤,那些方法也让它自动生成!

跑测试!全部红色!OK,这正是我们需要的,先要编写找到缺陷的测试!很简单!

TDD,要满足小步快跑,从简单入手,每次只完成一个任务。

第二步就是编码!

所以在HangMan这个类中,你只需写:

public int length() {

return 5;

}

public int tries() {

return 12;

}

public String used() {

return “AEIOU”;

}

然后跑测试!全部绿色!快速让测试通过!哇塞,你会惊呼这个什么意思?是编程吗?硬通过?!

是的,我们的原则就是让测试快速通过!当然随着测试的深入,硬通过是不行的,需要加些技术和算法,我们将一步步的深入,这样让问题简单,而且每一步都走的有信心!而且每次的测试都能回归!

第三步,重构代码和测试脚本!

这里代码没什么好重构的。测试脚本重复的语句,整理一下。

public class startGame {

HangMan hangMan=new HangMan("HELLO");

@Test

public void testLength() {

assertEquals(5,hangMan.length());

}

@Test

public void testTries() {

assertEquals(12,hangMan.tries());

}

@Test

public void testUsed() {

assertEquals("AEIOU",hangMan.used());

}

}

再跑测试!绿色!OK,重构后没有引入任何问题!那么可以提交第一部分的代码啦!

接下来我们考虑,如果把单词换一个!

接下来换一个单词例如“WHO”。(本文TDD步子迈得有些大,着重了解精髓,实践时还是以小步为上)

再执行测试,测试出错了!长度的测试出错

OK,这正是我们要的,第一步,让测试出错。

第二步,我们来编写代码:

public HangMan(String word) {

this.word=word;

}

public int length() {

return this.word.length();

}

}

再执行测试!绿色!OK,通过测试。

进行重构代码。

到此为止:启动游戏的工作就做完啦!

接下来是,需求的第二部分:Play

Play:玩游戏,其实就是玩着尝试字母,会有两种情况:

1)输入正确
used增加一个字母;单词上增加相应的字母。
2)输入错误
used中没有的,就used增加该字母;Tries减1;

写测试代码,(小步快跑是可以从一个测试方法入手,一个一个的TDD,这里限于篇幅,全部写出来)我们根据需求写出所有的测试。

public class playGame {

HangMan hangMan=new HangMan("HELLO");

@Test

public void input_Correct_VowelChar() {

hangMan.types('O');

assertEquals("AEIOU",hangMan.used());

}

@Test

public void input_Correct_ConsonantChar() {

hangMan.types('L');

assertEquals("AEIOUL",hangMan.used());

}

@Test

public void input_Wrong_Char() {

hangMan.types('M');

assertEquals(11,hangMan.tries());

assertEquals("AEIOUM",hangMan.used());

}

}

跑测试!红色!OK,正是我们需要的!

第二步,编码,使得测试能快速通过。

第三步:重构,整理代码,Clean it!

以此类推,我们写出最后一部分

游戏结果(Game Result)的测试脚本。

public class gameResult {

HangMan hangMan=new HangMan("HELLO");

@Test

public void gameWin() {//输入有效的字母,完成猜测

hangMan.types('H');

hangMan.types('L');

assertEquals("HELLO",hangMan.solutionIni());

}

@Test

public void gameLose() {//输入的次数超出12次,游戏失败!

for(int i=0;i<12;i++){

hangMan.types('K');

}

assertEquals(0,hangMan.tries());

}

}

跑测试!红色!OK

编写代码!跑测试!绿色!OK

代码重构!跑测试!绿色!OK!

最后代码整理结果如下,代码重构,使得方法的代码尽量短,简单简洁!以下代码还可以再不断完善出不同版本和风格的代码!

public class HangMan {

String usedString="AEIOU";

char typeCh;

String word="";

String solutionWord="";

int triesNumber=12;

public HangMan(String word) {

this.word=word;

generationSolution();

}

private void generationSolution() {

for(int i=0;i<word.length();i++){

solutionWord+="_";

}

}

public int length() {

return this.word.length();

}

public int tries() {

return triesNumber;

}

public String used() {

return usedString;

}

public void types(char ch) {

char[] solutionArray = replaceChar(ch);

triesReduce(ch);

usedIncrease(ch);

solutionWord=String.valueOf(solutionArray);

}

private void usedIncrease(char ch) {

if(usedString.indexOf(ch)<0&&ch!='\0'){

usedString+=ch;

};

}

private void triesReduce(char ch) {

if(word.indexOf(ch)<0&&ch!='\0'){

triesNumber--;

}

}

private char[] replaceChar(char ch) {

char[] wordArray=word.toCharArray();

char[] solutionArray=solutionWord.toCharArray();

for(int i=0;i<word.length();i++){

if(ch==wordArray[i]){

solutionArray[i]=wordArray[i];

}

}

return solutionArray;

}

public String solutionIni() {

char[] wordArray=word.toCharArray();

char[] solutionArray=solutionWord.toCharArray();

for(int i=0;i<word.length();i++){

if("AEIOU".indexOf(wordArray[i])>=0){

solutionArray[i]=wordArray[i];

}

}

solutionWord=String.valueOf(solutionArray);

return solutionWord;

}

}


至此,我们领略了好玩的hangMan,也领略了别有风味的TDD。在TDD中更多感受的是实现需求,实现需求,实现需求,重构代码,重构代码,重构代码。

这种重需求,重实现,轻算法的开发思路,孰是孰非,各位看官自行评断咯。


猜你喜欢

转载自blog.csdn.net/jinhe123/article/details/52749205
TDD