发布于 2026-01-06 3 阅读
0

Coding Chess with TDD Using TDD Rinse and repeat Finally

使用TDD编写国际象棋代码

使用TDD

冲洗并重复

最后

TDD 的理念很简单,就是在编写任何代码之前先编写测试,然后只编写足够多的代码来使失败的测试通过。

测试驱动开发(TDD)有三大定律。

在编写任何生产代码之前,你必须先编写一个会失败的测试。
测试代码的长度不得超过足以导致测试失败或编译失败的程度。
生产代码的长度也不得超过足以使当前失败的测试通过的程度。
最近,我读了鲍勃大叔的《代码整洁之道》(Clean Coder)这本书——书中有很多精彩的观点,其中我立刻被他对测试驱动开发(TDD)的热情所吸引。

使用TDD

最近我一直在下棋不过棋艺很烂。我想,还有什么比用代码来学习下棋更好的方法呢?顺便一提,我也想好好尝试一下测试驱动开发(TDD)。

我编写的所有代码都是开源的,可以在我的GitHub上找到。

我用于编写测试的框架是Jest,画布库p5.js。

制作图块

那么,我需要什么呢?我需要一个棋盘,棋盘上有方块,每行需要有 8 个方块,我们来创建一个失败的测试。

注意:实际周期比我在这些示例中展示的要短一些。我会先编写足够多的代码来生成一个失败的测试,然后再编写足够多的生产代码来使该测试通过——所以在下面的示例中,我会在编写完代码后立即创建 board 类new Board() 现在我们已经有了一个失败的测试,接下来让我们编写代码来使该测试通过。
第一次测试失败的图片

https://i.imgur.com/oFSVHKD.png

太棒了,测试通过了,我们得到了一个代表棋盘的二维数组!

显示棋盘时,
我应该指出,我没有编写任何测试来实际渲染棋盘,因为 p5.js 为我完成了这项繁重的工作——这也解释了为什么覆盖率没有达到 100%。

创作作品

下一步合乎逻辑的做法就是在棋盘上摆放一些棋子。我们先从兵开始。

首先,让我们编写一个失败的测试来检查游戏开始时棋盘上是否存在该兵:
https://i.imgur.com/alresn7.png

现在我们只需编写足够的代码让这个测试通过即可。 妙啊,然后我对白方的兵重复了相同的过程。
https://i.imgur.com/NoYzYXa.png

我们已经在棋盘上摆好棋子了!
带有兵的国际象棋棋盘

下一步合乎逻辑的做法是找出兵的可能走法,但在此之前,我需要一种方法来区分黑白棋子。所以,让我们写一个测试,确保底部的兵是白棋,顶部的兵是黑棋。

https://i.imgur.com/llIn9xZ.png
所以在这个测试中,我引入了一个新的常量来表示棋子的颜色。接下来,我需要编写足够的代码来让测试通过,最简单的办法就是将新的颜色属性添加到类中Pawn,这样就能让测试通过。现在测试已经完成,我可以进行重构了。我知道每个棋子都需要一个颜色属性,所以与其在每个棋子(主教、国王、王后、车、马)中重复编写代码,不如创建一个基类来处理Piece颜色属性。

https://i.imgur.com/DpqDMZX.png
我只需重新运行测试套件就能知道它有效,TDD 让你能够充满信心地进行重构!

寻找可能的行动

那么,在国际象棋中,兵可以做什么?

  1. 它可以向前移动 1 格
  2. 斜向移动,吃掉敌方棋子
  3. 如果是第一步,则移动两格。

还有几项举措我暂时先忽略:

  • 晋级——当你到达棋盘末端时
  • 可以走吃过路兵棋,这是一种出于原则而走的棋,目的是向对手表明,是的,我知道什么是吃过路兵棋。

让我们编写第一个测试来检查兵何时只能走一步: 我在这里添加了一些内容,一个新的标志位,用于指示兵是否已经移动过,以及类中的一个新方法,该方法应该可以帮我们找到合法的走法。让我们编写生产代码来使此测试通过:
兵只有一个移动代码
Pawn

仅需编写足够多的生产代码即可通过 Pawn 测试。
所以这里我们只是检查兵前面是否有棋子,如果有,那就意味着我们不能移动到那里,这也意味着如果这是我们的第一步,我们不能向前移动两格!

你可能会觉得我有点不地道,为了让测试通过,我写了太多生产代码,你说得对。这段代码足以让后面的测试也通过。

如果白方兵没有移动,应该找到两种可能的走法。

如果白方前面有棋子,就不应该找到合法的走法。
这是我从实践 TDD 中学到的关键教训之一:不要操之过急——只需编写足够的代码让测试通过即可,不要编写更多。

codecademy.com 在其博客“红、绿、重构”中提供了一张很好的图片和解释。

  • 红色——想想你想发展什么
  • 格林——想想如何才能让你的测试通过。
  • 重构——思考如何改进你现有的实现。

红色、绿色、重构的图像

如果你像我一样操之过急,就会错过“重构”这一步。没错,你可以在写完所有生产代码之后再进行重构,但重构3行代码肯定比重构30行代码要简单得多,TDD(测试驱动开发)正是强调这一点。

现在我们已经讲解了兵向前移动和兵在初始移动中移动两格的情况,让我们添加一个测试来讲解进攻的情况。

如果敌方兵位于当前尚未移动的兵的左斜线上,则应显示 3 种合法走法。
让我们编写生产代码来返回向左斜线进攻的走法:

左斜线攻击
太棒了,这个测试通过了,但是如果我们的棋子在棋盘的最左边会发生什么呢?我非常确定代码会出错,因为它会尝试从某个地方获取值tiles[-1][y],我们来写个测试来验证一下:
如果是边兵,应该显示 2 个合法棋子。

正如我所料:
TypeError: Cannot read property '5' of undefined

让我们通过添加一个检查来解决这个问题,看看当前兵是否在棋盘的末端: 太好了,现在我们的测试通过了!然后我对右斜线重复之前的步骤,你可以想象一下那是什么样子。
添加了 if 检查,以确保它不在板子的边缘。

现在我们有了可以移动的兵,我添加了一些视觉代码,这样当你选择一个兵时,它会显示可能的移动方式。
绿色部分显示可能的走法

冲洗并重复

然后,我重复了之前分析兵的步骤,找出车的可能走法; 接着是象的走法:
鲁克斯

主教们补充道

还有骑士、国王和王后:
骑士、国王和王后

还稍微美化了一下,谁能想到Unicode里居然有国际象棋棋子呢?https://www.wikiwand.com/en/Chess_symbols_in_Unicode

Unicode国际象棋棋子

最后

我继续采用先写测试后写代码的流程,最终得到了一个可以正常运行的国际象棋游戏。当然,肯定还有一些小问题我可能遗漏了,但这仅仅是一次实践测试驱动开发(TDD)的练习。我从中学到的最重要的一点——这一点常常被忽略——就是TDD真的很有趣,而且非常有趣。没有什么比看到你精心编写的测试用例通过更令人满足的了。这种瞬间释放的内啡肽让TDD几乎让人上瘾。拥有一套可靠的、运行时间不到一秒的测试用例,能让你在重构或添加新代码时充满信心,它就像一张巨大的安全网。而且,由于你在编写任何生产代码之前就已经编写了测试,你可以确信代码漏洞极少,即使有,如果没有使用TDD,这些漏洞也肯定会存在。

我希望这篇博客能启发你尝试一下 TDD,我知道以后我肯定会默认使用它,正如我所说,它真的很有趣。

谢谢!如果您喜欢我的絮叨,欢迎访问我的个人博客网站:https://codeheir.com/

文章来源:https://dev.to/lukegarrigan/coding-chess-with-tdd-46j5