- 投稿日:
今回は何故単体テスト書くのかというのを個人的な見地から書いていく。TypeScriptとJestで記述するが、他の言語でも応用は可能だと思う。
単体テストとは何か?
関数やクラスのメソッドを単体としてみた場合の振る舞いを確かめるものである。以下はその一例だ。
hoge.ts
export const joinString = (string1: string, string2: string) => {
return `${string1}${string2}`;
};
hoge.spec.ts
import { joinString } from '.';
describe('joinString', () => {
it('引数として渡した文字列が正しく結合されること', () => {
const actual = joinString('123', 'abcd');
expect(actual).toBe('123abcd');
});
it('第一引数が空文字の時に第二引数のみが出力されること(空文字が結合されること)', () => {
const actual = joinString('', 'edfg');
expect(actual).toBe('edfg');
});
it('第二引数が空文字の時に第一引数のみが出力されること(空文字が結合されること)', () => {
const actual = joinString('zxcvb', '');
expect(actual).toBe('zxcvb');
});
});
何のために単体テストを実装するか
- 実装されているコードが実装者の意図通りに動いていることを証明するため
- いわゆるテストコードが仕様書になるってやつです
- 実装コードの改修時に元々の意図と異なる状況(不具合など)が発生した場合に、それを検知するため
- 初回実装時であってもコードを直したときに予期せぬ不具合を防ぐため
- 動作確認した後にちょろっと直すことはよくあると思うが、そういう時に動作を担保するのに使える
- 他にも実装時にコードを思案しながら書いているときに、動作確認をする手間を省くのにも使える
何故単体テストなのか?
結合テストやe2eテストではなく、何故単体テストをするか、これは実務面では結構出てくる疑問だと思うので、以下で説明する。
まず単体テストは一般的に結合テストやe2eテストに比べ、実行速度が速く、コードを修正したときに素早く回し、デグレードしていないかの確認に使うときに有益である。これは単体テストは実際にサーバーを起動したり、ヘッドレスブラウザでUI操作を行うなどしないためだ。他にも同じ処理を何度も見るのでテストのオーバーヘッドが膨らみ、実行時間が落ちるといったこともある。単体テストであれば簡単なロジックの実行しかしないし、結合部分はすべてモックにするため高速に実行できる利点がある。
次に結合テストやe2eテストは共通処理を一個直すと、それを使っている処理のテストをすべて直す必要があり、保守性がよくない。単体テストは基本的に直した箇所のテストコードを直すだけなので保守性が高い。
最後に単体テストでは基本的に関数単体を見るためテストコードが肥大化しづらく、見通しが良いので何をテストしているのかが結合テストやe2eテストと比べて分かりやすい。またスコープが関数単体であるため、カバレッジが取りやすく、いわゆるC0, C1, C2と言われる、命令網羅、分岐網羅、条件網羅の三点を網羅しやすい。結合やe2eでは共通処理の全機能を使うとは限らないため、この網羅がしづらい。
単体テスト向きの設計は何か?
これについてはSOLID原則を使った設計が一番無難だと考える。画面であれば更にMVCモデルを使うのがよい。
SOLIDにする利点
- S:単一責任の原則で関数の責務を切り分けることで、関数の責務を小さくすることができる。責務が小さい関数はテストケースが少なく、テストコードを書きやすく保守しやすい
- O:閉鎖解放の原則により、関数を呼ぶ関数自体を不変にでき、テストの保守性を向上することができる
- L:特にテストには貢献しない
- I:特にテストには貢献しない
- D:依存逆転の法則により、関数内で呼び出す関数をモックやスパイといったテストオブジェクトに置換できるため、テスト容易性が向上する
MVCにする利点
基本的に画面の実装は密結合になりやすい。密結合になっているとテストが書きづらいのでModelとViewとControllerに分けてテストをしやすくするのが目的だ。きちんと分けた場合、Viewは文字列が出ているか、表示項目が出ているかみたいなテストだけでよく、Modelはビジネスロジックのテストをするだけでいいし、Controllerは互いの橋渡しができているかを見ればいい。このため、テストコードをシンプルにできるのが利点だ。
欠点は画面の項目数が多い場合、インターフェースがそれに応じて肥大化するため、画面に仕様変更が入ったときに保守性が悪くなることだ。ここは品質とのトレードオフで許容するしかないと思う。
単体テストで処理の結合部をどう見るか
一つの関数で完結している関数はそれでいいとして、他の関数を呼んでいる関数をどのように単体テストするかという部分。よくあるパターンとしては他の関数ごとテストしてしまうことだが、これでは結合観点になってしまう。
この部分については呼び出す関数をモックすることで単体観点で確認することが出来る。呼び出す先の関数が単体テストで網羅されていれば、その関数の振る舞いは正しいはずなので、呼び出す側としては呼び出す関数をモックして、関数が正常終了したケースと、異常終了したケースでカバレッジが取れるようにテストすればよい。これは例えば次のようなテストを書くことが出来る。
index.ts
import { isEmpty } from './lib/string-util';
export const validate = (hoge: string) => {
if (isEmpty(hoge)) {
throw new Error('空です。');
} else {
// 何もしない
}
};
index.spec.ts
import { validate } from '.';
import * as empty from './lib/string-util';
jest.mock('./lib/string-util');
describe('validate', () => {
it('hogeが空文字の場合、例外がスローされること', () => {
jest.spyOn(empty, 'isEmpty').mockReturnValueOnce(true);
expect(() => {
validate('aaaa');
}).toThrow(new Error('空です。'));
});
it('hogeが空文字でない場合、例外がスローされないこと', () => {
jest.spyOn(empty, 'isEmpty').mockReturnValueOnce(false);
expect(() => {
validate('aaaa');
}).not.toThrow(new Error('空です。'));
});
});
このテストではisEmpty()
の実際の振る舞いを一切見ていないため、validate()
の単体テストということが出来る。但しisEmpty()
のテストが漏れていると機能しないため、これをきちんと機能させる場合、呼び出し先の関数の単体テストも必須だ。
単体テストに出来ない事と、単体テストの意義
単体テストは単体を見るものなので、当たり前だが単体しか見ることが出来ない。結合観点やe2e観点はなく、単体テストが通っているからデグレやバグがないという通りはない。
基本的に単体テストは動作確認が人間がやり、カバレッジが網羅できており、テストしている内容も正しければ以後のテストを工数を削るためのものである。過去に行った動作確認でテストが通っていて、今も通っているのだから恐らく動作しているだろうということを見るためのもので、単体テストが通っているからデグレがないとか、そんなことの保証はない。
では単体テストを何のためにするかだが、複数の確認工程鵜を作ることでバグを作りこむことを防ぐ役割が一つだ。例えば単体テスト+結合テスト+e2eテスト+人による動作確認やテストと言ったように多層の防御があれば、不具合が起きる可能性を減らすことが出来る。もう一つはコードの仕様となることだ。実装を見て意図が読み取れないコードであってもテストコードがあればわかることもあるだろう。つまるところ実装品質を保つための防御機構の一つとみなすことが出来る。
単体テストを書くときにやったほうがいいこと
テストコードの動作確認をする
テストコードを書いても、そのテストコードが有効に動いているかどうかの観点が抜けていると有意なものとならないので、テストコードの動作確認は必要だ。偶にテストコードは動いているが、偶々テストがパスしてそれっぽく動いているだけみたいなこともある。TDDでやってもこれは起きえる。
期待値や結果値をいじって落ちるかどうかや、パラメーターなどの条件をいじって落ちるかは見たほうが良い。何をしてもパスする場合、そのテストコードは無意味である。
境界値テストを書く
文字列の入力パターンを見るテストや組み合わせテストではカバレッジを網羅していてもテストケースが漏れていることがある。例えば次のテストコードはカバレッジ100%だがtrueになる場合のテストが不足している。もしも関数の中身が必ずfalse
を返す実装になっている場合に、これを確かめることが出来ない。実装を知っているからこれでいいとみなすのではなく、基本的には実装を知らない体で書いた方が良い。とはいえ、限度はあるので現実的に起きうる条件でテストするのが無難だろう。この辺りはSIerで堅めの開発をしていると身に付きやすいのだが、個人の感性に左右される部分であり難しいところでもあると思う。
index.ts
export const isEmpty = (str: string) => {
return str === '';
};
index.spec.ts
import { isEmpty } from '.';
describe('isEmpty', () => {
it('空文字でない時にfalseが返る事', () => {
const actual = isEmpty('aa');
expect(actual).toBe(false);
});
});
変数を型で縛る
例えば以下の実装であれば引数は文字列型なので文字列以外が来ることは考えなくていい。
export const isEmpty = (str: string) => {
return str === '';
};
しかし以下の実装では何が来るかわからない。テストとしてはありとあらゆる値が来ることを想定して書かないとリファクタリングに耐えられないだろう。そんなテストは書きたくない。そもそも実装からどれほどテストを書けばいいのか予測し辛いのもある。
export const isEmpty = (str: any) => {
return str === '';
};
もしどうしても何が来るかわからない場合はunknown
にしておくとよいだろう。何故なら以下のコードはエラーになるからだ。
export const isEmpty = (str: unknown) => {
return str === '';
};
また全体的にきちんと型で縛っておくと、改修などで型が変わった時に実装コードやテストで型不整合が発生する機会が増え、型チェックでエラーが発生するため、未然に不具合を防ぎやすくなる筈だ。
- 投稿日:
米櫃を見たらうにょうにょして物が見えたとか、コメを研いでいたらなんか埃のようなものがたくさん浮いてたみたいな経験をしたことがある人はそこそこいると思う。これは実はコメに沸く虫だ。
スーパーなどでコメ櫃の虫よけというのが売られている通り、コメには虫が湧く。今回はそれを回避している個人的な方法を書く。
まずどのようにしてこの虫は湧くのだろうか?コメ櫃の外からやってくるのだろうか?実は彼らはコメの中からやってくるのだ。元々栽培中の米の中に虫が潜んでいて、それが米の中から出てくることによって発生するのだ。因みに一匹二匹出てきた程度では気づくことはないが、厄介なのは奴らは繁殖するのだ。繁殖すると増えて米櫃の中がグロテスクな状態になる。
さて、ではどうやって回避するかだが、個人的な取り組みとしては次のことをしている。
- 米を買いすぎない
- まず米は一袋だけ買い、これが尽きるまで次の米を買わないことにしている。ストックしていると米の中の虫が外に出てきて繁殖する機会が増えるからだ
- 米櫃の中が少なくなり、新しい米を入れるときは残っている古い米をすべて出す
- これは古い米は中から虫が出てくる確率が新しい米に比べて高いためだ
この取り組みを始めてから3年ほど建つが、米に虫が湧くことは一切なくなった。
米に虫が湧くのに気が付いたのは食べすぎを防ぐために一食当たりの米の量を減らし始めた時だった。米の消費量が減ったので必然的に米櫃の中の米が古くなりやすい状況だった。これによって虫の繁殖機会を増やしてしまっていたのだと思う。
米櫃に沸いた虫を排除するのは中々精神的に辛かったので、米と米櫃を捨てることになったが、金輪際あのようなことはしたくないという思いで再発防止に努めている。
虫が湧くのを防ぐグッズも売られているが、あれは買い替えが必要なのが難点だ。米を入れ替えるだけならお金はかからないし、出した米は食べればゴミにならないのでエコでもある。
- 投稿日:
私はキャッシュレス歴が長く、過去を辿ると2015年からVISAカードを使い続けている。最初の頃は学生だったのでVISAデビットを使っていたが、学生を卒業してからはクレジットカードを使っている。他にもWebMoneyといったプリペイド式の決済や、PiTaPaやiDといったポストペイ決済も使っているしBTCで支払いをすることもある。
とまぁ、それなりに長いキャッシュレス歴を持っていて、少し前までは現金決済など無駄だから皆キャッシュレスになればいいと思っていたほどだ。しかし、ここ最近になり現金の存在を見直している。
現金を見直すようになった最大の理由の一つは海外資本企業による販売商材規制だ。例えば2018年にSteamが美少女ゲーム規制を打ち出したことは記憶に新しい。また2019年にPornHubがPaypalからBANされたこともあり、私は徐々に危機感を募らせていった。
特にここ最近、特に2022年頃からクレジットカード会社などが日本のコンテンツを集中的にBANして来ていることが伺える。このことから私は海外資本、特にクレジットカードブランドや収納代行業者を疑いの眼差しで強く見るようになった。
そして、丁度この時期を境に私は地産地消活動に目覚めてきたのもあり、地域の商店を使う頻度が増え、偶然にも現金決済の比率が高まってきた。
国際的な決済企業を使いすぎると、日本円は海外資本の手の内になってしまい、海外の民間企業の思うがままになり、日本という国を自由に操作できてしまうリスクを孕んでいることに気が付いたわけだ。そういうこともあり、最近は現金決済の比率を高めている。
現金決済をしていると釣銭の計算や、財布に置いておく小銭と札の比率なども考えるようになり、頭を使うようになるので中々楽しいし、クレジットカードは少なくない手数料を店が負担しているので、そこをなくせるのは少なからず貢献になっている可能性もある。現金の取り扱いコストと相殺できるといった話もあるし、勿論それもなくはないと思う。なので、個人商店をはじめとした小規模な相手には現金、企業経営をしているようなところにはクレジットを使うといったような使い分けもしている。
で、それをしたところで何があるのか?完全に現金にしてないじゃんなどはまぁあるのだが、あくまで個人で支障のない範囲で小さな抵抗をしているという感じだ。
また買い物に関しては地域支援のために個人商店を中心にするように徐々にシフトしてきており、極力クレジットカードを使わなくていい生活に倒してきている。
スーパーなどの買い物とは違いお店の人と顔なじみになれて世間話ができたり、いろいろ新しい発見もある。勿論仲違いするリスクもゼロではないだろうが、これもまた人生の醍醐味の一つだと思うので、悪くないなと思っている。
しかし、2021年にpixivがじぶん銀行の支店を作ったときは遂にこの時が来たか…と思ったものだが、つい先日pixivcobanなる決済を作り、じぶん銀行から使うと特典が付くとかを見ていると先見の妙がすごいなと思う。またDLsiteも本日付でVISAとMasterCardの決済を停止しているので、いよいよ怪しくなってきた感じがある。
このままこの動きが大きくなると、一部でまことしやかに囁かれているAWSが日本でまともに使えなくなるということが現実になるかもしれないし、個人的には中露の経済制裁みたいなのが日本に来ても不思議はないと思っているし、そうでなくともVISAやMasterCardみたいなところが日本に対して何かしら大規模なアクションを起こしてくれば日本のサブカルチャーもただでは済まないだろう。
以前から言っているが、私は外務省の言うところの「人権及び基本的自由は普遍的価値であること。また、各国の人権状況は国際社会の正当な関心事項であって、かかる関心は内政干渉と捉えるべきではないこと」という考えには反対だ。全ては各々国が決めることで国際社会、特に欧米の価値観で決定されるべきことではないと考えている。何故なら欧米の価値観に染まれば、各国の多様性が失われるからだ。
文化というのは差別や貧困、理不尽のようなよくはない出来事が元で発生していることが多いと私は考えていて、これを人権侵害だとか言い始めると文化というものはなくなると考えている。そもそも庭に不審者が入ってきたら無警告で射殺しても良いみたいなとんでもないことを認めている国の方がよほど人権的に問題がある。
自由すぎて女性がレイプに会う国と、少なからず抑制されてて痴漢程度で済んでいる国、余りに抑制されていて女性に自由がなく扇情的なことができないので痴漢すらないが、逆に厳しすぎて性犯罪が起きてるような国でいえば、どう考えても痴漢程度で済んでいる国が一番まともだろう。因みに前者はアメリカ、真ん中は日本、最後はイスラム系の国を指している。
どれがいいとか悪いとかではなく、すべてあっていい。人権や普遍的価値なんか知ったことではない。それより全ての国に自由な自治権があってしかるべきだ。考えの押し付けをしてはならない。幸福も不幸もない。そもそも称賛されている文化なんてどれも地獄のような生活をしていた時代のものばかりではないかと思う。
またクレジットカード会社による決済規制については、山田太郎参議院議員が国会で議題に挙げられており、既に政治的関心になっていることが伺える。なお、記事を見るとわかるが、完全に堂々巡りになっており話が通じていない。要するに政府としては関心がないということだ。悲しいかな。
最終的に何が言いたいのかよくわからない内容だが、要するに以前と比べて現金決済の比率を上げているということだ。理由としてはキャッシュレス経済に対して一家言できたというところが大きい。
- 投稿日:
ボディーソープから石鹼へと似たような内容になってくるが、今回は洗濯洗剤を液体から粉末に変えてみた。
理由もボディーソープを石鹸に変えたのと似たような理由でスペースの関係だ。液体洗剤はとにかく場所を食うし邪魔なのだ。学生の頃からずっとNANOXを使ってきたのだが、とにかく詰め替えパックが場所を食う。
あと液体洗剤は容器を置いてる場所がべとついてくる。使うたびに微量が容器から垂れてきて、それの積み重ねで置いている場所がべとべとになってしまう。
この問題を粉末洗剤は解決できるのではないか?と思ったのである。結論から言うと今のところ分からない感じだ。一ヶ月ほど粉末洗剤で運用してみているが、容器がべとつかなくなったくらいしかわからない。詰め替え時にこぼす事故はほぼ起きないと思うので、そこはメリットかもしれない。仮にこぼしても掃除機で吸い取れる。
というわけで粉末のアタックを買ってきてみた。
液体洗剤をこぼすと処理が非常に大変で、賃貸だと床がめくれて来たりしてなかなか悲惨なことになることを考えると、そのリスクがないのはありかもしれないが、かなりこじつけに近い理由だ。
今のところ感じている粉末洗剤に感じているメリットとしては洗剤投入の時間が早いことだ。NANOXは粘度が高く、計量して投入するのに二度注ぐ必要があり、地味に時間がかかるが、粉末洗剤はスプーンで掬って入れるだけなので、この工程が一瞬で終わる。それ以外に明確な嬉しさは感じていない。適当にググったところ汚れ落ちは粉末の方が上と聞くので、衣類の汚れに困る頻度は減るのかもしれないが、現状体感できるものがないので不明だ。逆に傷みやすさも上がるらしい。
因みにNANOXを使い始めた理由は単純で、当時液体製剤が珍しかったのと部屋干しに強く、嫌な臭いが少ないとあったからだ。容器は汚れがたまってきたなどで三回くらい買い替えているので、写真に写っているのは新しめのやつである。しかも嫌な臭いが少なかったかと言われれば、特にそんなことはなかったと思う。
結果としてこれまでと比べると変革要素が薄くなってしまったが、まぁ特に困ることはないのでこのまま粉末で行こうと思う。何か支障を感じたら液体に戻すかもしれない。