Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

テストコード #15

Closed
9 tasks done
stakiran opened this issue Dec 12, 2020 · 104 comments
Closed
9 tasks done

テストコード #15

stakiran opened this issue Dec 12, 2020 · 104 comments

Comments

@stakiran
Copy link
Member

stakiran commented Dec 12, 2020

lint にせよ refactoring にせよ、ガッと修正した後でも動くことをさっと確認したい。

サマリー

  • テストコード書くために、thenable なコードになっている必要があった(が全然なってなかった)
  • 数日かけてガッツリやった
  • mocha によるテストコードの仕組み自体はつくってある
  • 続きは テストコード2 #20

task

  • テストコード実行できるようにする
  • mocha で非同期テスト書けるようにする
  • こっから https://github.com/tritask/tritask-vscode/tree/testcode で一通りやる
  • add でワンパス通してみる
  • ==== テストコード書けないので thenable base に修正 ====
  • 1: 各関数および CursorMover を thenablePromise<boolean> で return させる
  • Promise せざるをえない場所の対処
    • 🐰 気の所為ならそれでいい(Thenableベースの途中でPromiseになるからテストコードダメかもという懸念 Promise に統一する。then() の中で resolve することで変換する
    • edit().then(){ この中で return false or true する場合}
    • endTask で、edit する前に end 不要で return する場合
  • 2: doSave に依存しているやつは全部 async funtion にして、await doSave() で待ち合わせる
  • (ここができたら await 対応ワンパス)
  • 3: copyTask まわりを await で待ち合わせる
  • ……
@stakiran
Copy link
Member Author

どうやるんだろうなぁ。

やってることが基本的に

  • helper.py 呼び出して trita ファイルいじらせる
  • vscode.window.activeTextEditor 使ってテキストやカーソルを取得したり動かしたり

で、unittest 書きづらいんだよなぁ。

vscode.window.activeTextEditor にモック差し込んだりとかできたりする?

@stakiran
Copy link
Member Author

いや、普通に「VSCode ウィンドウ新たに立ち上げて、a.trita 開いて、所定の操作させて、最後にカーソル位置やテキストの内容をgetしてassertする

↑ これできる気がする

@stakiran
Copy link
Member Author

テストコード実行するたびにいちいちウィンドウ新たに立ち上がる感じ(GUIテスト)になるけど、それでも手作業よりははるかに良い。

@stakiran
Copy link
Member Author

書くとしたら

日付が変わると並び順が変わるので、テスト時に毎回ゼロからデータをつくるようにする

  • add する
  • (addの結果をテストする)
  • copy する
  • (copyの結果をテストする)
  • ……
  • sort する
  • (sortの結果をテストする)
  • カーソル移動系操作1
  • (カーソル移動系操作1の結果をテストする)
  • ……

@stakiran
Copy link
Member Author

stakiran commented Dec 13, 2020

mocha 思い出す

mocha における describe it before beforeEach after afterEach の実行順序 - Qiita

テストコードは既に yo code のおかげで実行できるようになってる

  • src\test\suite\extension.test.ts これが実行されるようになってる
    • 正式には glob('**/**.test.js' なので、src 配下に *.test.js 置いたら全部実行される
  • launch の Extension Tests を実行すれば良い
  • 実行すると
    • 1: vscode window 新しく立ち上がる
    • 2: テストコード走る
    • 3: デバッグコンソールに結果出る
    • 4: テスト終わったら window もばいちゃ

少しおためし

import * as assert from 'assert';
import { describe, before, after, beforeEach, afterEach, it } from 'mocha';

import * as vscode from 'vscode';

// import * as myExtension from '../extension';

suite('Extension Test Suite', () => {
	before(() => {
		vscode.window.showInformationMessage('Start all tests.');
	});

	test('Sample test', () => {
		assert.strictEqual(-1, [1, 2, 3].indexOf(5));
		assert.strictEqual(-1, [1, 2, 3].indexOf(0));
	});
});

describe('describe1', () => {
	before(() => {
		console.log('before')
	});

	after(() => {
		console.log('after')
	});

	beforeEach(() => {
		console.log('before')
	});

	afterEach(() => {
		console.log('after')
	});

	it('test1', () => {
		assert.strictEqual(1, 100-98-1);
	});

	it('test2', () => {
		assert.strictEqual(2, 100-98);
	});

});

describe('describe2', () => {
	it('test3', () => {
		assert.strictEqual(1, 100-98-1);
	});
});
  Extension Test Suite
    √ Sample test
  describe1
2
before
    √ test1
after
before
    √ test2
2
after
  describe2
    √ test3
  4 passing (79ms)

@stakiran
Copy link
Member Author

suite と test って何?describe や it じゃダメなの?

@stakiran
Copy link
Member Author

Testing Extensions | Visual Studio Code Extension API

記述なさそう。たぶん単に「suite がスイート表してて、テストケースは test が使えるよ(mochaデフォの describe と it はわかりづらいけどこれならわかりやすいやろ?)」ってことかな

@stakiran
Copy link
Member Author

うん、suite は describe の、test は it のシンタックスシュガーっぽい

@stakiran
Copy link
Member Author

chaiは?

まあ assert あるし、いったんこれでいいか

@stakiran
Copy link
Member Author

テストコードから test.trita(空ファイル) を開くには?

@stakiran
Copy link
Member Author

VS Code API | Visual Studio Code Extension API

vscode.window.openNewFile() ← こんなんがあるはず

@stakiran
Copy link
Member Author

開いたかどうか見れないんだが。

function pause(){
	const commandLine = "pause"
	exec(commandLine, (err) => {
		if(err){
			console.log(err);
		}
	});
}

function sleep(msec: number){
	setTimeout(() => {}, msec)
}

pause でも setTimeout でも構わず終了しやがる

@stakiran
Copy link
Member Author

あとかれんとでぃれくとりもわからん。

		console.log(vscode.workspace.name)
		console.log(vscode.workspace.workspaceFolders)

undefined だし。まず workspace folder を開く必要がある?

@stakiran
Copy link
Member Author

https://code.visualstudio.com/api/references/vscode-api#TextDocument

activeTextEditor.document.fileName で「untitled 1」まで取れた。

が、ディレクトリはないっぽいな。

今が src/test/suite/extension.test.ts なんだが、src を得るにはどうしたらいいんだ?

@stakiran
Copy link
Member Author

		console.log(vscode.workspace.name)
		console.log(vscode.workspace.workspaceFolders)
		console.log(IDE.getEditor().document.fileName)
		console.log(vscode.workspace.asRelativePath('123')) // workspace folder とやらがないから 123 がそのまま帰る

その workspace folder のパスが知りたいんやが

@stakiran
Copy link
Member Author

console.log(vscode.env.appRoot)

ちゃう。c:\Program Files\Microsoft VS Code\resources\app が出た。

……いや、実行時にここに各種ファイルがコピーされてくるから、ここ基点で進めればええってこと?

@stakiran
Copy link
Member Author

いや、違うね

@stakiran
Copy link
Member Author

今が src/test/suite/extension.test.ts なんだが、src を得るにはどうしたらいいんだ?

これが欲しい。src/test/suite/test.trita を開きたいんですけども……

@stakiran
Copy link
Member Author

		console.log(vscode.env.appRoot)
		console.log(vscode.env.shell)
		console.log(vscode.workspace.name)
		console.log(vscode.workspace.workspaceFile)
		console.log(vscode.workspace.workspaceFolders)
		console.log(IDE.getEditor().document.fileName)
		console.log(IDE.getEditor().document.uri.fsPath)
		console.log(vscode.workspace.asRelativePath('123'))

マジで正解どれなん……?

@stakiran
Copy link
Member Author

javascript - VS Code extension - get full path - Stack Overflow

ググってもこれしか出てこない。

その workspace folder が空なんですよ。

@stakiran
Copy link
Member Author

.../src/test/suite/extension.test.ts を実行してるんだから、.../src/test/suite/ を得る手段が絶対あるはずだと思うんだが

@stakiran
Copy link
Member Author

@stakiran
Copy link
Member Author

path さんでしたか

console.log(path.resolve(__dirname))

d:\work\github\stakiran_sub\tritask-vscode-PUBLIC\tritask-language-features\out\test\suite

@stakiran
Copy link
Member Author

だが out 側に test.trita などテスト用ファイルがない

@stakiran
Copy link
Member Author

@stakiran
Copy link
Member Author

やっとできた

image

@stakiran
Copy link
Member Author

import * as path from 'path'

class Path {
	static get _entrypoint(){
		// __dirname には現在実行中のソースコードが格納されているディレクトリ.
		// node.js の動作.
		// src/test/suite/test.ts の場合, out/test/suite になる
		return path.resolve(__dirname)
	}

	static get root(){
		const defaultRoot = this._entrypoint
		return path.resolve(defaultRoot, '..', '..', '..', 'src', 'test', 'suite')
	}
}

わりかししんどい。 Path.root にあたるパスを一発で取得できるシステム変数があってもばちあたらんと思うが

@stakiran
Copy link
Member Author

stakiran commented Dec 13, 2020

ソースコード extension.ts を import する方法がわからん……

import * as tritask from '../../../src/extension'

これだと型定義なくてダメ。

image

const tritask = require('../../../src/extension.ts')

これだと SyntaxError: Cannot use import statement outside a module

@stakiran
Copy link
Member Author

// import * as myExtension from '../extension';

yo code のテンプレはこれを出してる

@stakiran
Copy link
Member Author

stakiran commented Dec 13, 2020

ぐぐると「型が存在してない」的な内容がヒットする……

image

ちゃんと定義してんだけどなぁ。それとも <string>SELF_EXTENSION_ID= みたいにしてないから認識されてない?

@stakiran
Copy link
Member Author

stakiran commented Dec 13, 2020

😭 endTask 以降がマジで難しい

戻り値何も考えずにつくってるから、thenable で返さないといけないようにするのがパッと見てできん

@stakiran
Copy link
Member Author

	if(isNotStarted){
		return  Promise.resolve(false);
	}

Promise と Thenable は違うわけだが、Thenable はどうやって定義すればいいの?

……たぶん想定されてないんだよなぁ。

@stakiran
Copy link
Member Author

endTask

export function endTask(){
	// s  e  >DoEnd>  s  e
	// -------------------
	// x  x           x  x    Invalid. start してないので end しない.
	// o  x           o  o    OK. 普通に end する.
	// o  o           o  x    OK. トグルで end をやめる.
	// x  o           -  -    Don't care.
	//
	// x  x から x  o に至れないので x  o の時は想定していない.
	// 今のコードだと end しない挙動になるが, 特に問題はないのでこのままで.

	const editor = getEditor();
	const doc = editor.document;
	const currentLine = doc.lineAt(CursorPositioner.current()).text;

	const currentStartTimeValue = currentLine.substr(POS_STARTTIME, LEN_TIME);
	const isNotStarted = currentStartTimeValue == EMPTYTIME;
	if(isNotStarted){
		return;
	}

	const currentEndTimeValue = currentLine.substr(POS_ENDTIME, LEN_TIME);
	const isDoneNewly = currentEndTimeValue == EMPTYTIME;
	const shouldCopyBecauseRepeat = currentLine.indexOf("rep:") != -1;

	let afterStringForEndTime = EMPTYTIME;
	if(isDoneNewly){
		afterStringForEndTime = DateTimeUtil.nowtimeString();
	}
	const endTimeRange = CursorPositioner.rangeBetweenEndTime()
	const endTimeBuilder = function(editBuilder: vscode.TextEditorEdit): void{
		editBuilder.replace(endTimeRange, afterStringForEndTime);
	}	
	const endTimePromise = editor.edit(endTimeBuilder);

	const functionAfterCopy = () => {
		CursorMover.golineend();
		doRepeatIfPossible();
	}

	endTimePromise.then(
		(isSucceedEdit) => {
			if(isSucceedEdit && isDoneNewly && shouldCopyBecauseRepeat){
				copyTask(functionAfterCopy);
			}
		}
	)
}

@stakiran
Copy link
Member Author

  • isnotstarted で return している
  • editor.edit で endtime 記入
  • endtime 記入後の then で copyTask
    • copyTask に渡してる golineend と doRepeatIfPossible() も非同期

ぐちゃぐちゃすぎる……w

@stakiran
Copy link
Member Author

copyTask

function copyTask(cursorMoverAfterCopy: any){ // eslint-disable-line  @typescript-eslint/no-explicit-any
	const editor = getEditor();
	const doc = editor.document;
	const currentLine = doc.lineAt(CursorPositioner.current()).text;
	const inserteeString = `${currentLine}\n`;
	const inserteePos = CursorPositioner.linetop();

	// 挿入後は現在行位置に「挿入した行」が来るため
	// 「挿入前における現在行位置」をそのまま使うだけで
	// 挿入した行に対する操作を行える.
	//
	//               line[x-1]
	// aaaaaaaIaa    line[x]
	//               line[x+1]
	// 
	//               line[x-1]
	// aaaaaaaIaa    line[x]    <== 挿入された行. x行目で変わらずアクセスできる.
	// aaaaaaaIaa    line[x+1]
	const startpos = CursorPositioner.startOfTimeFields();
	const endpos = CursorPositioner.endOfTimeFields();
	const replaceeRange = new vscode.Range(startpos, endpos);
	const afterString = `${EMPTYTIME} ${EMPTYTIME}`;

	// insert: 現在行を複製する.
	// replace: 複製した行の開始/終了時刻をクリアする(複製後は todo task として扱いたいはず).
	const f = function(editBuilder: vscode.TextEditorEdit): void{
		editBuilder.insert(inserteePos, inserteeString);
		editBuilder.replace(replaceeRange, afterString);
	}
	const promise = editor.edit(f);
	promise.then(
		(isSucceedEdit) => {
			if(!isSucceedEdit){
				return;
			}
			if(cursorMoverAfterCopy === undefined){
				return;
			}
			cursorMoverAfterCopy();
		}
	)
}

@stakiran
Copy link
Member Author

  • edit で insert と replace している
  • edit 後の then で、呼び出し元から渡されたコールバックを実行している
    • コールバックがない or edit 失敗時 → return void
    • コールバックがある → それを呼ぶ(return void)

copyTask の呼び出し元は二つあって、

endTask さん

	const functionAfterCopy = () => {
		CursorMover.golineend();
		doRepeatIfPossible();
	}

copyTask のエントリポイント

	const task_copy = vscode.commands.registerCommand('tritask.task.copy', () => {
		const callbackAfterCopy = CursorMover.golineend;
		copyTask(callbackAfterCopy);
	});

@stakiran
Copy link
Member Author

stakiran commented Dec 13, 2020

CursorMover.golineend

class CursorMover {
	static golineend() {
		vscode.commands.executeCommand("cursorLineEnd");
		return this;
	}

CursorMover 自体を return している(promise じゃない)

@stakiran
Copy link
Member Author

dorepeat if possible

function doRepeatIfPossible(){
	const editor = getEditor();
	const curPos = CursorPositioner.current();
	const doc = editor.document;
	const currentLine = doc.lineAt(curPos).text;
	if(currentLine.indexOf("rep:") == -1){
		return;
	}

	const curLineNumber = curPos.line;
	const commandLine = `${getHelperCommandline()} -y ${curLineNumber} --repeat`;
	console.log(`Repeat: "${commandLine}"`);

	doSave();

	exec(commandLine, (err) => {
		if(err){
			console.log(err);
		}
	});
}
  • これは helper から実行してるだけ

do save

function doSave(){
	const editor = getEditor();
	const doc = editor.document;

	// 変更が無い状態(not dirty)で save() すると failed になるので防止.
	if(!doc.isDirty){
		return;
	}

	const promise = editor.document.save();
	promise.then((result) =>{
		if(!result){
			abort("Failed to save()");
			throw new Error();
		}
	});
}
  • 非同期の save をしている(けど return してない)

@stakiran
Copy link
Member Author

😭 これはダメだ……ちょっと書き換えて済むレベルじゃない……

一から書き直した方が良いかもしれん

@stakiran
Copy link
Member Author

依存関係

  • copyTask
    • cursorMoverAfterCopy
  • endTask
    • copyTask
      • CursorMover.golineend
      • doRepeatIfPossible
        • doSave
  • doSort
    • doSave
  • doRepeatIfPossible
    • doSave

@stakiran
Copy link
Member Author

どうすべきか?

  • どの操作も promise(というかThenable) を return させる
  • テストコード側で「処理が全部終わるまで待てる」ように、promise chain or await list で実装する
    • 🐰 ここがイメージわかんなぁ

@stakiran
Copy link
Member Author

endTask(){
    // ……
    // ★ここどう書けばいい? copyTask, golineend, do repeat 順に実行する
    // ……
    return thenable_boolean_object
}

@stakiran
Copy link
Member Author

save(): Thenable<boolean> だから doSave は問題ない

@stakiran
Copy link
Member Author

executeCommand<T>(command: string, ...rest: any[]): Thenable<T | undefined> だから golinetop(などカーソル移動系)も問題ない

@stakiran
Copy link
Member Author

async function endTask(){ } で良い?

activate() の

	const task_end = vscode.commands.registerCommand('tritask.task.end', () => {
		endTask();
	});

ここが async に対応してるか心配なんだが

@stakiran
Copy link
Member Author

問題なさそう。

ならいけるね

@stakiran
Copy link
Member Author

stakiran commented Dec 13, 2020

🏃 ガッと修正する感じになるぞ

  • 1: 各関数および CursorMover を thenable で return させる
  • 2: doSave に依存しているやつは全部 async funtion にして、await doSave() で待ち合わせる
  • (ここができたら await 対応ワンパス)
  • 3: copyTask まわりを await で待ち合わせる
  • ……

ここまでいけばキレイな非同期コードになってるはず

@stakiran
Copy link
Member Author

さあてと、やりますか 🏃 🏃

@stakiran
Copy link
Member Author

stakiran commented Dec 14, 2020

抜本的修正なのでブランチ切った方が良い?

メモだけ: 48e1db5

@stakiran
Copy link
Member Author

stakiran commented Dec 14, 2020

1h でワンパス通します

2020/12/14 19:55:23

ready go!

2020/12/14 21:18:24 終わったの addTask だけ。まあそうスイスイとはいきませんよね

@stakiran
Copy link
Member Author

くせもの

class CursorMover {
	static golinetop() {
		return vscode.commands.executeCommand("cursorLineStart");
	}

	static golineend() {
		return vscode.commands.executeCommand("cursorLineEnd");
	}

	static left() {
		return vscode.commands.executeCommand("cursorLeft");
	}

	static up() {
		return vscode.commands.executeCommand("cursorUp");
	}

こいつらが Thenable を返しやがるせいで、Thenable たちと共存できない(chain できない)……

かといって vscode.Selection で up や down を再現するのは地獄

@stakiran
Copy link
Member Author

いや、いけるか。

@stakiran
Copy link
Member Author

いや、ダメ。

  • left の場合、「行頭にいる時に行末に移動する」みたいなケースも全部対応しなきゃいけない
  • up の場合、「前行が現在行より短い場合に、機械的に前行の同じ列、とできない(そんな列はない)」みたいなケースも対応が必要

えぐそう

@stakiran
Copy link
Member Author

up はつかってなかったから消した

@stakiran
Copy link
Member Author

add 後のカーソル位置は、

……I……

(追加された行)
……I……

つまり1行だけ下にずれる

@stakiran
Copy link
Member Author

move は left() ではなく、「前の行の pos_description」が正しい。

つまり left() 要らん

left() 実装マジエグかったので助かるンゴ……

	static left(): vscode.Position {
		const editor = getEditor();
		const curPos = editor.selection.active;

		let newY = curPos.line;
		let newX = curPos.character;

		const isLineTop = newX == 0
		const isNotLineTop = !isLineTop
		const isFirstLine = newY == 0
		const isNotFirstLine = !isFirstLine

		if(isLineTop && isNotFirstLine){
			const prevLinePos = curPos.with(newY - 1, newX)
			const doc = editor.document
			const prevLine = doc.lineAt(prevLinePos).text;
			const prevLineLength = prevLine.length
			newY = newY - 1
			newX = prevLineLength
		}else if(isLineTop && isFirstLine){
			// pass
		}else if(isNotLineTop){
			newX = newX - 1
		}

		return curPos.with(newY, newX);
	}

@stakiran
Copy link
Member Author

stakiran commented Dec 14, 2020

endTask の戻り値の型を Thenable か Promise にしたひ……

image

が、少なくとも 処理不要時の return で Promise.resolve() するしかない ので Promise にするしかない。(Thenable オブジェクトはつくる手段がない)

ってことは、今 Thenable 返すやつは、then() で Promise 返すようにする必要がある。

	return editor.edit(f);

	return editor.edit(f).then(()=>{return Promise.resolve(true)})

みたいな

@stakiran stakiran mentioned this issue Dec 15, 2020
4 tasks
@stakiran
Copy link
Member Author

重くなってきたので移動する

#20

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant