※スマホ対応はしてません。

CIで試験回すと便利だよ。(Frog Advent Calendar 2018 – Adventar – 08日目)

カテゴリー: Web

Frog Advent Calendar 2018 – Adventar – 08日目

自身で、または職場で使っている技術・Framework・ツールを教えてください!

というわけで、本日は主に趣味で使ってる試験や継続開発の方面のツールの紹介をします。

実際に使ってる例はこれです。

話すこと

おれが使ってるツールの紹介と使い方の例。

話さないこと

良い試験の書き方、他の類似ツールとの比較。

概要

  • Mocha … 試験を実行
  • Chai … 試験を記述
  • ESLint … 文法や記述の揺れを確認
  • GitHub … コード保管
  • TravisCI … 試験他を自動実行
  • Greenkeeper … 依存パッケージの自動更新提案

Mocha

試験を実行してくれるツールです。

Mochaの実行例。

コマンドラインから実行します。

$ npx mocha tests/**/*.test.js

これを package.json の scripts.test に書いておく感じです。

ESLint

試験実行時に describe と it が global へ追加されます。 (ので node foo.test.js はだめ。)

require しなくてよいのは楽なんだけど、ESLintでエラーが出るはず。

[eslint] 'describe' is not defined. [no-undef]

ESLintの設定で mocha: true するとこれらが登録されます。

またESLintは設定ファイルの、何と言うんだろ、カスケーディング?ができます。これを利用して、主となるものとは別に、試験スクリプト用のディレクトリに test/.eslintrc.js を置くと、試験スクリプトは専用の設定で上書きできます。

module.exports = {
    "env": {
        "mocha": true,
    },
};

Chai

実際の試験を記述するライブラリーです。

いくつか書き方ができるんだけど、自分は expect().to.be. のBDDスタイルです。

const { expect } = require('chai');
const IndexedArray = require('../../lib/IndexedArray.js');

describe('IndexedArray', () => {
  describe('constructor', () => {
    it('creates empty array', () => {
      const db = new IndexedArray();
      expect(db.length).to.be.eql(0);
    });
  });
});

いやなところ

getterみたいになってる検証があって、メソッド呼び出しじゃないけど処理が実行される。

expect(result).to.be.false;

false() ではない。

これがどうも気持ち悪い……。 というのもあって最近はJestに気持ちが傾きつつある。

ESLint

使ってない変数とか改行の位置とか、そういうのを確認してくれるやつ。

自動修正機能もあって、エディター(VS Code)と連携してファイル保存のたびに細かいところを直してくれたりもします。

すごい便利!

TypeScript

にはTSLintというのがあります。

GitHub

もちろんですとも。

Travis CI

さあここからが本番だぞ。

CIサービスです。要は git push したら自動的に試験実行してくれる。

すごい便利! しかも基本無料です。

使い方

  1. アカウント作成
  2. リポジトリ連携
  3. 設定ファイル作成
  4. git push
  5. 結果を見守る
    • 失敗するとメールが飛んでくる

設定ファイル

プロジェクトのルートディレクトリに .travis.yml を置きます。

language: node_js
node_js:
  - "8"
  - "10"
script:
  - npm run lint
  - npm run test-travis

結果

実際に動いてる結果画面がこちら。

バッジとしてREADMEに画像を置いたりもできます。 (試験失敗するとバレるね。)

READMEのバッジ例。左側がTravisCIのもの。

GitHubと連携

コミットのところにチェックマークやらが付いてるのを見ることがあると思うんだけど、それがこれです。

このコミットは試験を失敗させない。

PRのところにも出てくる。

PRが作成、更新されると試験も自動で実行され、結果がPRのページに表示される。これはCircleCIの例。

デプロイも自動化

できるはず。まだやったことない。

Greenkeeper

package.json の内容を読んで、依存パッケージに更新があればPull Requestを作ってくれます。同時にCIの試験結果も見てくれる。

すごい便利!

実際に自動で用意されたPRの例。

PRの変更を見ると package.json が更新されている。

問題なければそのままマージするだけ。らくちん~。

その他

特に試験とかそういうんじゃなしに。

VS Code

ずっとVimだったけど最近はもっぱらこちらへ。

良いVimエミュレーターのプラグインがあれば教えてほしい……。

Windows Subsystem for Linux (WSL)

Windows 10の中でLinuxを動かす技術。

cmdじゃなくてshが動く! 隣席のカナダ人(Windows利用)に教えたら「Gamechangerだ!」といたく感動されました。

ところどころ動かない罠的なところもあるけれど、それでもなお使わない手はないぜって感じです。

EditorConfig

エディターの、インデントや改行コードなんかの設定を自動的に設定してくれるやつ。

Linterでもいいかもしれないけど。

Code Climate

コードの品質を計測してくれる。

最近見かけて使い始めたところ。 気付かない重複個所も指摘してくれて、あーこれまとめとこーみたいになる。

おしまい

ちゃんと試験を書くのが前提だけど、CI系はすごい便利。

最初に一度用意すれば何もしなくても勝手に結果を教えてくれたりとか、コピペで他のプロジェクトへ適用したりもできます。ちょっとやってみて損はないですよ。

興味ある方はおれに聞いてみてください。何なら知ってる限りのことはお教えします。

逆に詳しい方はおれに教えてください。お願いしましたよ。

Frogをよろしく

技術系の留学エージェントです。セナさんの。

昨日はbg_kさんがバンクーバーの勤務先で使ってるツールと就活経験のお話でした。

明日はユウヘイさんの、ハードウェアの開発環境のお話だそうです。

それでは良いお歳を!

配列は継承できるから、IDでランダムアクセスできるやつを自作できるかも。(配列とかおれおれAdvent Calendar2018 – 08日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の8日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 08日目

配列はサブクラス化可能なように設計されています。 必要に応じて extends Array で継承して、独自の拡張配列クラスを用意することができます。

Arrayを継承したクラスの例

class SugoiArray extends Array {
  // ランダムに要素を返す
  random() {
    return this[Math.floor(Math.random() * this.length)];
  }

  // 空にする
  empty() {
    this.length = 0;
  }
}

const arr = new SugoiArray(10, 20, 30);
console.log(arr); // => [ 10, 20, 30 ]

console.log(arr.random());
console.log(arr.random());
console.log(arr.random());
console.log(arr.random());
console.log(arr.random());
console.log(arr.random());

arr.empty();
console.log(arr); // => []

仕様書の記載

ES 2018の仕様書の項目22.1.1The Array Constructorにそう記載があります。

The Array constructor … is designed to be subclassable.

たまたま動くだけじゃないんだよー。

各種メソッド

コンストラクターのメソッドもプロトタイプのメソッドも、全て this の種類に依存しない、汎用関数として設計されています。

例えば concat() の仕様。

Note 2

The concat function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

ノート2

この concat 関数は、汎用であるよう意図されており、 this の値が配列オブジェクトであることを必要としません。従って、他の種類のオブジェクトへ移してメソッドとして利用することが可能です。

これとほぼ同じ文章が、全てのメソッドに記載されています。

これはいける!

例

ちなみに concat() その他のメソッドは Array.prototype.concat のようにして、関数オブジェクトとして配列コンストラクターのプロトタイプに設定されています。

コンストラクターメソッドは Array.of とか。そのまんま。

任意のオブジェクトで使う

関数オブジェクトのメソッド call() や apply() を使って、任意のオブジェクトを this に設定して関数実行してやることができます。

const arrayLike = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
};

Array.prototype.forEach.call(arrayLike, (value) => {
  console.log(value);
});

apply() の使い方はこんなん。(これは call() だけど。)

2011年てまじか。

任意のオブジェクトのメソッドにする

オブジェクトが決まっているなら、関数プロパティつまりメソッドとして設定してやるとなお素直な実装ができます。

const arrayLike = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
  forEach: Array.prototype.forEach,
};

arrayLike.forEach((value) => {
  console.log(value);
});

DOM APIの NodeList ( querySelectorAll() の結果)もこの方式です。

console.log(NodeList.prototype.forEach === Array.prototype.forEach);
// => true

任意のクラスのインスタンスメソッドにする

prototype に代入するしかないですかね、今は。

ArrayLike.prototype.forEach = Array.prototype.forEach;

TypeScriptならプロパティを書ける。 ひどい例だけどこれで大丈夫かな。

// TypeScript
class ArrayLike {
  public forEach = Array.prototype.forEach;

  public get length () {
    return 3;
  }

  constructor () {
    this[0] = 11;
    this[1] = 22;
    this[2] = 33;
  }
}

const arrayLike = new ArrayLike();
arrayLike.forEach((value) => {
  console.log(value);
});

IDでランダムアクセスできちゃう配列

夢のようなやつ。

class IndexedArray extends Array {
  constructor (...items) {
    super(...items);

    this._index = new Map();
    this._remap();
  }

  assertItem (item) {
    if (!item || !item.id || typeof item.id !== 'string') {
      console.warn(item);
      throw new Error('Item must have a string ID');
    }
  }

  add (item) {
    this.assertItem(item);

    // overwrite
    if (this._index.has(item.id)) {
      const index = this._index.get(item.id);
      this.splice(index, 1, item);
    }
    // add
    else {
      this._index.set(item.id, this.length);
      super.push(item);
    }
  }

  get (id) {
    const index = this._index.get(id);
    return this[index];
  }

  delete (id) {
    const index = this._index.get(id);
    if (index < 0) {
      return false;
    }

    this._index.delete(id);
    this.splice(index, 1);

    this._remap();

    return true;
  }

  _remap () {
    this.forEach((item, index) => {
      this.assertItem(item);
      this._index.set(item.id, index);
    });
  }
}
const db = new IndexedArray(
  { id: '11', name: 'Alice' },
  { id: '22', name: 'Bob' },
  { id: '33', name: 'Charlie' },
);

console.log(db);
console.log(db[0]); // => { id: '11', name: 'Alice' }
console.log(db.get('11')); // => { id: '11', name: 'Alice' }

console.log('Add Diana');
db.add({ id: '44', name: 'Diana' });
console.log(db[3]);
console.log(db.get('44'));

console.log('Diana was a kong!');
db.add({ id: '44', name: 'Donkey Kong' });
console.log(db[3]);
console.log(db.get('44'));

console.log('Delete Bob');
db.delete('22');
console.log(db);
console.log(db[1]);
console.log(db.get('33'));

といっても、本当はもっと push() とかを上書きしてやらないといけない。あと Array(3) みたいな入力への対処とか。( Array.of() の件。) いっそ Array を継承しない方が楽そう。

初期化子 [] は元のArrayのまま

自作コンストラクターで window.Array や global.Array を置き換えたとしても、配列初期化子 [] で生成されるのは元の Array のインスタンスになります。

これは初期化子 [] から生成する際に固有オブジェクト %ArrayPrototype% を利用するためです。

昔はそうなってなくて、そこから攻撃したりもできたらしい。といってもJavaScript 1.5なんて時代だそうだけど。

おしまい

関連

参考

配列の要素挿入、置き換えもsplice()で。Vue.jsでも大丈夫。(配列とかおれおれAdvent Calendar2018 – 07日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の7日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 07日目

削除を splice() でやれました。

同じく挿入も splice() を使ってやれます。

const arr = [11, 22, 33];

const index = 1;
const value = 99;
arr.splice(index, 0, value);
console.log(arr); // => [ 11, 99, 22, 33 ]

さらに置換もできます。

const arr = [11, 22, 33];

const index = 1;
const value = 99;
arr.splice(index, 1, value);
console.log(arr); // =>[ 11, 99, 33 ]

第2引数を 0 にするとただ挿入するだけ、 1 だと置き換えます。

仕様

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

昨日書いた通りだけど、 item1, item2, ... を挿入します。 複数可能。

Vue.jsで配列を置き換え

普通に置き換えたい場合は arr[i] = obj すれば十分なんだけど、Vue.jsの data で配列を持っている場合、それは「やってはいけないこと」です。

splice() でなら、それができます。

動かない例

ボタンを押すと配列要素のひとつを別のオブジェクトに置き換えるようにしました。

const index = 1;
const item = { text: "REPLACED", done: false };

this.todos[index] = item;

置換しても、次の再描画まで反映されない。

動く例

続いて splice() を使った、動くコード。

const index = 1;
const item = { text: "REPLACED", done: false };

this.todos.splice(index, 1, item);

置換するとすぐ反映される。

詳しくはVue.jsの公式文書をご確認くださいまし。

その他の変更

配列の要素全体じゃなくて要素のプロパティを変えるのはセーフ。

あと配列全体を代入しちゃうのもセーフ。

順序を変えるなら sort()

splice() で置き換えても良いけど、よっぽどの量でなければ sort() してできあがる配列全体をプロパティに設定してやるのが簡単で良いでしょう。

例

話をVue.jsからJavaScript全体に戻します。

配列を追加

追加する要素は第3引数だけでなく、第4、第5といくらでも与えられます。

配列をまるごと追加する場合、スプレッド構文 ... で展開してやりましょう。

const arr = [11, 22, 33];
const values = [99, 88];

const index = 1;
arr.splice(index, 0, ...values);
console.log(arr); // => [ 11, 99, 88, 22, 33 ]

配列をそのまま置くと、その配列自体が全体の配列の要素になっちゃいます。二重配列だ。

検索して置き換え

findIndex() と組み合わせます。

const users = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];
const value = { id: '999', name: 'Zack' };

const id = '102';
const index = users.findIndex((v) => v.id === id);

users.splice(index, 1, value);
console.log(users);
// => [ { id: '101', name: 'Alice' },
//      { id: '999', name: 'Zack' },
//      { id: '103', name: 'Charlie' } ]

その他のメソッド

普通に置き換えるなら [index]

でいいよね。

末尾に追加するなら push()

これはこれでよく使う。

末尾に複数追加するなら concat()

ただし非破壊で、新しい配列を作ります。

const arr = [11, 22, 33];
const values = [99, 88];

const arr2 = arr.concat(values);
console.log(arr); // => [ 11, 22, 33 ]
console.log(arr2); // => [ 11, 22, 33, 99, 88 ]

values.concat(arr) にすれば先頭に追加したものが出てきます。

先頭に追加するなら unshift()

各要素のプロパティを置き換えるなら forEach()

うーん何というか、繰り返しながら更新するだけ。

const users = [
  { id: '101', name: 'Alice', active: true },
  { id: '102', name: 'Bob', active: true },
  { id: '103', name: 'Charlie', active: true },
];

users.forEach((user) => {
  user.active = false;
});

おしまい

関連

参考

配列の要素削除はsplice()で。(配列とかおれおれAdvent Calendar2018 – 06日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の6日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 06日目

splice() を使ってやれます。

const arr = [11, 22, 33, 44, 55];

const index = 2;
console.log(arr[index]); // => 33
arr.splice(index, 1);
console.log(arr); // => [ 11, 22, 44, 55 ]

第2引数 1 を忘れるとごそっと消え去るので気を付けてください。

仕様

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

対象の配列は破壊的に変更されます。

引数

  • start … 削除開始する位置。先頭は 0 。負数の場合、後ろから数える
  • deleteCount … 削除する数。初期値は array.length - start
  • item1, item2, ... … 削除個所に挿入する要素

戻り値

削除された要素の配列。削除数 deleteCount が 1 や 0 の場合も配列。

例

要素を探して削除

findIndex() でインデックスを検索、それを削除します。

const users = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];

const id = '102';
const index = users.findIndex((v) => v.id === id);
const removedUser = users.splice(index, 1);
console.log(users);
// => [ { id: '101', name: 'Alice' }, { id: '103', name: 'Charlie' } ]
console.log(removedUser);
// => [ { id: '102', name: 'Bob' } ]

合致するものを全て削除

地道に繰り返してやります。まあ処理は一緒。

const users = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];

const ids = ['101', '103'];
ids.forEach((id) => {
  const index = users.findIndex((v) => v.id === id);
  users.splice(index, 1);
});
console.log(users); // => [ { id: '102', name: 'Bob' } ]

別インスタンスで良ければ filter() にしよう。

その他のメソッド

新しい配列を生成するなら filter()

splice() は破壊的に取り除くけど、 filter() なら非破壊で新しい配列を作ってくれます。

さっきの例を filter() でやる版。


const users = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];

const id = '102';
const newList = users.filter((v) => v.id !== id);

console.log(users.length); // => 3

console.log(newList.length); // => 2
console.log(newList);
// [ { id: '101', name: 'Alice' }, { id: '103', name: 'Charlie' } ]

一部を取り出すなら slice()

名前似てるけど違う機能。

こいつも非破壊で、別インスタンスを作ります。

const arr = [11, 22, 33, 44, 55];

const arr2 = arr.slice(1, 3);
console.log(arr2); // => [ 22, 33 ]

第2引数が抜き出す数じゃなくて抜き出し終わりの位置だってことに注意。

全部消すなら length = 0

arr.length = 0;

わあかんたん。

その他

空要素に対応

undefined すら入ってないような配列でも安心してお使い頂けます。やるじゃん。

const arr = Array(5);
console.log(arr); // => [ <5 empty items> ]

const removed = arr.splice(1, 2);
console.log(arr); // => [ <3 empty items> ]
console.log(removed); // => [ <2 empty items> ]

Vue.jsで使える

Vue.jsがサポートする配列の変更メソッドに含まれております。 良かったね。

おまけ: 英単語 splice

あんまり耳馴染みないと思うんだけど、2つのものを「継ぎ合わせる」という意味だそうです。

解いて組み継ぎする、より継ぎする、継ぎ合わせる、(…に)継ぐ、重ね継ぐ、結婚させる、接合する

Googleで画像検索すると理解しやすそう。くるっと結ぶだけじゃなくて完全に一体化しちゃう感じかな。

この動画わかりやすい。

Short Splice Knot | How to Tie a Short Splice Knot – YouTube

映画 “Splice”

splice だけで検索すると2009年の映画のやつが出てくる。(人と地域によるかも。)

こんなあらすじらしい。

Genetic engineers Clive Nicoli and Elsa Kast hope to achieve fame by successfully splicing together the DNA of different animals to create new hybrid animals for medical use.

遺伝子工学者のClive NicoliとElsa Kastは、異なる動物のDNAを継ぎ合わせ (splicing together)、医療目的の新しい混成動物を創造することで歴史に名を刻むことを望んでいた。

YouTubeにトレーラーがあるけど、ホラーSFっぽい。

遺伝子工学方面ではスプライシングって普通に言う?

DNAから写しとった遺伝情報のなかから,不要な部分を取り除く分子的な編集作業をスプライシングという。

おしまい

英単語的には挿入する方が主たる意味になるのかな。

関連

参考

配列風オブジェクトをArray.from()で本物の配列へ変換。(配列とかおれおれAdvent Calendar2018 – 05日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の5日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 05日目

配列風オブジェクトから Array インスタンスを生成します。

const nodeList = document.querySelectorAll('.target');
const arr = Array.from(nodeList);
console.log(arr);

利用可能なのは以下の2種類です。

  • 配列風オブジェクト
  • 反復可能なオブジェクト

あと第2引数に関数を受け付けて、 map() みたいなマッピングが可能です。

配列風オブジェクト

length を持ってて [0], [1], [2], … で要素にアクセスできるやつ。

const arrayLike = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
};
const arr = Array.from(arrayLike);
console.log(arr); // => [ 11, 22, 33 ]

反復可能なオブジェクト

反復可能とは、ざっくり言うと for-of で使えるiterableなやつ。正確なところは別の記事に分けます。

配列

実は配列自体も反復可能なのです。特別扱いされているんじゃなくて、そういう共通処理が使える。

なおshallow copyなので、要素は同じです。

const arr0 = [
  { value: 11 },
  { value: 22 },
  { value: 33 },
];
const arr1 = Array.from(arr0);

console.log(arr0 === arr1); // => false
console.log(arr0[0] === arr1[0]); // => true

Map, Map

こいつらも反復可能なので使えます。

const set = new Set();
set.add(11);
set.add(22);
set.add(11); // 重複
set.add(33);
const arr = Array.from(set);
console.log(arr); // => [ 11, 22, 33 ]

文字列

配列風オブジェクトの範疇ですが、実は本当に反復可能なオブジェクトです。

const arr = Array.from('Hello World!');
console.log(arr);
// => [ 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!' ]

NodeList

DOMのやつ。反復可能です。

const els = document.querySelectorAll('.target');
const arr = Array.from(els);
console.log(arr);

HTMLCollection ?

document.links みたいなやつらもそうっぽい。

const els = document.getElementsByClassName('foo');
const arr = Array.from(els);
console.log(arr);

Chrome, Firefox, Edgeでは動いたんだけど、これどこの仕様かわからない……。

jQuery

ところでjQueryもiterableなんですね。私、知らなかったわ。

というわけで使えます。

マッピング

第2引数に関数を受け付けて、マッピングしながら作成できます。

const nodeList = document.querySelectorAll('input[type=text]');
const textList = Array.from(nodeList, (el) => el.value);
console.log(textList);

任意の数の配列を作る

MDNに載ってたやり方。配列風オブジェクトの応用でやれます。

const arr = Array.from({ length: 3 });
console.log(arr); // => [ undefined, undefined, undefined ]
console.log(arr.length); // => 3

new Array(3) ↓と違い、ちゃんと undefined が設定されていることに注目してください。

const arr = Array(3);
console.log(arr); // => [ <3 empty items> ]
console.log(arr.length); // => 3

マッピングと組み合わせて初期値を設定するのも良さそう。

const arr = Array.from({ length: 3 }, (_, i) => i);
console.log(arr); // => [ 0, 1, 2 ]

その他

継承

Array.of() ↓もそうだったんだけど、 Array.from() の方も継承したコンストラクターから利用かのうなよう設計されています。

おしまい

スプレッド構文 ... の方が楽な気がする。

これは反復可能であれば使えます。配列風はだめなので、今回の Array.from() で。

関連

参考