使用TDD构建JSON解析器

最近,一个朋友需要解析具有自定义格式的文件,并问我如何实现该文件。 我的脑海立刻被我上大学的记忆所淹没。 因此,我回答了他,我首先将制作一个词法分析器,该词法分析器将使用文本文件并生成令牌,然后创建一个状态机,该状态机会将令牌作为状态转换的事件,并且每个转换都会建立一点点解析的结果,直到达到终端状态为止。

我对TDD感兴趣已经有一段时间了,但是没有机会尝试一下,因此这看起来像是一个制作东西的好机会。

在本系列文章中,我将尝试使用TDD创建JSON解析器,并尝试在每个步骤中共享我的尽管流程。 让我们一起学习🙂

在我们开始之前,有一些想法:

  • 我将生产代码和测试代码都写在一个文件中,以便于在它们之间进行切换。
  • 我们将从仅解析没有空格和换行符的JSON文件开始。 我们稍后会处理。
  • 我知道Foundation库有一个非常优化的JSON解析库,我们在Swift 4中添加了Codable协议,涵盖了JSON解析,但是我主要是作为练习来做的。
  • 对于那些不熟悉的人,我将尝试遵循TDD三个规则:

除非要通过失败的单元测试,否则不允许编写任何生产代码。
您不能编写任何足以使单元测试失败的单元测试。 而编译失败就是失败。
您不能编写任何足以通过一项失败的单元测试的生产代码。


我们首先创建带有单元测试的可可触摸框架项目,然后删除默认测试
对于最退化的情况,第一个测试应该始终是。 因为我们没有在任何地方传递可选参数,所以我们可以创建的最简陋的字符串是空字符串。 现在,我们只说如果解析失败,则返回nil。

现在我们处于红色阶段,我们必须通过此测试通过以使其再次变为绿色。 第一个错误是JSONParser不存在。 因此,我们创建了它。 让我们上一堂课。 现在,对parse方法的调用不存在。 我们可以通过创建一个接受字符串并返回Any的方法解析来解决此问题 。 空字符串应该失败,因为它不是有效的JSON,因此我们只返回nil

JSON RFC中,它表示JSON可以是以下之一: BooleanNumberStringArrayObject 。 让我们从最简单的解析器开始:一个布尔值。
首先,我们将解决真实情况:

TDD通过最简单的方式产生更好的结果,因此让我们尝试最简单的方法来验证值是否为true

这次我们有机会在测试代码中进行一些重构 。 解析器分配开始重复,并且可能会在每个测试中重复。 将其移至设置功能似乎是个不错的机会。 然后我们有:

列表上的下一个是处理错误的布尔值。 我们以与真实案例相同的方式进行测试:

我们做了一个if来检查是否正确 。 让我们尝试使用相同的方法,并将if演化为switch语句。

我们该怎样改进这个? 我们需要一种将“ true”转换为true并将“ false”转换false的方法 ,并且如果解析失败,我们不能忘记返回nil 。 等等,有一个功能可以做到这一点! 它叫做… Bool.init

现在我们已经处理了布尔值,让我们转到另一种类型。 所有这些双引号和所有内容都使字符串看起来很复杂。 数组和对象看起来更糟。 让我们尝试一下数字。 JSON的数字定义适用于整数和浮点数,但让我们从int解析开始。

布尔事件过后,我们变得聪明起来。 整数将变得容易。 让我们使用Int.init并嘲笑失败的测试。

即使看起来不错,我也不喜欢在代码中到处都有if let和guard let。 也许我们可以简化一下。 Bool初始化程序返回一个可选的Bool。 我们只想在布尔初始化程序失败时调用Int初始化程序。 是否只有可选为nil时才调用的函数? 有! 而且它甚至是一个运算符

欢迎使用单行编码。 我们的解析器可以处理两种类型的JSON值,并且它是一行编写的! 现在,它得到了一系列测试的支持。 在我们最近的更改之后,哪个当然可以通过。
我们着火了! 到目前为止,这个小解析器给我带来了很多乐趣,而且我相信在完成之前,我还会有更多的乐趣。


最终代码可在GitHub上找到
每个TDD步骤都在单个提交中完成,因此您可以使用自己喜欢的git工具比较提交差异来跟踪进度。