お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

以前記事にした加古川総合文化センターにて行われた「いとうのいぢ展 ぜんぶ!」で注文した版画が遂に到着した。

これは私にとっては初の版画となる。版画といえば、今から20年かそのくらい前には版画屋という悪質商人がいるという噂で良いイメージがなかったが、今ではそういう話も聞かなくなった気がするのできっと良い世界になったのだと思う。個人的には版画自体は職業イラストレーターの貴重な収入源の一つになっていると考えている。

まぁ、前振りは程々にしておき、こちらが今回届いた版画だ。プチプチで二重巻きにされてた上に、角には段ボールが嵌めてあり、更に額縁そのものは段ボールで包装されており、更にその中もビニールで保護されているという、非常に厳重に作品が保護されていて、素直に凄いと思った。まだこの写真の時点ではビニールで包装された状態だ。

今回届いた版画

しかし買う前のサンプルで解っていたことだが、やはり加古川市章はない。

これはビニール包装を解いた裏側だ。ぶら下げる金具の様なものと謎の紐が見えるが、これはぶら下げる金具ではなく紐通しらしい

版画の裏側、額縁の背中だ

この紐を使ってフックなどにぶら下げて飾る用途のようだ。よく見たら作品に添付されていた説明書にもそう書かれていた。作品を飾るのにちょうどいい塩梅で設定されているらしく、解かないほうがいいらしい。ただ金具類は調子に応じて調整してとあったので、左下の緩んでいる金具だけドライバーで締めておいた。

作品保証書までついていた。エンボス加工がされており、高級感があるが、恐らくは偽造防止用だろう。エディションナンバーまで刻まれていて、これが私の元に届いたものであることを正に証明している。

作品保証書

ひとまず撮影台の上に置いておいても仕方がないので飾ってみることにした。フックはあるのだが、程よい高さに丁度いいスペースがないのと、立てかけていた方が額縁らしい気がしたので壁に立てかけてみた。部屋の明かりの関係で反射するため、暗めの写真になってしまったがいい感じだ。下に並んでいるのはアートタイルで、こちらは会場で直接購入したものだ。版画は会場での直接販売がなかったので、会場での予約販売を利用した。

版画を飾ってみたところ

因みに版画とは言うが、いわゆる木彫りの板にインクを塗って紙に押し付けるようなものではなく、特殊印刷と呼ばれるものである。例えばこの作品には88グラフという印刷技法が使われている。一般的な版画作品はシルクスクリーンという技法がベースになっていると聞くが、こちらも例にもれずシルクスクリーンを利用しているようだ。

大和グランド株式会社の説明によるとシルクスクリーン印刷は一般商業印刷ではほとんど用いられず、特殊な印刷か、軽印刷の分野で多用されているとのことだった。ステッカー、Tシャツ、看板、計器版、プリント回路などの印刷に用いられるケースが多いようである。印刷対象や塗料の種類が豊富なのが強みらしいので、複合的な印刷技術を用いる版画向きの手法なのかもしれない。

ただ今回の版画はアールジュネスで売られているものと比べると幾分か安く見えたので、廉価な印刷技法を使うなど、どこかでコストカットを行っているのだろうか?そこは少しだけ気になった。確か桁が違ったような…。

私の部屋には他にもポスターやタペストリーといった美術品があるが、この版画はそれらとは一線を画した美術品だと思うので、墓に入るまで持ち続けていたいものである。何よりこれは兵庫県の加古川市をモチーフにしたいとうのいぢ先生の作品だ。神戸在住の私としても加古川の宝の一つと言える。

因みにこのキャラクターの名前はかこのちゃんに内定している。なお加古川市章を身に着けたかこのちゃんのフルサイズイラストが見れるのは私が知りうる限り、加古川市のサイトや、加古川市の関連施設のみで、恐らく商業的なグッズ化などはされていない。

JestからNode.js組み込みのテストランナーに移行する時の方法や注意点をまとめたメモ

確認環境

Env ver
Node.js 20.8.0
Jest 29.7

Node.jsの組み込みテストランナーとは

英語圏ではNode.js built-in test runnerとか呼ばれている存在で、Node.jsの組み込みテストランナーのこと。恐らくDenoのテストランナーに対抗して生まれた気配がする(Node.jsはDenoに機能追加があると真似する傾向があるため)

Node.jsの組み込みテストランナーは機能が二分されており、テストランナーアサートに分かれている。Jestみたいにエコシステムが発達しておらず、標準ではdescribeなどはimportする必要がある。TypeScriptをテストする場合は間にレジスター[^1]を嚙ます必要がある。噛まし方はswc/jestでESM, CJS混在のコードをJestを使ってテストする有力な方法は今のところ多分ないに書いた。

Jest関数との対応リスト

describe, it, beforeAll, afterAll, beforeEach, afterEach辺りは違和感がなかったが、それ以外は軒並み互換性がないので大きく書き換えが必要だと感じた。便利なMatcherは完膚なきまでに全滅している。お陰で覚えることが減ったのは逆に良くなったと感じる

なお、not始まりの機能は実際の動作を確認しておらず、Jestと同じ機能かどうかは確認していないことに留意すること

describe(name, fn), it(name, fn, timeout?), test(name, fn, timeout?)

Node.jsのTest機能では、以下となる

  • describe([name][, options][, fn])
  • it([name][, options][, fn])
  • test([name][, options][, fn])

基本的な差はないがJestにあったtimeout引数はNode.jsではoptionsパラメーターで設定するようになっている模様。今回調べるまで存在自体を知らなかったのもあり、このtimeoutがJestと同じ機能かどうかは確認していない

import { describe, it } from 'node:test';

describe('hoge', () => {
  it('hoge', () => {
    // ここにテストコード
  });

  it.todo('piyo');
});

beforeAll(fn, timeout?), afterAll(fn, timeout?), beforeEach(fn, timeout?), afterEach(fn, timeout?)

Node.jsのTest機能では、以下となる

  • before([fn][, options])
  • after([fn][, options])
  • beforeEach([fn][, options])
  • afterEach([fn][, options])

基本的な差はないがJestにあったtimeout引数はNode.jsではoptionsパラメーターで設定するようになっている模様。今回調べるまで存在自体を知らなかったのもあり、このtimeoutがJestと同じ機能かどうかは確認していない

+import { after, before, beforeEach, afterEach, describe } from 'node:test';
+
 describe('test', () => {
-  beforeAll(() => {
+  before(() => {
     console.log('before');
   });
   beforeEach(() => {
     console.log('beforeEach');
   });
   afterEach(() => {
     console.log('afterEach');
   });
-  afterAll(() => {
+  after(() => {
     console.log('after');
   });
 });

.toBe(value), .not.toBe(value)

Node.jsのTest機能では、以下となる

  • assert.strictEqual(actual, expected[, message])
  • assert.notStrictEqual(actual, expected[, message])
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
     const actual = 123;

-    expect(actual).toBe(123);
+    assert.deepStrictEqual(actual, 123);
   });
 });

.toStrictEqual(value), .not.toStrictEqual(value)

Node.jsのTest機能では、以下となる

  • assert.deepStrictEqual(actual, expected[, message])
  • assert.notDeepStrictEqual(actual, expected[, message])
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
     const actual = {
       hoge: {
         piyo: {
           id: 1,
           value: 'one'
         }
       },
       fuga: [[123, 456], 'ABC', 'EFG']
     };

-    expect(actual).toStrictEqual({
+    assert.deepStrictEqual(actual, {
       hoge: {
         piyo: {
           id: 1,
           value: 'one'
         }
       },
       fuga: [[123, 456], 'ABC', 'EFG']
     });
   });
 });

.toHaveLength(number), .toBeNull(), .toBeUndefined(), .toBeNaN()

Node.jsのTest機能では、assert.strictEqual()となる

+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
     const arr = [1, 2, 3];
     const undef = undefined;
     const nil = null;
     const nan = NaN;

-    expect(arr).toHaveLength(3);
-    expect(undef).toBeUndefined();
-    expect(nil).toBeNull();
-    expect(nan).toBeNaN();
+    assert.strictEqual(arr.length, 3);
+    assert.strictEqual(undef, undefined);
+    assert.strictEqual(nil, null);
+    assert.strictEqual(nan, NaN);
   });
 });

.toBeTruthy()

Node.jsのTest機能では、以下となる

  • assert.ok(value[, message])

否定版は不明

+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
-    expect(true).toBeTruthy();
-    expect({}).toBeTruthy();
-    expect([]).toBeTruthy();
-    expect(42).toBeTruthy();
-    expect('0').toBeTruthy();
-    expect('false').toBeTruthy();
-    expect(new Date()).toBeTruthy();
-    expect(-42).toBeTruthy();
-    expect(12n).toBeTruthy();
-    expect(3.14).toBeTruthy();
-    expect(Infinity).toBeTruthy();
-    expect(-Infinity).toBeTruthy();
+    assert.ok(true);
+    assert.ok({});
+    assert.ok([]);
+    assert.ok(42);
+    assert.ok('0');
+    assert.ok('false');
+    assert.ok(new Date());
+    assert.ok(-42);
+    assert.ok(12n);
+    assert.ok(3.14);
+    assert.ok(Infinity);
+    assert.ok(-Infinity);
   });
 });

.toBeInstanceOf(Class)

Node.jsのTest機能では、assert.ok()となる

結果がTruthyであればなんでもpassするので注意

+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
     const actual = new Error('hoge');

-    expect(actual).toBeInstanceOf(Error);
+    assert.ok(actual instanceof Error);
   });
 });

.toThrow, not.toThrow

Node.jsのTest機能では、以下となる

  • assert.throws(fn[, error][, message])
  • assert.doesNotThrow(fn[, error][, message])

Jestより便利になっており、Error型以外も扱えるので後述する

+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
     const err = new Error('test');

-    expect(() => {
+    assert.throws(() => {
       throw err;
-    }).toThrow(err);
+    }, err);
   });
 });
Node.jsではJestと異なりError型以外も扱える

Jestの`.toThrow()はError型以外を扱うことができず、オブジェクトをThrowするようなコードではエラーになる

describe('test', () => {
  it('test', () => {
    const obj = { id: 1, value: 'hoge' };

    // このテストは失敗する。またTypeScriptの型エラーにもなる
    expect(() => {
      throw obj;
    }).toThrow(obj);
    // このテストは失敗する。またTypeScriptの型エラーにもなる
    expect(() => {
      throw obj;
    }).toThrow({ id: 1, value: 'hoge' });
  });
});

しかしNode.jsであればこれはエラーにならない。例えば次のテストコードは成功する

import { describe, it, mock } from 'node:test';
import assert from 'node:assert';

describe('test', () => {
  it('test', () => {
    const obj = { id: 1, value: 'hoge' };

    assert.throws(() => {
      throw obj;
    }, obj);
    assert.throws(
      () => {
        throw obj;
      },
      { id: 1, value: 'hoge' }
    );
  });
});

expect.arrayContaining(array), expect.objectContaining(object), expect.stringContaining(string)

恐らく非対称マッチャーはないので自分でロジックを書いてassert.strictEqual()で判定するしかないと思われる。元々微妙な機能だったのでやむなし

jest.spyOn(object, methodName, accessType?)

Node.jsのTest機能では、node:testからmockをimportして以下を使う

  • mock.method(object, methodName[, implementation][, options])
+import { describe, it, mock } from 'node:test';
+
 describe('test', () => {
   it('test', () => {
-    jest.spyOn(console, 'log');
+    mock.method(console, 'log');
   });
 });
本来の挙動を塞ぎたい場合

例えば以下のようにテストコードを書いた場合、execSync()が実際の挙動で動作してしまい、テストとして機能しない。

import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
import child_process, { execSync } from 'node:child_process';

const mockedExecSync = mock.method(child_process, 'execSync');

describe('execSync', () => {
  it('execSyncが正しい引数で呼ばれること', () => {
    execSync('false');

    assert.strictEqual(mockedExecSync.mock.calls[0].arguments[0], 'false');
  });
});

このような場合、以下のようにmock.method()の第三引数を指定してやると封じることができる。単体テストの観点では基本的に第三引数には空関数を入れておくのが望ましいだろう。

import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
import child_process, { execSync } from 'node:child_process';

- const mockedExecSync = mock.method(child_process, 'execSync');
+ const mockedExecSync = mock.method(child_process, 'execSync', () => {});

describe('execSync', () => {
  it('execSyncが正しい引数で呼ばれること', () => {
    execSync('false');

    assert.strictEqual(mockedExecSync.mock.calls[0].arguments[0], 'false');
  });
});
モックパターン
module.exportsされている関数のモック

import foo from 'foo';形式でmock.method()の第一引数を埋める

import { describe, it, mock } from 'node:test';
import assert from 'node:assert';
import child_process, { execSync } from 'node:child_process';

const mockedExecSync = mock.method(child_process, 'execSync', () => {});

describe('execSync', () => {
  it('execSyncが正しい引数で呼ばれること', () => {
    execSync('false');

    assert.strictEqual(mockedExecSync.mock.calls[0].arguments[0], 'false');
  });
});

上記が正常に動作することはNode.js v20.0.0時点のコードで確認している

Global objectから生えている関数のモック

以下のようにmock.method()の第一引数にGlobal objectを設定すればよい

import { describe, it, mock } from 'node:test';
import assert from 'node:assert';

describe('exit', () => {
  // exitが実際に走って落ちるのでmock.methodの第三引数を指定している
  const mockedExit = mock.method(process, 'exit', () => {});

  it('call exit', () => {
    process.exit(1);

    assert.strictEqual(mockedExit.mock.calls.length, 1);
  });
});
Named exportされている関数のモックは今のところ無理そう

ファイルモックをする手段がないので正攻法では無理そう
https://github.com/nodejs/help/issues/4298

2024-10-15追記

Node.js v22.3.0でテスト走行時に--experimental-test-module-mocksを渡すことで近いことができるようになった模様だが、試したところ上手く動かないし、spy的な使い方はできなさそうだ。mock.method()との組み合わせも試してみたが、上手くいかなかった。

mock.module() (nodejs.org)

ObjectやNamespaceでラップされている関数のモック

実装例(Object)

export const Hoge = {
  validateFoo() {
    // 例外を飛ばす可能性のある何かの処理
  },
  hoge() {
    Hoge.validateFoo();

    return 1;
  },
};

実装例(Namespace)

export namespace Hoge {
  export const validateFoo = () => {
    // 例外を飛ばす可能性のある何かの処理
  };
  export const hoge = () => {
    validateFoo();

    return 1;
  };
}

実装例に対するテストコード

ObjectもNamespaceも同じ書き方でテスト可能

import { describe, it } from 'node:test';
import assert from 'node:assert';
import { Hoge } from './hoge';

describe('hoge', () => {
  it('validateFooが例外をスローした場合、例外がスローされること', (t) => {
    t.mock.method(Hoge, 'validateFoo', () => {
      throw new Error('foo');
    });

    assert.throws(() => {
      Hoge.hoge();
    }, Error('foo'));
  });

  it('全ての関数が正常終了した場合、戻り値を返すこと', () => {
    const actual = Hoge.hoge();

    assert.strictEqual(actual, 1);
  });
});

.toHaveBeenCalled(), .toHaveBeenCalledTimes(number)

Node.jsのTest機能では、assert.strictEqual()でモックから生えてるやつを調べる。returnも同様の手法で実現できる

+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
-    const spiedConsoleLog = jest.spyOn(console, 'log');
+    const mockedConsoleLog = mock.method(console, 'log');

     console.log();

-    expect(spiedConsoleLog).toHaveBeenCalled();
-    expect(spiedConsoleLog).toHaveBeenCalledTimes(1);
+    assert.deepStrictEqual(mockedConsoleLog.mock.calls.length, 1);
   });
 });

.toHaveBeenCalledWith(arg1, arg2, ...)

Node.jsのTest機能では、assert.strictEqual()でモックから生えてるやつを調べる。returnも同様の手法で実現できる。

Jestでは.toEqual()処理されるがNode.jsの組み込みテストランナーの場合、厳密比較ができるので便利

+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
-    const spiedConsoleLog = jest.spyOn(console, 'log');
+    const mockedInfo = mock.method(console, 'log');

     console.log('test');

-    expect(spiedConsoleLog).toHaveBeenCalledWith('test');
+    assert.deepStrictEqual(mockedInfo.mock.calls[0].arguments[0], 'test');
   });
 });

jest.fn(implementation?)

Node.jsのTest機能では、以下となる

  • mock.fn([original[, implementation]][, options])
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
+// 動作確認用の関数
 const testTarget = (cbParam: string, callbackFn: (param: string) => number) => {
   return callbackFn(cbParam);
 };

 describe('test', () => {
   it('test', () => {
-    const mockFn = jest.fn((_: string) => {
+    const mockFn = mock.fn((_: string) => {
       return 5;
     });
     const actual = testTarget('hoge', mockFn);

-    expect(mockFn).toBeCalledWith('hoge');
-    expect(mockFn).toReturnWith(5);
-    expect(actual).toBe(5);
+    assert.deepStrictEqual(mockFn.mock.calls[0].arguments[0], 'hoge');
+    assert.deepStrictEqual(mockFn.mock.calls[0].result, 5);
+    assert.deepStrictEqual(actual, 5);
   });
 });

jest.useFakeTimers(fakeTimersConfig?), jest.runAllTimers()

Node.jsのTest機能では、以下となる

  • mock.timers.enable([timers])
  • mock.timers.runAll()
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
+describe('test', () => {
-  jest.useFakeTimers();
+  mock.timers.enable();

+  it('test', () => {
-    const mockFn = jest.fn();
+    const mockFn = mock.fn();
     setTimeout(() => {
       mockFn();
     }, 9999);

-    expect(mockFn).not.toHaveBeenCalled();
-    jest.runAllTimers();
-    expect(mockFn).toHaveBeenCalledTimes(1);
+    assert.deepStrictEqual(mockFn.mock.calls.length, 0);
+    mock.timers.runAll();
+    assert.deepStrictEqual(mockFn.mock.calls.length, 1);
   });
 });

jest.useRealTimers()

Node.jsのTest機能では、以下となる

  • mock.timers.reset()
+import { mock } from 'node:test';

-jest.useRealTimers();
+mock.timers.reset();

mockFn.mockClear()

Node.jsのTest機能では、以下となる

  • ctx.resetCalls()
+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
-    const mockFn = jest.fn((param: string) => `<${param}>`);
+    const mockFn = mock.fn((param: string) => `<${param}>`);

     mockFn('hoge');
-    expect(mockFn).toHaveBeenCalledTimes(1);
-    expect(mockFn).toReturnWith('<hoge>');
+    assert.deepStrictEqual(mockFn.mock.calls.length, 1);
+    assert.deepStrictEqual(mockFn.mock.calls[0].result, '<hoge>');

-    mockFn.mockClear();
+    mockFn.mock.resetCalls();

-    expect(mockFn).toHaveBeenCalledTimes(0);
+    assert.deepStrictEqual(mockFn.mock.calls.length, 0);
     mockFn('piyo');
-    expect(mockFn).toHaveBeenCalledTimes(1);
-    expect(mockFn).toReturnWith('<piyo>');
+    assert.deepStrictEqual(mockFn.mock.calls.length, 1);
+    assert.deepStrictEqual(mockFn.mock.calls[0].result, '<piyo>');
   });
 });

mockFn.mockReset()

Node.jsのTest機能では、以下となる

  • mockFn.mock.restore()

振る舞いが微妙に違うため後述する

+import { describe, it, mock } from 'node:test';
+import assert from 'node:assert';
+
 describe('test', () => {
   it('test', () => {
-    const spiedConsoleLog = jest
-      .spyOn(console, 'log')
-      .mockImplementation((param: any) => {
-        return `<${param}>`;
-      });
+    const mockedConsoleLog = mock.method(console, 'log', (param: any) => {
+      return `<${param}>`;
+    });

     console.log('hoge');
-    expect(spiedConsoleLog).toHaveBeenCalledTimes(1);
-    expect(spiedConsoleLog).toReturnWith('<hoge>');
+    assert.deepStrictEqual(mockedConsoleLog.mock.calls.length, 1);
+    assert.deepStrictEqual(mockedConsoleLog.mock.calls[0].result, '<hoge>');

-    spiedConsoleLog.mockReset();
+    mockedConsoleLog.mock.restore();
   });
 });

JestとNode.jsでの振る舞いの差異

但しJestとNode.jsのTest機能では微妙に差異がある

例えばJestでは以下の実装が正しくPASSするが

describe('test', () => {
  it('test', () => {
    const mockFn = jest
      .spyOn(console, 'log')
      .mockImplementation((param: any) => {
        return `<${param}>`;
      });

    console.log('hoge');
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toReturnWith('<hoge>');

    mockFn.mockReset();

    expect(mockFn).toHaveBeenCalledTimes(0);
    console.log('piyo');
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).not.toHaveBeenCalledWith();
    expect(mockFn).toReturnWith(undefined);
  });
});

Node.jsで以下の実装を書いても同じように機能しない

import { describe, it, mock } from 'node:test';
import assert from 'node:assert';

describe('test', () => {
  it('test', () => {
    const mockFn = mock.method(console, 'log', (param: any) => {
      return `<${param}>`;
    });

    console.log('hoge');
    assert.deepStrictEqual(mockFn.mock.calls.length, 1);
    assert.deepStrictEqual(mockFn.mock.calls[0].result, '<hoge>');

    mockFn.mock.resetCalls();
    mockFn.mock.restore();

    assert.deepStrictEqual(mockFn.mock.calls.length, 0);
    console.log('piyo');
    // 以降のテストはいずれも落ちる
    assert.deepStrictEqual(mockFn.mock.calls.length, 1);
    assert.deepStrictEqual(mockFn.mock.calls[0].result, '<piyo>');
  });
});

但しモック実装を削除したうえで再度呼び出すという行為には意味がないので、特に問題にはならないと思われる

モック機能についての備考

it([name][, options][, fn])の第三引数のコールバックの第一引数にはTestContextが入っており、これを使ってモックすることもできる

これを使う場合、スコープアウトでモックが復元されるため、例えば以下のような関数をテストするときに便利である。

実装

export namespace Hoge {
  export const validateFoo = () => {
    // 例外を飛ばす可能性のある何かの処理
  };

  export const validateBar = () => {
    // 例外を飛ばす可能性のある何かの処理
  };

  export const validateBaz = () => {
    // 例外を飛ばす可能性のある何かの処理
  };

  export const hoge = () => {
    validateFoo();
    validateBar();
    validateBaz();

    return 1;
  };
}

テストコード

import { describe, it } from 'node:test';
import assert from 'node:assert';
import { Hoge } from './hoge';

describe('hoge', () => {
  it('validateFooが例外をスローした場合、例外がスローされること', (t) => {
    t.mock.method(Hoge, 'validateFoo', () => {
      throw new Error('foo');
    });

    assert.throws(() => {
      Hoge.hoge();
    }, Error('foo'));
  });

  it('validateBarが例外をスローした場合、例外がスローされること', (t) => {
    t.mock.method(Hoge, 'validateBar', () => {
      throw new Error('bar');
    });

    assert.throws(() => {
      Hoge.hoge();
    }, Error('bar'));
  });

  it('validateBazが例外をスローした場合、例外がスローされること', (t) => {
    t.mock.method(Hoge, 'validateBaz', () => {
      throw new Error('baz');
    });

    assert.throws(() => {
      Hoge.hoge();
    }, Error('baz'));
  });

  it('全ての関数が正常終了した場合、戻り値を返すこと', () => {
    const actual = Hoge.hoge();

    assert.strictEqual(actual, 1);
  });
});
投稿日:
ジャンル::生活

SONY MDR-CD900ST

ヘッドホンは電池が切れると使えなくなる、最早これは世間の常識だと思います。しかし世の中には電池が切れないヘッドホンがなんと存在します。恐らく若い人の中には知らない人がいたり、そうでなくても忘れている人も少なくはないでしょう。

今日ふと街中を歩いていると街中利用にしては見慣れないヘッドホンをつけている人を見かけました。どう見ても赤帯だったので歩く速度を速め、追い抜きざまに見てみると、なんと赤帯でした。まさか街中で赤帯をつけてる人を見ることになるとは思わず、だいぶ驚きました。

今やイヤホンジャックがついたスマホはレアなので刺しているとしたらiPodやWalkman的な何かでしょうか…。それも変換プラグがないと使えないはずです。まさかヘッドホン側のプラグを取り換えてるのでしょうか…。

個人的に今の時代に普通のヘッドホンを屋外、それも街中で使っている人がいるのは驚きでした。それも赤帯をです。

因みに私はプラグがないスマホを使っている関係でSONY WI-XB400を利用しています。どっちかというとプラグがないからというよりかは、過去に有線を使っていた時に人混みで他の人に引っかかって断線した経験があるので変えた感じですが…。

ただこのイヤホンも終売なので、次どうするかが悩ましいところです。紛失防止や、取り外し容易性の観点からネックバンド式がいいんですよね。例えば人とちょっと話すときに首にぶら下げておけるのは結構便利なので。ストックとして数本持っておくのもなくはないとは思いますが…。

因みに家の中ではSONY MDR-XB55をUSB-C変換アダプタを通じて使っている他、PCではMDR-CD900ST(赤帯)を使っているので、「電池切れのないヘッドホン」を使っています。外ではUSB-C変換アダプタが折れる可能性があるのと、3極プラグではスマホ音楽プレイヤーの制御ができないので使っていません。

ところで全然関係ないですが、最近イヤホンのことをヘッドホンと呼ぶ風潮について、個人的にはあまり気に入ってません。というか、これに限らず同系統の名称を何か特定の名称に寄せる現象ってある気がします(恐らく細分化されていると判断できないのでしょう)

しかし一体、件の人は何にヘッドホンをどう刺してつかっていたのでしょうか?ちょっと気になりました。

筆記具調理具と続いた、この変革シリーズも第三弾になった。今回はGoogleからMicrosoftへという内容だ。因みに変革シリーズというのは記事のタグに変革と入れているところから名付けている。

私は長らくGoogle IMEとGoogle Chromeのユーザーであったが、これをMS-IMEとMicrosoft Edgeに変更したという内容だ。これもまた、レガシーへの回帰の一環といえるだろう。

まず何故変えようと思ったかだが、最大の契機はChromeのアップデートで使えない機能が毎回増えていく中、ある時UIが大改編されたことだ。

ブックマークバーやメニューの一項目が以前より巨大化し、この時かなり萎えて使う気がなくなってしまった(119.0.6045.160なら再現すると思う)。そして、これを機にIMEとブラウザをMSに戻そうと思ったのである。Edgeの方がまともだという根拠はなかったが、使ってみたところまともだったので良かった。

Edgeについて個人的に良いと感じたところは間違えてウィンドウを閉じてしまったときに、閉じたウィンドウを復元する機能があることだ。知りうる限りChromeにこの機能はない。他にもUIがChromeよりスマートで、タブを表示する部分に余計な物が出てこないことや、各種登録フォームで氏名が分割された入力欄にオートコンプリートがちゃんと入ることも評価している。

またカスタマイズ出来る項目が多く自分の手に馴染む内容にできることや、開いてるタブ一覧も垂直タブで見やすくできる部分は気に入っている。左に出て来たり、本来のタブバーが消えるのは気に入ってないが、これも{Ctrl} + {Shift} + {,}で切り替えられるので特に問題視してはいない。

Webサイトを右クリックしたときのメニューがやたら多い部分やお気に入りバーを展開したときの文字サイズがやたら大きい部分、ダウンロードのポップアップが若干扱いづらい部分は不満があるが、慣れれば気にならなくなると思うので許容範囲だ。

因みにスマートフォン(Android)のブラウザもChromeからEdgeに変更しているが、Secretモードと通常モードがタブになっていて見やすいことや、タブの一覧性の良さが気に入っている。メニューのカスタマイズもできるしお気に入りも見易い位置に置けるのはありがたい(Android Chromeのあれは存在を忘れてしまいがちだ…)。そのほかにスピードダイアルを編集できるのもすごくいいし、セキュリティが強固で、クレジットカードのオートコンプリートが3段階認証になっているのも好感が持てる。無駄な買い物をする前に思いとどまるチャンスが増やせるのはいい事だ。

基本的に私はGoogleを信頼していないので、Microsoftに移せる場所は移していきたいと思っている。例えばかつてはGoogle Docsを使っていたこともあったのが、MS Officeと比べるとチープなので随分前にOneDriveに移行している。しかしMSも万能ではないのでGoogleに委任して置かざるを得ない部分も少なからずあり、そこはGoogleを頼ることにしている。ただGoogleがしくじればMSに移行する準備はあるので、その日までといえばそうである。何故なら私がGoogleで使っているプロダクトは基本的にMS側にもあるからである。むしろ、その逆はないこともしばしばある。

IMEについてもGoogle IMEはカジュアルに使うには悪くないのだが、杓子定規に日本語を入力しようとすると上手くいかない。例えば「出刃包丁」を変換するときに「でばほうちょう」と打たないと変換できず、「でばぼうちょう」では変換できないと言うことが挙げられる。こんな打ち方をしていたら私の中であるべき日本語を忘れてしまう、日本人としてこれはいけないと思い、私はGoogle IMEを捨てることにした。

次使うIMEは連濁促音化のような日本語を扱えるのが良いと思い、MS-IMEなら叶えてくれるのではないかと思ったところ、これが見事当たった。Microsoftは日本人のための開発をしてくれていると痛感したのだ。Google IMEは言っては悪いが片手間だ。ATOKと勝負しているようなプロダクトでは到底ない。

検索エンジンもGoogleからBingに変えようとしたのだが、これは失敗した。まぁ元々MSNを検索エンジンとして使っていたことがないので、これは穏当といえる。理由としてはBingは文字列を本来の意味で解釈した結果を検索結果として出してくるからである。つまりキーワード検索ではない。

例えばAmazonの欲しいものリストで本名がバレてしまうことについて調べたいと思い「干し芋 本名」で検索してみたところ、Googleは期待通りの結果を返してくれるが、Bingは食べ物の干し芋についての結果を返してくる。他にもBingは検索結果にAIの提案みたいなのが滲み出てきていて少々嫌というのもある(この結果もUserCSSでノイズをかなり消している)

Googleの検索結果 Bingの検索結果
Googleの検索結果には意図した結果が出ている
Bingの検索結果はとても純粋で、全く意図しない結果になってしまっている

Googleの検索結果はSEO業者やいかがでしたブログのような低湿な情報も多々あるのだが、個人的にはその辺りを回避する術を身に着けているため、余り問題になることがなく、検索エンジンまでは変えなくてもよいかと思ったのである。

ただGoogleはうちのブログをあんまりクロールしてくれないのでインデックスが遅いので、そこは全く評価していない。そこら辺のアフィサイトより確実に良質の記事を書いている自信はあるのだが…。いや、メモ書きがメインなので中身がスカスカの記事も多くあるし、そこは認めているが…。

インデックスについてはBingはかなり早く、翌週にはインデックスされていることもザラである。深く確認してないが仕組み的には翌日にでもインデックスされているかもしれない。反してGoogleは翌月でもインデックスされていないことがしばしばなので、ここについてはあまり評価していない。手動でインデックスリクエストすることもしばしばである。とはいえ、Bingも以前はうちのブログをインデックスしてくれなかったことがあり、その時は全く評価していなかった。MSにクレームを出してもインデックスされなかったのだが一年放置していたら何故かインデックスされていた。謎。

最後に余談だが、私の利用ブラウザの変遷について振り返ってみたいと思う。といっても記憶の限りなので正確性は怪しいところだが、Microsoft Edgeより以前は記憶が確かであれば、Netscape Navigator→Internet Explorer→Slepnir 1.66→Sleipnir 2→Sleipnir 3→Firefox→Google Chromeという感じだ。IEは恐らく4-6辺りを使っていたのではないかと思うのだが、当時はバージョンを意識していなかったので不明だ。最初のブラウザがネスケなのは親のPCの標準ブラウザだったからである。Sleipnirが3で止まっているのは開発方針に疑問を感じたからだった気がする。Geckoエンジンのサポート終了でFirefoxに乗り換えたものの、UIや設定や複雑怪奇な上に拡張機能が競合してカオスすぎたのでシンプルだったChromeに移った記憶がある。

上記の他にも古い端末向けにレンダリングエンジンがPresto時代のOperaを使っていたり、比較検討用にLunascapeやSRWare Ironを使っていたこともあるし、Tor Browserも使ったこともある。仕事の都合でSafariを使う事もなくはないが、宗教上の理由もあり、滅多に使わない。Presto時代のOperaはIEが通用しなくなった時代にWindows XP世代の骨董品マシンでWebブラウズするのには省メモリで大変重宝した記憶がある。因みに私は2004年に発売されたVAIO PCG-FR77J/Bを2017年まで使い続けていたことがある。これはネトゲで3PCをするためのパーツとして使っていた。

話は反れるが過去に使っていた端末はこんな感じである。NEC PC-9801DX2→FMV-BIBLO NE/33→SONY VAIO PCG-FR77J/B→東芝 DYNABOOK 型番失念→ASUS X550VC→ツクモBTO→自作PC

モダンからレガシーへ、ということで、これは前書いた記事の第二弾のようなものだ。モダンに逆らってレガシーへ変えてみる挑戦だ。

というわけで今回は調理器具を更新した。具体的には軽くて曲げやすい便利なプラスチック製のまな板とセラミック包丁から木製のまな板と金属製の包丁に変えたのだ。

樹脂製のまな板と、セラミック製の包丁
木製のまな板と金属製の包丁

実はセラミック包丁は前々からやめたいと思っていた。理由は単純で硬いものに使えないからだ。硬そうなものを切ろうとしようと怯えながら切らないといけない。そんな怯えながら料理をしたくなかったのだ。日々の料理は楽しいものでありたい。バリエーションもあった方がいい。そういう時に使途が限定されるものでは困ったのが一つだ。

もう一つは金属包丁の特性を調べていくと、セラミック包丁にはない特性があることが分かったのもある。何かといえばセラミック包丁は金属包丁と比べた時に切れ味が悪いということだ。例えば和平フレイズ公式ブログではセラミック包丁の特性として以下の説明がある。

次にセラミックですが、陶器である為もろいというお話をしたかと思いますが、刃を薄く仕上げてしまうと簡単に欠けてしまうので、セラミックの刃はかなり鈍角に仕上げられています。

こういった特徴があるので、例えば臭い移りを気にされる場合や、金属アレルギーをお持ちの方がお使いになる際はセラミックがお勧めです。

ただ、刃が鋭くないので切れ味を求められるのならば金属系のものをおすすめします。

個人的には切れ味の方が欲しいので金属の方がいいかなと思った。

また、まな板に関しても金属包丁を使う場合は木製の方がいいということが、これまた和平フレイズ公式ブログで紹介されていた。

これまではペラペラのプラスチックのまな板を使っていたのだが、心機一転、木のまな板にしてみようという思いもあり、木のまな板にすることに決めた。

買って使ってみた結果だが、まず買ったばかりなので包丁の切れ味はいいし、まな板もヒノキの香りがして非常に気持ちがよい。それとプラスチックのまな板は厚みが出ると非常に重いのだが、木のまな板は軽いので、これもよい。

今までのまな板と比べると鍋に切ったものを入れるのに少し苦労するが、風情があるので今のところは気にならない。何より国産のヒノキを使ったまな板で、製造も三重県で行っているらしいので親近感もある。やはり日本人であるからには日本製を使っていきたいものだ。このまな板は有限会社ウメザワさんの製品だ。

ついでに包丁立ても買った。まな板と包丁をしまう場所がなかったからだ。コンパクトに包丁とまな板を収納できる一品で一人暮らしにはうってつけのアイテムだ。

コンパクトに包丁とまな板を収納できる包丁立て

これは元々ニトリの商品だったと思うのだが取り扱いがなかったのでコーナンで買った。ニトリの製品はOEMが多いので終売になったらホムセンに流れることが多く、何なら販売中の商品がホムセンにあることもざらなので、ここは知っておくと便利なチップスだ。モノによってはドンキにあったりもする。

因みに今までのまな板は吊戸棚にぶら下げていたのだが、この吊り下げ方ができるまな板は限られるため丁度よかった。この時の包丁はシンク下の戸棚に備え付けの包丁差しに刺していたが、刺しづらいのと閉じ切った場所でイマイチだったので、今回の変更は悪くなかった。

吊戸棚からぶら下がるまな板

買って一週間ほど経ち、多少の不便は感じるものの、今のところ概ね満足している。これからも可能な範囲で古き良き日本文化というものを大切にしていきたいものだ。