在Erlang 中初步应用TDD

在FutureLearn MOOC 关于 Erlang的课程中担任导师时,我提出了一个人们喜欢的想法。事实上,这是我在教授 Erlang 时向人们介绍模式匹配的方式。这是一种编写测试的方法,让您可以自然地从中提取代码……听起来很熟悉?是的!这是测试驱动开发(TDD)

过程

我在学习 Smalltalk 时学习了 TDD。这是一堂改变人生的课!Smalltalk 是学习它的最佳环境,因为TDD是为它而构建的。在 Smalltalk 中,我相信用不同的进程编写代码实际上更难。如果你还没有尝试过,你绝对应该尝试一下!跟随埃尔南威尔金森,他的公司 (10pines) 提供 Smalltalk 课程,您可以在其中向专家学习,以及其他一些很棒的东西。

无论如何,对于外行来说,TDD 的一般流程是……

001

图中没有描述(特别是对于编译语言,如 Erlang),添加测试后,为了看到测试失败,您首先需要代码进行编译。乍一看,这似乎很困难。但这实际上是可能的,可以使用 Erlang 的一个很好的热代码加载功能。

让我们举个例子……

我们有额外的一天吗?

因此,假设您必须编写一个名为year的模块,其中具有单一的函数:is_leap/1。 正如您可能猜到的那样,这个想法是让该函数接收一个整数并返回一个布尔值,告诉您它是否是闰年。

添加测试

第一步,我们已经使用了我上面提到的技巧。看看我们要写的模块……

-module year.
-export [test/0].
test() ->
  false = year:is_leap(1),
  ok.

让我们剖析一下这件事……

前两行应该很明显。我们定义了 module 和 export test/0,这是我们将用来运行测试的函数。

然后我们编写test/0函数。该函数将像大多数测试函数一样包含一系列断言,指定我们函数的预期结果is_leap/1。

第一个断言说当入参为1时不是闰年。但请注意,即使我们最终要is_leap/1在同一个模块中定义,我们也使用它的完全限定版本 ( year:is_leap(…)) 而不是仅仅编写is_leap(1). 为什么?当然是因为代码热加载!

如果 Erlang 编译器发现对未定义的内部函数的调用,将会失败,它不会编译你的模块,因此它不会运行测试。除非……如果您使用完全合格的调用。因为由于热代码加载背后的逻辑,您可以在模块的更高版本中完全定义该功能。

这对我们非常有用,因为现在我们可以……

见证测试失败

1> c(year).
{ok, year}
2> year:test().
** exception error: undefined function year:is_leap/1
     in function  year:test/0 (year.erl, line 5)

编写代码

好吧,现在我们的测试告诉我们需要定义is_leap/1……所以我们照做了!

-module year.
-export [test/0].
-export [is_leap/1].
test() ->
  false = year:is_leap(1),
  ok.
is_leap(_) -> false.

运行测试

我们尝试以更简洁的方式编译和运行我们的测试,这样我们每次进入控制台时都可以使用向上箭头……哦,懒惰的开发者!

3> c(year), year:test().
ok.

清除然后重复

嗯……这很好!现在让我们在测试中添加更多断言……

-module year.
-export [test/0].
-export [is_leap/1].
test() ->
  % regular years are not leap
  false = year:is_leap(1),
  false = year:is_leap(1995),
  false = year:is_leap(1997),
  % multiples of 4 are leap…
  true = year:is_leap(1996),
  true = year:is_leap(2004),
  true = year:is_leap(2008),
  % …except if they're multiples of 100…
  false = year:is_leap(1800),
  false = year:is_leap(1900),
  false = year:is_leap(2100),
  % …except if they're also multiples of 400…
  true = year:is_leap(1600),
  true = year:is_leap(2000),
  true = year:is_leap(2400),
  ok.
is_leap(_) -> false.

……然后运行……

5> c(year), year:test().
** exception error: no match of right hand side value false
     in function  year:test/0 (year.erl, line 11)

第 11 行是1996(如预期的那样)……我们现在针对这种情况修复我们的代码,再次运行测试……许多迭代经过……以此类推……

为简洁起见,我将跳到最终版本……

-module year.
-export [test/0].
-export [is_leap/1].
test() ->
  % regular years are not leap
  false = year:is_leap(1),
  false = year:is_leap(1995),
  false = year:is_leap(1997),
  % multiples of 4 are leap…
  true = year:is_leap(1996),
  true = year:is_leap(2004),
  true = year:is_leap(2008),
  % …except if they're multiples of 100…
  false = year:is_leap(1800),
  false = year:is_leap(1900),
  false = year:is_leap(2100),
  % …except if they're also multiples of 400…
  true = year:is_leap(1600),
  true = year:is_leap(2000),
  true = year:is_leap(2400),
  ok.
is_leap(Year) ->
  is_leap(Year rem 4, Year rem 100, Year rem 400).
is_leap(_, _, 0) -> true;
is_leap(_, 0, _) -> false;
is_leap(0, _, _) -> true;
is_leap(_, _, _) -> false.

然后再次运行…

7> c(year), year:test().
ok

更复杂的东西

当然,这种技术只适用于不应该投入生产的非常小的代码片段。您不希望测试代码弄乱您的发布!

但是如果你选择Common Test作为你的测试框架,这个模块的套件看起来会非常熟悉……

-module year_SUITE.
-export [all/0].
-export [is_leap/1].
all() -> [is_leap].
is_leap(_) ->
  % regular years are not leap
  false = year:is_leap(1),
  false = year:is_leap(1995),
  false = year:is_leap(1997),
  % multiples of 4 are leap…
  true = year:is_leap(1996),
  true = year:is_leap(2004),
  true = year:is_leap(2008),
  % …except if they're multiples of 100…
  false = year:is_leap(1800),
  false = year:is_leap(1900),
  false = year:is_leap(2100),
  % …except if they're also multiples of 400…
  true = year:is_leap(1600),
  true = year:is_leap(2000),
  true = year:is_leap(2400),
  {comment, ""}.

你怎么看?我说服你为 Erlang 使用测试驱动开发了吗?😉

Related Posts

2021 年你需要知道的关于 Erlang 的一切

今天,我们将看一个相当古老且有些古怪的东西。 你们大多数人可能没有注意到的语言。 虽然 Erlang 不像某些现代编程语言那样流行,但它安静地运行着 WhatsApp 和微信等每天为大量用户提供服务的应用程序。 在这篇文章中,我将告诉你关于这门语言的更多事情、它的历史,以及你是否应该考虑自己学习它。 ## 什么是 Erlang,它在哪里使用? Erl

Read More

Erlang JIT中基于类型的优化

这篇文章探讨了 Erlang/OTP 25 中基于类型的新优化,其中编译器将类型信息嵌入到 BEAM 文件中,以帮助JIT(即时编译器)生成更好的代码。 ## 两全其美 OTP 22 中引入的基于SSA的编译器处理步骤进行了复杂的类型分析,允许进行更多优化和更好的生成代码。然而,Erlang 编译器可以做什么样的优化是有限制的,因为 BEAM 文件必须

Read More

Erlang JIT之路

自从Erlang 存在,就一直有让它更快的需求和野心。这篇博文是一堂历史课,概述了主要的 Erlang 实现以及如何尝试提高 Erlang 的性能。 ## Prolog 解释器 Erlang 的第一个版本是在 1986 年在 Prolog 中实现的。那个版本的 Erlang 对于创建真正的应用程序来说太慢了,但它对于找出Erlang语言的哪些功能有用,哪

Read More