在 Arturo 赛道上测试

了解如何在 Exercism 上测试你的 Arturo 练习


Arturo 赛道所有的练习测试都使用 Unitt 3.0.0 单元测试包。 这个包提供了一个测试框架,既支持现代 RSpec 风格的语法,也支持传统的 XUnit 风格语法。 在本地练习文件夹中运行 arturo tester.art 或 exercism test,首次运行时会从互联网安装这个包。 你也可以通过运行 arturo --package install unitt 3.0.0 来安装 Unitt。 只会运行第一个测试,并把结果反馈给你。 这个测试通过后,取消跳过下一个测试,再次运行测试,直到每个测试都通过。 对 RSpec 和 XUnit 两种设置来说,做法都很简单,只要分别从 it.skip 或 test.skip 里去掉 .skip 就行。 所有测试都通过后,就可以提交你的解答,但要注意测试运行器会运行所有测试,包括被跳过的那些。

开始使用

你下载的练习文件夹里会有一些文件,但下面这三个是必不可少的。

leap/
├── src/leap.art         # Your solution file
├── tests/test-leap.art  # Test cases for the exercise
└── tester.art           # Test runner entry point

运行 arturo tester.art 会先安装好 Unitt,然后运行 tests/test-leap.art 里的测试。 测试会导入包含你解答的 src/leap.art,然后针对具体输入,对你的解答应该返回什么做出一系列断言。

解答文件

isLeap?: function [year][
    panic "please implement the isLeap? function"
]

开始一个练习时,你的解答文件会附带一个模板,其中包含期望的基本结构。 在这个来自 Leap 的例子中,文件里有一个最简版的 isLeap? 函数定义,还有一个会停止测试执行的 panic。 删掉 panic 那一行,然后开始编写 isLeap? 的实现。

RSpec 测试结构

import.version:3.0.0 {unitt}!
import {src/leap}!

describe "Leap" [
    it "a year not divisible by 4 is a common year" [
        expects.be:'false? @[isLeap? 2015]
    ]

    it.skip "a year divisible by 4 and not divisible by 100 is a leap year" [
        expects.be:'true? @[isLeap? 1996]
    ]
]
  • describe 定义一组验证同一件事的测试用例,可以嵌套。
  • it 和 it.skip 都定义一个测试用例,前者会运行,后者会被跳过。
  • expects 引出测试用例的核心,也就是断言。expects.be 接收一个谓词函数,用来做出断言。

在 expects.be:'false? @[isLeap? 2015] 中,测试套件断言函数 isLeap? 对 2015 年应返回 false。 另一种写法是 expects.be:'equal? @[false isLeapYear? 2015],这里我们断言 isLeapYear? 2015 的返回值等于 false。 这种比较两个值的较长写法,是 Arturo 赛道上最常见的断言方式。 在块里,期望值总是排在结果值前面。

XUnit 测试结构

suite "Leap" [
    test "a year not divisible by 4 is a common year" [
        result: isLeap? 2015
        assert -> false = result
    ]

    test.skip "a year divisible by 4 and not divisible by 100 is a leap year" [
        result: isLeap? 1996
        assert -> true = result
    ]
]

为了向下兼容,Unitt v1 引入的类 XUnit API 仍然受支持,它大体上等同于现代的类 RSpec API,只是断言没那么灵活。

  • suite 定义一组验证同一件事的测试用例,可以嵌套。
  • test 和 test.skip 都定义一个测试用例,前者会运行,后者会被跳过。
  • assert 引出测试用例的核心,也就是断言。后面 -> 右侧的表达式求值必须为 true。

使用 XUnit 测试时,通常会在 assert 行之前创建表示结果的变量,以及可选的期望值。

测试输出

无论你用的是 RSpec 还是 XUnit API,测试输出都大体相似。 唯一的区别是每个测试名称下面打印的内容。 XUnit 会用求值后的结果来报告断言,所以你会看到 false = false。 而 RSpec 会把它报告成 false? false,因为我们通过 expects 使用了 false? 函数。

从头开始,如果起始文件里的 panic 还没有删掉,Arturo 会报告一个错误,并且不执行任何测试。

Uncaught error in tests/test-leap.art:
#[
        output: {
                
                ══╡ Program Error ╞═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ <script> ══
                
                  Please implement the isLeap? function
                
                
        }
        code: 1
]

panic 删掉之后,Arturo 会报告测试中每个断言的状态。 现在第一个测试通过了,但第二个测试被跳过。

======== .unitt/tests/test-leap.art ========

Describe: Leap

    ✅ - assert that a year not divisible by 4 is a common year
         ✅: false? false

    ⏩ - assert that a year divisible by 4 and not divisible by 100 is a leap year
         skipped!


========== Summary ==========

⏏️   TOTAL: 2 assertions
✅  PASSED: 1 assertion
⏩ SKIPPED: 1 assertion
❌  FAILED: 0 assertion

========== ======= ==========

手动取消跳过第二个测试后,两个测试都会运行。 不过,目前的 isLeap? 代码实现只返回 false,所以第二个测试的断言会失败。

======== .unitt/tests/test-leap.art ========

Describe: Leap

    ✅ - assert that a year not divisible by 4 is a common year
         ✅: false? false

    ❌ - assert that a year divisible by 4 and not divisible by 100 is a leap year
         ❌: true? false


========== Summary ==========

⏏️   TOTAL: 2 assertions
✅  PASSED: 1 assertion
⏩ SKIPPED: 0 assertion
❌  FAILED: 1 assertion

========== ======= ==========

随着你取消跳过更多测试,这个过程会一直持续。 所有测试通过后,这个练习就完成了,你可以提交解答。 请注意,无论测试是否被跳过,测试运行器在本地都会运行所有测试。