|
| 1 | +--- |
| 2 | +title: YS Testing |
| 3 | +date: 2025-07-12 |
| 4 | +draft: false |
| 5 | +authors: [ingydotnet] |
| 6 | +categories: [Summer-of-YS] |
| 7 | +edit: blog/2025-07-12.md |
| 8 | +comments: true |
| 9 | +--- |
| 10 | + |
| 11 | +YS is a great language for writing tests in. |
| 12 | + |
| 13 | +Why? |
| 14 | + |
| 15 | +Well tests are mostly data. |
| 16 | +Inputs and outputs for some code you want to test. |
| 17 | +Code is usually in functions, and functions usually have names and names are |
| 18 | +just strings and strings are just data. |
| 19 | + |
| 20 | +YAML is a great format for data. |
| 21 | +YS is YAML with code (expressed as YAML data). |
| 22 | + |
| 23 | +So it seems like YS should be a great language for writing tests in. |
| 24 | +And it is. |
| 25 | + |
| 26 | +<!-- more --> |
| 27 | + |
| 28 | + |
| 29 | +## Inline Tests in YS |
| 30 | + |
| 31 | +Let's say we have a YS program called `rot13.ys`, and we want to have some tests |
| 32 | +for it inside the program. |
| 33 | + |
| 34 | +We can do that like this: |
| 35 | + |
| 36 | +```yaml |
| 37 | +#!/usr/bin/env ys-0 |
| 38 | + |
| 39 | +tests =:: |
| 40 | +- in: Hello, world! |
| 41 | + out: Uryyb, jbeyq! |
| 42 | +- in: Uryyb, jbeyq! |
| 43 | + out: Hello, world! |
| 44 | +- in: I like pie |
| 45 | + out: V yvxr cvr |
| 46 | + |
| 47 | +defn run-tests(): |
| 48 | + each test tests: |
| 49 | + got =: test.in:rot-13 |
| 50 | + if got == test.out: |
| 51 | + say: "PASS - rot-13('$(test.in)') -> '$got'" |
| 52 | + say: "FAIL - '$got' != '$(test.out)'" |
| 53 | + |
| 54 | +defn main(input=nil): |
| 55 | + if input: |
| 56 | + say: input:rot-13 |
| 57 | + run-tests: |
| 58 | + |
| 59 | +dict =: set(\\A .. \\Z) + (\\a .. \\z) |
| 60 | +dict =: dict:cycle.drop(13 * 2).zipmap(dict) |
| 61 | + |
| 62 | +defn rot-13(str): |
| 63 | + escape str: dict |
| 64 | +``` |
| 65 | +
|
| 66 | +We can run the program like this: |
| 67 | +
|
| 68 | +```bash |
| 69 | +$ ys rot-13.ys 'rot-13 is cool' |
| 70 | +ebg-13 vf pbby |
| 71 | +$ ys rot-13.ys |
| 72 | +PASS - rot-13('Hello, world!') -> 'Uryyb, jbeyq!' |
| 73 | +PASS - rot-13('Uryyb, jbeyq!') -> 'Hello, world!' |
| 74 | +PASS - rot-13('I like pie') -> 'V yvxr cvr' |
| 75 | +$ |
| 76 | +``` |
| 77 | + |
| 78 | +If we give it a string, it will print the ROT-13 of that string. |
| 79 | + |
| 80 | +If we give it no arguments, it will run the tests. |
| 81 | + |
| 82 | +The tests are just data and YAML is great for that. |
| 83 | + |
| 84 | + |
| 85 | +## The `ys::taptest` Module |
| 86 | + |
| 87 | +Writing inline tests can be useful once in a while, but it's not the best way to |
| 88 | +write tests for a real project. |
| 89 | + |
| 90 | +Let's morph that example into something more like a real project. |
| 91 | + |
| 92 | +Program file `rot-13`: |
| 93 | + |
| 94 | +```yaml |
| 95 | +#!/usr/bin/env ys-0 |
| 96 | + |
| 97 | +use rot-13: :all |
| 98 | + |
| 99 | +defn main(input=nil): |
| 100 | + say: input:rot-13 |
| 101 | +``` |
| 102 | +
|
| 103 | +Library file `rot-13.ys`: |
| 104 | + |
| 105 | +```yaml |
| 106 | +!YS-v0 |
| 107 | +
|
| 108 | +ns: rot-13 |
| 109 | +
|
| 110 | +dict =: set(\\A .. \\Z) + (\\a .. \\z) |
| 111 | +dict =: dict:cycle.drop(13 * 2).zipmap(dict) |
| 112 | +
|
| 113 | +defn rot-13(str): |
| 114 | + escape str: dict |
| 115 | +``` |
| 116 | + |
| 117 | +Test file `rot-13.t`: |
| 118 | + |
| 119 | +```yaml |
| 120 | +#!/usr/bin/env ys-0 |
| 121 | +
|
| 122 | +use rot-13: :all |
| 123 | +use ys::taptest: :all |
| 124 | +
|
| 125 | +test:: |
| 126 | +- code: rot-13('Hello, world!') |
| 127 | + want: Uryyb, jbeyq! |
| 128 | +- code: rot-13('Uryyb, jbeyq!') |
| 129 | + want: Hello, world! |
| 130 | +- code: rot-13('I like pie') |
| 131 | + want: V yvxr cvr |
| 132 | +``` |
| 133 | + |
| 134 | +This code is much cleaner. |
| 135 | +We don't have to write testing logic because the YS builtin `ys::taptest` module |
| 136 | +takes care of that. |
| 137 | + |
| 138 | +Let's run the program and the tests: |
| 139 | + |
| 140 | +```bash |
| 141 | +$ ./rot-13 'Hello, world.' |
| 142 | +Uryyb, jbeyq. |
| 143 | +$ prove -v rot-13.t |
| 144 | +rot-13.t .. |
| 145 | +ok 1 - rot-13('Hello, world!') |
| 146 | +ok 2 - rot-13('Uryyb, jbeyq!') |
| 147 | +ok 3 - rot-13('I like pie') |
| 148 | +1..3 |
| 149 | +ok |
| 150 | +All tests successful. |
| 151 | +Files=1, Tests=3, 0 wallclock secs ( 0.00 usr 0.00 sys + 0.00 cusr 0.01 csys = 0.01 CPU) |
| 152 | +Result: PASS |
| 153 | +$ |
| 154 | +``` |
| 155 | + |
| 156 | +Sweet. |
| 157 | + |
| 158 | +The `prove` command is a [TAP](https://testanything.org/) testing harness that |
| 159 | +is likely on your system. |
0 commit comments