16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AtCoderやPaizaの問題をTDDでやる (TypeScript)

Last updated at Posted at 2019-05-12

趣旨

競技プログラミング(AtCoder, Paiza 等)を TDD でやる環境を作ります。
ここでは言語として TypeScript を使う場合を説明しますが、どの言語であってもやることは同じです。

入力値と出力値をすばやく取得する

Image from Gyazo

こういうやつです。一つの問題に対して大体2〜3セット用意されています。
地道に一つずつコピーしてテストコードに貼り付けていっても良いのですが、こういう作業は一瞬で終わらせたいですね。

なのでこのようなスクリプトを用意します。

// AtCoderの問題ページから入力値と出力値のJSONを取得するスクリプト
(function() {
  var selector = 'span.lang-en div.div-btn-copy + pre';
  var i = document.createElement('input');
  i.value = JSON.stringify(Array.from(document.querySelectorAll(selector)).map(e => e.innerText).reduce((acc, v, i) => { let j = Math.floor(i / 2); if (!acc[j]) acc[j] = []; acc[j].push(v.trim().replace(/\n/g, '\\n')); return acc }, []));
  document.body.append(i); document.querySelector('body > input:last-child').select();
  if (document.execCommand('copy')) { document.querySelector('body > input:last-child').remove(); alert('copied to clipboard.\n' + i.value); }
})();

これを例えば https://atcoder.jp/contests/abc086/tasks/arc089_a のような AtCoder の問題ページで実行すると、

Image from Gyazo

このようなアラートが表示され、

[["2\\n3 1 2\\n6 1 1","Yes"],["1\\n2 100 100","No"],["2\\n5 1 1\\n100 1 1","No"]]

上記のような JSON が得られます。
この値はクリップボードにコピーされます。

セレクターの指定( var selector = 'span.lang-en div.div-btn-copy + pre'; )はサイトによって変わりますので、自分が使っているサイト用に調整しましょう。

テストコードに貼り付ける

そして、下記のようなテストコードの変数 json の部分に値を貼り付けます。

describe('ABC086', () => {
  describe('C', () => {
    const json = `
    [["2\\n3 1 2\\n6 1 1","Yes"],["1\\n2 100 100","No"],["2\\n5 1 1\\n100 1 1","No"]]
    `;
    const params = JSON.parse(json);
    params.forEach(([input, output], i) => {
      it(`Test params[${i}]`, () => {
        expect(solve(input.split('\n'))).toBe(output); // solve はインポートしている。
      });
    });
  });
});

変数 params入力値と出力値の文字列を格納した要素数2の配列を格納する要素数nの配列 となるようにパースします。
JavaScript なら JSON.parse() とするだけです。

また、solve() は引数として string[] を受け取り、戻り値として string を返す関数です。
どの言語を使う場合でも、関数 solve() の引数と戻り値の型は統一しておくのがコツです。

僕が使っている実際のコード (抜粋)

テストコード

テストのライブラリは Jest を使っています。

abc-086.spec.ts
import solveABC086C from 'src/atcoder/abc/abc-086-c';

jest.setTimeout(1000 * 2);

describe('ABC086', () => {
  describe('D', () => { .... }); 

  describe('C', () => {
    const json = `
    [["2\\n3 1 2\\n6 1 1","Yes"],["1\\n2 100 100","No"],["2\\n5 1 1\\n100 1 1","No"]]
    `;
    const params = JSON.parse(json);
    const customParams = [];
    runTest(solveABC086C, params, customParams);
  });

  describe('B', () => { .... });
  describe('A', () => { .... }); 
});

function runTest(solver, params, customParams = []) {
  const test = param => {
    const [i, o] = param;
    expect(solver(i.split('\n'))).toBe(o);
  };
  params.forEach((p, index) => {
    it(`Test params[${index}]`, () => test(p));
  });
  customParams.forEach((p, index) => {
    it(`Test customParams[${index}]`, () => test(p));
  });
}

提出用コード

solve() だけ export してテストします。

abc-086-c.ts
export default solve;

// main
function main(lines) {
  console.log(solve(lines));
}

// solver
function solve(lines: string[]): string {
  const { N, destinations } = parseLines(lines);
  /* 省略 */
  return canArrive ? 'Yes' : 'No';
}

// helpers
function parseLines(lines: string[]) {
  const N = +lines[0];
  const destinations = lines.slice(1, 1 + N).map(line => line.split(' ').map(s => +s));
  const obj = { N, destinations };
  debug(obj);
  return obj;
}

// general helpers
function debug(message?: any, ...optionalParams: any[]) {
  if (!!process.env.LOCAL_DEBUG) console.log(message, ...optionalParams);
}

// reader
if (!process.env.LOCAL_DEBUG) {
  const { stdin: input, stdout: output } = process;
  input.resume();
  input.setEncoding('utf8');
  const lines = [];
  require('readline')
    .createInterface({ input, output })
    .on('line', line => lines.push(line))
    .on('close', () => main(lines));
}

あとは export LOCAL_DEBUG=1 && npx jest atcoder/abc/abc-086.spec --watch のようなコマンドをターミナルで実行しておけば、コードを保存する度にテストが実行されます。

AtCoder なら TypeScript のコードをそのまま提出できますが、Paiza のような JavaScript のみ受け付けるようなサイトの場合は、トランスパイルした JS のファイルを提出することになります。
そのため、テストの watch をしているターミナルとは別に、トランスパイルを watch するターミナルも必要です。

ちなみに実行環境は下記のようです。(2019年5月13日調べ)

  • AtCoder ... { node: 'v5.12.0', npm: '3.8.6' }
  • Paiza ... { node: 'v10.13.0', npm: '6.4.1' }

それでは良い TDD ライフを。

16
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?