高木のブログ

「Effective Deno」を読みながら、Denoを触ってみる

2021/05/17

ZennにDenoのめちゃくちゃ良さげなテキスト(Effective Deno)が公開されていたので、それを読みながらDenoを触ってみた

いい感じのテーマが思い浮かばなかったのでFizzBuzz問題を実装することにした

「1から100までの数字を画面に表示する。ただし、3の倍数のときは数字の代わりにFizzと表示し、5の倍数のときは数字の代わりにBuzzと表示し、15の倍数のときは数 字の代わりにFizzBuzzと表示する」

Denoの環境

前に書いた記事(DenoでHello, World!)同様、Dockerで用意する
.bashrcに記述して、使える状態にした

Denoのバージョンは触った時点で最新の1.9.2を使った

.bashrc
deno () {
  docker run \
    --interactive \
    --tty \
    --rm \
    --volume $PWD:/app \
    --volume $HOME/.deno:/deno-dir \
    --workdir /app \
    hayd/ubuntu-deno:1.9.2 \
    deno "$@"
}
$ deno --version
deno 1.9.2 (release, x86_64-unknown-linux-gnu)
v8 9.1.269.5
typescript 4.2.2

実装

仮のfizzbuzz関数の実装

まずはfizzbuzz関数のガワだけを作る
受け取った引数の値を文字列にして返す

fizzbuzz.ts
export function fizzbuzz(number: number): string {
  return String(number);
}

本来、Fizz,Buzz,FizzBuzzはString型で、数値はNumber型で返してあげた方がいい気がするけど、今回はすべてString型で返すことにする

テストの実装

fizzbuzz関数のガワだけできたので、先にテストを書く

fizzbuzz_test.ts
import { assertEquals } from 'https://deno.land/std@0.95.0/testing/asserts.ts';
import { fizzbuzz } from './fizzbuzz.ts';

Deno.test('fizzbuzz(1)', () => {
  assertEquals(fizzbuzz(1), '1');
});

Deno.test('fizzbuzz(3)', () => {
  assertEquals(fizzbuzz(3), 'Fizz');
});

Deno.test('fizzbuzz(5)', () => {
  assertEquals(fizzbuzz(5), 'Buzz');
});

Deno.test('fizzbuzz(15)', () => {
  assertEquals(fizzbuzz(15), 'FizzBuzz');
});

テストの実行

deno testで実行できる
もちろんString型にして返すだけなので、1の場合しかテストは通らない

$ deno test
running 4 tests
test fizzbuzz(1) ... ok (4ms)
test fizzbuzz(3) ... FAILED (4ms)
test fizzbuzz(5) ... FAILED (2ms)
test fizzbuzz(15) ... FAILED (1ms)

failures:

fizzbuzz(3)
AssertionError: Values are not equal:


    [Diff] Actual / Expected


-   "3"
+   "Fizz"

    at assertEquals (https://deno.land/std@0.95.0/testing/asserts.ts:222:9)
    at file:///app/fizzbuzz_test.ts:9:3
    at asyncOpSanitizer (deno:runtime/js/40_testing.js:37:15)
    at resourceSanitizer (deno:runtime/js/40_testing.js:73:13)
    at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:100:15)
    at TestRunner.[Symbol.asyncIterator] (deno:runtime/js/40_testing.js:272:24)
    at AsyncGenerator.next (<anonymous>)
    at Object.runTests (deno:runtime/js/40_testing.js:347:22)
    at async file:///app/$deno$test.ts:3:1

fizzbuzz(5)
AssertionError: Values are not equal:


    [Diff] Actual / Expected


-   "5"
+   "Buzz"

    at assertEquals (https://deno.land/std@0.95.0/testing/asserts.ts:222:9)
    at file:///app/fizzbuzz_test.ts:13:3
    at asyncOpSanitizer (deno:runtime/js/40_testing.js:37:15)
    at resourceSanitizer (deno:runtime/js/40_testing.js:73:13)
    at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:100:15)
    at TestRunner.[Symbol.asyncIterator] (deno:runtime/js/40_testing.js:272:24)
    at AsyncGenerator.next (<anonymous>)
    at Object.runTests (deno:runtime/js/40_testing.js:347:22)
    at async file:///app/$deno$test.ts:3:1

fizzbuzz(15)
AssertionError: Values are not equal:


    [Diff] Actual / Expected


-   "15"
+   "FizzBuzz"

    at assertEquals (https://deno.land/std@0.95.0/testing/asserts.ts:222:9)
    at file:///app/fizzbuzz_test.ts:17:3
    at asyncOpSanitizer (deno:runtime/js/40_testing.js:37:15)
    at resourceSanitizer (deno:runtime/js/40_testing.js:73:13)
    at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:100:15)
    at TestRunner.[Symbol.asyncIterator] (deno:runtime/js/40_testing.js:272:24)
    at AsyncGenerator.next (<anonymous>)
    at Object.runTests (deno:runtime/js/40_testing.js:347:22)
    at async file:///app/$deno$test.ts:3:1

failures:

        fizzbuzz(3)
        fizzbuzz(5)
        fizzbuzz(15)

test result: FAILED. 1 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out (11ms)

正しく動くfizzbuzz関数に書き換える

テストが書けたので、fizzbuzz関数の中身を実装する

fizzbuzz.ts
export function fizzbuzz(number: number): string {
  if (number % 15 == 0)
    return 'FizzBuzz';
  else if (number % 3 == 0)
    return 'Fizz';
  else if (number % 5 == 0)
    return 'Buzz';
  else
    return String(number);
}
$ deno test
Check file:///app/$deno$test.ts
running 4 tests
test fizzbuzz(1) ... ok (4ms)
test fizzbuzz(3) ... ok (1ms)
test fizzbuzz(5) ... ok (1ms)
test fizzbuzz(15) ... ok (1ms)

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (8ms)

テストが無事通った

メインのコードを書く

fizzbuzz関数が無事完成したので、「1から100までの数字を画面に表示する。ただし、3の倍数のときは数字の代わりにFizzと表示し、5の倍数のときは数字の代わりにBuzzと表示し、15の倍数のときは数字の代わりにFizzBuzzと表示する」コードを書く

app.ts
import { fizzbuzz } from './fizzbuzz.ts';

for (let i = 1; i <= 100; i++) {
  console.log(fizzbuzz(i));
}
$ deno run app.ts
Check file:///app/app.ts
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

... 省略 ...

86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz

無事完成

フォーマッタを掛ける

特にコーディングルールを読まずに普段のRubyを書くように書いただけなので、フォーマッタを掛ける
deno fmtでフォーマットしてくれる

$ deno fmt
/app/app.ts
/app/fizzbuzz.ts
/app/fizzbuzz_test.ts
Checked 4 files

そこそこ修正された

# fizzbuzz.ts
 export function fizzbuzz(number: number): string {
-  if (number % 15 == 0)
-    return 'FizzBuzz';
-  else if (number % 3 == 0)
-    return 'Fizz';
-  else if (number % 5 == 0)
-    return 'Buzz';
-  else
+  if (number % 15 == 0) {
+    return "FizzBuzz";
+  } else if (number % 3 == 0) {
+    return "Fizz";
+  } else if (number % 5 == 0) {
+    return "Buzz";
+  } else {
     return String(number);
+  }
 }

# fizzbuzz_test.ts
-import { assertEquals } from 'https://deno.land/std@0.95.0/testing/asserts.ts';
-import { fizzbuzz } from './fizzbuzz.ts';
+import { assertEquals } from "https://deno.land/std@0.95.0/testing/asserts.ts";
+import { fizzbuzz } from "./fizzbuzz.ts";

-Deno.test('fizzbuzz(1)', () => {
-  assertEquals(fizzbuzz(1), '1');
+Deno.test("fizzbuzz(1)", () => {
+  assertEquals(fizzbuzz(1), "1");
 });

-Deno.test('fizzbuzz(3)', () => {
-  assertEquals(fizzbuzz(3), 'Fizz');
+Deno.test("fizzbuzz(3)", () => {
+  assertEquals(fizzbuzz(3), "Fizz");
 });

-Deno.test('fizzbuzz(5)', () => {
-  assertEquals(fizzbuzz(5), 'Buzz');
+Deno.test("fizzbuzz(5)", () => {
+  assertEquals(fizzbuzz(5), "Buzz");
 });

-Deno.test('fizzbuzz(15)', () => {
-  assertEquals(fizzbuzz(15), 'FizzBuzz');
+Deno.test("fizzbuzz(15)", () => {
+  assertEquals(fizzbuzz(15), "FizzBuzz");
 });

# app.ts
-import { fizzbuzz } from './fizzbuzz.ts';
+import { fizzbuzz } from "./fizzbuzz.ts";
 for (let i = 1; i <= 100; i++) {
   console.log(fizzbuzz(i));
 }

CIを用意する

GitHub Actionsでテストやリンタが自動で実行されるようにセットアップする

.github/workflows/ci.yml
name: ci

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
      - uses: denoland/setup-deno@main
        with:
          deno-version: "1.9.2"
      - name: Run fmt
        run: |
          deno fmt --check
      - name: Run lint
        run: |
          deno lint --unstable
      - name: Run tests
        run: |
          deno test -A

first commit · ytkg/deno_fizzbuzz@d2e6104

ちゃんと動いた良き
first commit · ytkg/deno_fizzbuzz@d2e6104

リポジトリ

今回やった内容をGitHubにあげた

ytkg/deno_fizzbuzz - GitHub


Written by ytkg, Twitter, GitHub

Pixela