趣旨
競技プログラミング(AtCoder, Paiza 等)を TDD でやる環境を作ります。
ここでは言語として TypeScript を使う場合を説明しますが、どの言語であってもやることは同じです。
入力値と出力値をすばやく取得する
こういうやつです。一つの問題に対して大体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 の問題ページで実行すると、
このようなアラートが表示され、
[["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 を使っています。
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
してテストします。
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 ライフを。