#概要
Javascriptで自作関数の出力をチェックをしたくなったときに、ある程度汎用的に使えるテスト用関数を作りました。
意識したことは以下です。
- 被テスト関数の引数の個数を限定しない
- リストやオブジェクトを返す関数の場合も、要素同士の異同を判定できるようにした
#実装
function Tester(){
const _this = {}
_this.exec = function(_func, _args, _expected){
const _arguments = [].slice.call(arguments);
const func = _arguments[0];
const args = _arguments.slice(1, -1);
const expected = _arguments[_arguments.length-1];
const value = func(...args);
//console.log(value, args,expected);
if(_this.isSame(value, expected)){
return true;
}
else{
return false;
}
}
_this.testAll = function(func, data){
let ok = true;
for(let d of data){
const args = d.slice(0,-1);
const value = func(...args);
const expected = d[d.length-1];
if(_this.isSame(value, expected)){
console.log("OK: args=",JSON.stringify(args), JSON.stringify(value), "=",JSON.stringify(expected));
}
else{
console.log("NO: args=",JSON.stringify(args), JSON.stringify(value), "!=",JSON.stringify(expected));
ok = false;
}
}
return ok;
}
_this.typeOf = function(obj){
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
_this.isSame = function(object1, object2){
const type1 = _this.typeOf(object1);
const type2 = _this.typeOf(object2);
if(type1 !== type2)return false;
if(["undefined","null","boolean","number","string"].includes(type1)){
return object1 == object2;
}
else if(type1 === "array"){
if(object1.length != object2.length)return false;
for(let i=0;i<object1.length;i++){
if(_this.isSame(object1[i], object2[i]) === false)return false;
}
return true;
}
else if(type1 === "object"){
if(object1.length != object2.length)return false;
for(let k in object1){
if(k in object2 == false) return false;
if(_this.isSame(object1[k], object2[k]) === false)return false;
}
return true;
}
else{//mapとかsetとかsymbolとか
console.log("warning: unsupposed type", type1);
return object1 === object2;
}
}
return {
exec: _this.exec,
testAll: _this.testAll
}
}
export { Tester }
#解説
##Tester
Tester
はパブリックな関数を2つもつクロージャです。
exec
は一回のテストの結果を真偽値で返します。
testAll
は被テスト関数に代入したい引数と期待出力のセットのリストを渡すことで、複数のテスト結果をコンソール出力することができます。
クラスで実装しても良かったのですが、this
のややこしさを避けるためと、パブリックにする関数を絞るためにクロージャで実装しました。
##_this
const _this = {}
クロージャ内関数が自由にアクセスできるthis
的な変数として_this
を用意しました。
クロージャなので別になくてもよいのですが、_this
に紐付け可能なものは紐付けておいたほうが、クロージャ内で宣言された変数/関数かどうかが判断しやすいので用意しました。
##可変長引数
exec
は関数_func
に引数_args
を代入したときの出力を期待出力expected
と比較し、真偽値を返します。
関数内でarguments
を用いることで、_argsが2つ以上のときも対応可能できるようにしました。(exec(func, arg1,arg2,...,expected)のような形で実行できます)
またfunc
には、...
を使ってargs
を展開することで、func
が取る引数の個数によらずうまく渡せるようにしました。
const _arguments = [].slice.call(arguments); #argumentsはlist-like変数なので一応listに変換する
const func = _arguments[0];
const args = _arguments.slice(1, -1);
const expected = _arguments[_arguments.length-1];
const value = func(...args);
##リストやオブジェクトの比較
array
やobject
の比較はアドレスの比較となるため、そのままだと中身が一緒でもfalseになってしまいます。
そこでisSame
という比較用の関数を作りました。
isSame
ではtypeOf関数で引数のタイプを特定したあと、タイプがarray
やobject
であれば、要素ごとの比較を再帰的に行うことで、要素同士が一致しているかどうかの比較をできるようにしました。
ただし完璧ではなく、map
など一部の特殊な型はうまく比較できないので注意が必要です。
_this.isSame = function(object1, object2){
const type1 = _this.typeOf(object1);
const type2 = _this.typeOf(object2);
if(type1 !== type2)return false;
if(["undefined","null","boolean","number","string"].includes(type1)){
return object1 == object2;
}
else if(type1 === "array"){
if(object1.length != object2.length)return false;
for(let i=0;i<object1.length;i++){
if(_this.isSame(object1[i], object2[i]) === false)return false;
}
return true;
}
else if(type1 === "object"){
if(object1.length != object2.length)return false;
for(let k in object1){
if(k in object2 == false) return false;
if(_this.isSame(object1[k], object2[k]) === false)return false;
}
return true;
}
else{//mapとかsetとかsymbolとか
console.log("warning: unsupposed type", type1);
return object1 === object2;
}
}
##変数の型の取得
isSame
のなかで変数の型を取得しています。通常のtypeof
だとarray
がobject
として検出されるなど判定が少し粗いので(実は今回の場合はarray
はobject
として判定されても問題ないのですが)、より細かく取得できる関数を定義しています。
_this.typeOf = function(obj){
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
#テスト
ブラウザのコンソール出力でテストします。
<script type="module" src="js/main.js"></script>
import { Tester } from "./Tester.js";
//可変長引数の関数
function sum(){
const args = [].slice.call(arguments);
let cnt = 0;
for(let v of args)cnt += v;
return cnt;
}
//リストやオブジェクトを返す
function getList(x){
return [x,2,3];
}
let tester = new Tester();
tester.testAll(sum, [
[1,2,3],//OK
[1,2,4],//FAIL
[1,1],//OK
[0]//OK(引数ゼロの場合
]);
tester.testAll(getList, [
[1, [1,2,3]],//OK
[[1,2],[[1,2],2,3]],//OK
[{a:3},[{a:3},2,3]],//OK
[{a:3},[{b:3},2,3]]//FAIL
]);
被テスト関数の引数がいくつでも正しく比較できています。
また、被テスト関数の戻り値がリストやオブジェクトの場合でも正しく比較できています。