deno 2.0 「ローマ数字変換プログラム」をTDDで始めるのが便利だった

### はじめに
みなさんも一度は **「ローマ数字を相互に変換する（4 ↔ Ⅳ）プログラム書きたいなー！」** と思ったことがありますよね。

実際は以下のような結果を得られる仕様です。
```ts
import { romanize, deromanize } from './main.ts'

romanize(1)
// 'I'
romanize(2)
// 'II'
romanize(3)
// 'III'
romanize(4)
// 'Ⅳ'
romanize(2024)
// 'MMXXIV'

deromanize('I')
// 1
deromanize('II')
// 2
deromanize('III')
// 3
deromanize('IV')
// 4
deromanize('MMXXIV')
// 2024
```
さっそく作っていきましょうー

### denoでサクッと準備

まずはdenoのinstall。
```terminal
brew install deno
```

プロジェクトの作成
```terminal
deno init romanize
cd romanize
```

ファイル内容を確認。treeコマンド便利です。

```terminal
tree
.
├── deno.json
├── main.ts
└── main_test.ts
```

### テストを先に書ける
`main_test.ts` が用意されているので、テストを先に書きはじめられます。

```ts
import { assertEquals } from '@std/assert'
// 後で実装するメソッド
import { romanize, deromanize } from './main.ts'

Deno.test('romanize 1 to 10', () => {
  assertEquals(romanize(1), 'I')
  assertEquals(romanize(2), 'II')
  assertEquals(romanize(3), 'III')
  assertEquals(romanize(4), 'IV')
  assertEquals(romanize(5), 'V')
  assertEquals(romanize(6), 'VI')
  assertEquals(romanize(7), 'VII')
  assertEquals(romanize(8), 'VIII')
  assertEquals(romanize(9), 'IX')
  assertEquals(romanize(10), 'X')
})

Deno.test('romanize 10 to 100', () => {
  assertEquals(romanize(10), 'X')
  assertEquals(romanize(20), 'XX')
  assertEquals(romanize(30), 'XXX')
  assertEquals(romanize(40), 'XL')
  assertEquals(romanize(50), 'L')
  assertEquals(romanize(60), 'LX')
  assertEquals(romanize(70), 'LXX')
  assertEquals(romanize(80), 'LXXX')
  assertEquals(romanize(90), 'XC')
  assertEquals(romanize(100), 'C')
})

Deno.test('romanize 100 to 1000', () => {
  assertEquals(romanize(100), 'C')
  assertEquals(romanize(200), 'CC')
  assertEquals(romanize(300), 'CCC')
  assertEquals(romanize(400), 'CD')
  assertEquals(romanize(500), 'D')
  assertEquals(romanize(600), 'DC')
  assertEquals(romanize(700), 'DCC')
  assertEquals(romanize(800), 'DCCC')
  assertEquals(romanize(900), 'CM')
  assertEquals(romanize(1000), 'M')
})

Deno.test('romanize 1000 to 3000', () => {
  assertEquals(romanize(1000), 'M')
  assertEquals(romanize(2000), 'MM')
  assertEquals(romanize(3000), 'MMM')
})

Deno.test('romanize 493', () => {
  assertEquals(romanize(493), 'CDXCIII')
})

Deno.test('romanize 2024', () => {
  assertEquals(romanize(2024), 'MMXXIV')
})
Deno.test('deromanize 1 to 10', () => {
  assertEquals(deromanize('I'), 1)
  assertEquals(deromanize('II'), 2)
  assertEquals(deromanize('III'), 3)
  assertEquals(deromanize('IV'), 4)
  assertEquals(deromanize('V'), 5)
  assertEquals(deromanize('VI'), 6)
  assertEquals(deromanize('VII'), 7)
  assertEquals(deromanize('VIII'), 8)
  assertEquals(deromanize('IX'), 9)
  assertEquals(deromanize('X'), 10)
})

Deno.test('deromanize 10 to 100', () => {
  assertEquals(deromanize('X'), 10)
  assertEquals(deromanize('XX'), 20)
  assertEquals(deromanize('XXX'), 30)
  assertEquals(deromanize('XL'), 40)
  assertEquals(deromanize('L'), 50)
  assertEquals(deromanize('LX'), 60)
  assertEquals(deromanize('LXX'), 70)
  assertEquals(deromanize('LXXX'), 80)
  assertEquals(deromanize('XC'), 90)
  assertEquals(deromanize('C'), 100)
})

Deno.test('deromanize 100 to 1000', () => {
  assertEquals(deromanize('C'), 100)
  assertEquals(deromanize('CC'), 200)
  assertEquals(deromanize('CCC'), 300)
  assertEquals(deromanize('CD'), 400)
  assertEquals(deromanize('D'), 500)
  assertEquals(deromanize('DC'), 600)
  assertEquals(deromanize('DCC'), 700)
  assertEquals(deromanize('DCCC'), 800)
  assertEquals(deromanize('CM'), 900)
  assertEquals(deromanize('M'), 1000)
})

Deno.test('deromanize 1000 to 3000', () => {
  assertEquals(deromanize('M'), 1000)
  assertEquals(deromanize('MM'), 2000)
  assertEquals(deromanize('MMM'), 3000)
})

Deno.test('deromanize 493', () => {
  assertEquals(deromanize('CDXCIII'), 493)
})

Deno.test('deromanize 2024', () => {
  assertEquals(deromanize('MMXXIV'), 2024)
})

```
### 本番コードにメソッド定義
`main.ts` にはメソッドを定義しましょう。
```ts
export function romanize(n: number): string {
  return ''
}

export function deromanize(r: string): number {
  return 0
}
```
TypeScriptなのも開発体験めちゃくちゃ高いですね。


### テストを実行 `deno test`

まずは失敗することを確認します。
```terminal
romanize 1 to 10 => ./main_test.ts:4:6
romanize 10 to 100 => ./main_test.ts:17:6
romanize 100 to 1000 => ./main_test.ts:30:6
romanize 1000 to 3000 => ./main_test.ts:43:6
romanize 493 => ./main_test.ts:49:6
romanize 2024 => ./main_test.ts:53:6
deromanize 1 to 10 => ./main_test.ts:56:6
deromanize 10 to 100 => ./main_test.ts:69:6
deromanize 100 to 1000 => ./main_test.ts:82:6
deromanize 1000 to 3000 => ./main_test.ts:95:6
deromanize 493 => ./main_test.ts:101:6
deromanize 2024 => ./main_test.ts:105:6

FAILED | 0 passed | 12 failed (8ms)

error: Test failed
```

**あとは実装コードを書くだけ！**

手数少なくテスト書き始められるのが便利でした！

実装コードはチャレンジしてみてください！ｗ

### Rubyバージョンのイベント紹介
言語はちがうのですが、この問題のRuby版で、Qiitaでおなじみの凄腕プログラマのコードレビューが受けられるイベントがもうすぐ開催されます！

（本当は、このイベントにJSでも挑戦できるようにしたかったのですが色々調整が間に合わなかったので記事にしておくことにしました。）

https://sonicgarden.connpass.com/event/332113/

### JSでの回答例

<details><summary>ネタバレを含みますので、実際に解いてみてからどうぞ。（ツッコミも歓迎！）</summary>

```ts
const K1 = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']
const K2 = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']
const K3 = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']
const K4 = ['', 'M', 'MM', 'MMM']

const D1 = K1.filter((s) => s.length > 0).reverse()
const D2 = K2.filter((s) => s.length > 0).reverse()
const D3 = K3.filter((s) => s.length > 0).reverse()
const D4 = K4.filter((s) => s.length > 0).reverse()

export function romanize(n: number): string {
  const [n4, n3, n2, n1] = String(n)
    .padStart(4, '0')
    .split('')
    .map((x) => parseInt(x))
  return K4[n4 % 10] + K3[n3 % 10] + K2[n2 % 10] + K1[n1 % 10]
}

export function deromanize(r: string): number {
  let o = r
  let sum = 0

  D4.forEach((s, i) => {
    if (o.startsWith(s)) {
      o = o.replace(s, '')
      sum += (D4.length - i) * 1000
    }
  })
  D3.forEach((s, i) => {
    if (o.startsWith(s)) {
      o = o.replace(s, '')
      sum += (D3.length - i) * 100
    }
  })
  D2.forEach((s, i) => {
    if (o.startsWith(s)) {
      o = o.replace(s, '')
      sum += (D2.length - i) * 10
    }
  })
  D1.forEach((s, i) => {
    if (o.startsWith(s)) {
      o = o.replace(s, '')
      sum += D1.length - i
    }
  })
  return sum
}
```
</details>