uzullaがブログ

uzullaがブログです。

header後にdieするテストのアンチパターン

明後日土曜日はPHPConference Fukuoka 2019ですね、楽しみなuzullaです。

さて、Hachioji.pmのSlackでPHPのテスト手法について質問があって、結果的にその解決策はいいのが無かったんですけど、過去こういうことはしたことがあるよってメモ書きです。

エントリ名に「header後にdieするテストのアンチパターン」とかきましたが、そもそもheader() からのexit() コンボがアンチパターンですので、イケてるフレームワークを正しく使う方々には本エントリは役立たないはずです。

PHPのexitは終了してしまう

さて、phpで以下みたいな「リダイレクトした直後にexit」というコードはしばしば見かけるものです。

<?php
if($need_redirect){
  header("Location: /");
  exit;
}

ぐぐってみるとわかりますが、本当に多くて、本当かよと思います。しかし、実際俺もかいてた事があるので本当です。

で、こういうコードをテストしようと思うと、理論上以下みたいになるので、phpunitが終了します。

<?php
use PHPUnit\Framework\TestCase;

class MykTest extends TestCase
{
    public function testOne()
    {
      //実際にはここにべた書きするわけないですが
        if($need_redirect){
          header("Location: /");
          exit;
        }
    }

    public function testTwo()
    {
        // ここがテストできない
    }
}

つらい。

こういうこともできない

register_shutdown_functionとかを知っていると、以下みたいにかけるかなと思いますが、わりとできません。

<?php
use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    /**
     * @test
     * @expectedException \Exception
     */
    public function testOne()
    {
        register_shutdown_function(function () {
            throw new \Exception("don't die!");
        });

        exit();
    }

    public function testTwo()
    {
        echo "plz test me!!!";
    }
}

register_shutdown_functionのスコープはphpunitの外にとびでちゃうので、ここで投げてもphpunitがキャッチしてくれません(というふうに私は認識しています)

そもそもexitはphpunitをまるごと殺そうとしますので、キャッチできたとしてもdieまっしぐらです。

evalもだめ

<?php
use PHPUnit\Framework\TestCase;

class MyTest3 extends TestCase
{
    public function testOne()
    {
        eval("exit();");
    }

    public function testTwo()
    {
        echo "plz test me!!!";
    }
}

他の言語の経験があると、一見できそうです。でもevalはやっぱりこのスコープで実行されるのでダメです。(終了しちゃいます)

https://www.php.net/manual/ja/function.eval.php

解決策は?

無い(迫真)

無いので、以下みたいなものもあるようです。ブクマも数件ついてます、皆さん悩みは一緒ですね。

github.com

緩募:Pure PHPで、exit/dieでphpunitを殺さない黒魔術をご存知の方は教えてください。

さて、「runkitは?」という声もきこえてきますが、exitは関数ではない。

(動的な)ソースフィルタは?という怖い声がきこえてきますが、尋常な精神では無理。

「いやいや、コードをリファクタしようよ!」という正気の意見はここでは黙殺します、世の中にはできないこともある。

「exitを例外に単純置換しよう!」といったある意味PHPerらしい動けばいい系アドホック解決策は、解決はするかもしれませんが早晩似たような地獄に到達するのでやめたほうがよいです。

(え?exitの代わりにユーザー定義な致命的Errorを投げる?…フム、興味深さもありますね)

で、結局、exitが絡む時にphpunitを殺さない為には別プロセスで実行するしかない*1。みんな大好きなexecしましょう(正気かギリギリのライン)

事案

で、「私の場合は」あるリダイレクトがちゃんと指定のURLにリダイレクトされているか確認したかったことがある。

非常にアレなのでわすれてましたが、こういうことを過去してしまいました。

<?php
use PHPUnit\Framework\TestCase;

class MyTest3 extends TestCase
{
    public function testOne()
    {
        // dumb_exit.phpは勿論最初からファイルをつくっておいておいてもよい
        file_put_contents("dumb_exit.php", "<?php 
        register_shutdown_function(function(){
            echo var_export(headers_list()); // ウッ
        });

        // アプリをうごかしているような体裁を泣きながら整える
        $_SERVER['REQUEST_URI'] = "/hogehoge";
        $_GET['hoge'] = "fuge";
        require_once(__DIR__.'/vendor/autoload.php');

        WebApp::bootup();
        // この中で、
        // header(\"Location: http://example.jp/\");
        // exit();
        // が実行されるとおもってください
        ");
      
        exec("php-cgi dumb_exit.php", $out, $exitCode); // php-cgiでないとヘッダーがとれない

        $this->assertEquals(0, $exitCode); // お好みで

        $stdout = implode("\n", $out);
        list($header, $body) = preg_split("/\n\n/u", $stdout, 2); // ヘッダー分離

        $header_list = eval("return {$body} ;"); // アッ
        $this->assertEquals(1, count(preg_grep("|\Alocation: https://example.jp/\z|ui", $header_list)));
    }

    public function testTwo()
    {
        $this->assertIsString("plz test me!!!");
    }
}

安易に真似されても何なので説明は省略しますが、exitが呼ばれた時にセットされていたヘッダーを、標準出力の文字列経由で配列で受け取って検証する、という感じです。

これはregister_shutdown_functionの中でも、headers_list()はうごくし、var_exportと標準出力をうまいことすればとれないことはない(気もする)という話でもあるのですが、

書いておいて唐突ですが、これは本当によくないので、アンチパターンですね、マネしないようにしましょう!!

(なお、「headers_listしなくても出力のヘッダーを分離すればいいだけじゃん?」というご質問については全くそうなのですが、私は文字列操作が嫌いだったようです、上の中でもめっちゃしてるくせに)

がんばってseleniumとかpuppeteerとかでE2Eしような!!

header吐いてexitしちゃう、しかもreq/resオブジェクトみたいなは無いし、テストできない。そういったレガい案件ではすなおにE2Eするのがセオリーなのだと思います。俺だって人にきかれたらそう答える。

しかし、人も金もなく、ガンガン走らせたいユニットテストごときにヘッドレスブラウザを飼育するのもコスパがよいとはいえません。そのために人はこういった苦難の道を歩みがち、実に人生という感じがします。

素直に最初テスタブルなアプリにしましょう。

しかし人生には取り返しのつかない事だってあるよね。

こちらからは以上です。

*1:そういえばforkで解決…できるのかな…?

激安アクティビティトラッカーガチャ結果のご報告と、そのレビュー

私はスマホを(物理的に)手元に置いておく癖がなく、離れた所で通知に気が付かないことが結構あります。結果として洗濯とか食器洗いとかをしているとDMの呼び出しとか奥さんからの連絡を逃すことが結構ありました。

まあー所詮そのラグは10分とか1時間とかなのですが、奥さんが帰宅ついでの買い物をする時とかはタイミングを逃してなんで返事ないの的なことになります。なので、できれば通知を逃さないようにApple watchとかほしいなーーとか思ってたです。

ただ、いかんせんあいつは高いんですよね、なんやかや5万くらいするじゃないですか。

そんな折、amazonを見ていて直接USBコネクタが生えたアクティビティトラッカーをみつけました、LINEやFacebookメッセンジャーの通知が表示できる。これなら行けるかなーと購入したのがこのガチャの始まりでした。

なお、前提として、今の私はAndroidがメインのスマホです。OSが違うとアプリ側がサポートする機能も違うと思うんで、注意してください(特に通知)。

あと、「ここで紹介しているAmazonリンクや、そこから辿れる似た商品が同じ性能をもっているとはかぎらない」ということを強く意識してください、ほんと全く同じ見た目なのにアプリが違うとかあるっぽいんで…。

オチとして、これが一番良かった

f:id:uzulla:20190623000352p:plain

https://amzn.to/2IYS7aG

(消えるかもしれないんで、スクショにしておきます)

これを先々週くらいに買って現在も使っているんですが、必要な機能が一通りありこれが一番だ!となりました。ここまで3つ買って1万円くらいかかっているわけですが、なあ悪くない「引き」かなと思いますw

ただ、もう品切れです…(後継機らしきものもあるが、本当に同一とはおもえない(アプリのスクショが違う)ので注意してください)

良い点を挙げると、

  • SlackやKyashとか、指定すればあらゆるアプリの通知が受けられる
  • スマホ側アプリがまともで、アプリを起動しなくともメトリクスがちゃんと取れる(ぽい)
  • 時計本体からUSB-Aコネクタが生えており、そのままUSBポートに挿して充電できる
  • 体感として一週間以上バッテリーが持つ
  • 通知等の日本語が化けない(絵文字は無理)
  • 通知画面がちゃんとキープされる
  • 日付表示がちゃんと(?)MM/DD
  • ちゃんと安い!

というかんじです。

実際Slackの通知が受けられるのが最高ですね。3000円くらいで、一週間電池がもって、時計にもなるとか最高かよ。

f:id:uzulla:20190623001641p:plain
ちょうどSlackの通知がなかったんで、LINEの通知でかんべんな!

通知の次に重要な時計表示や、睡眠トラッキングもちゃんと動いているので満足です。私は割と睡眠が苦手なのですが、このトラッカーの「深い睡眠」は比較的正しい気がします、実際ちゃんと睡眠時間を各補したつもりでも、深い睡眠の時間が1時間をきると翌日眠い。

あんまり興味はないんですが、24時間の心拍数計測もちゃんと動いています。

欠点としては、

  • 標準でついているバンドがゴツくて重い
  • (他のでも同じだが)時計本体のUIの使い勝手はひどい
  • これ系の時計でよくありますが、天気予報が動かない
  • (いらないけど)ウォッチフェイスは固定なので、楽しさがうすい

このタイプの中華アクティビティトラッカーってものすごく軽くて、つけてることを忘れるほどなんですが、バンド部分がしっかりしてると逆に重くて気になるんですよね。なので、私は他のトラッカーについていたバンドを無理やり?着けて使っています。完全に同じ形状ではないんですけど、シリコンゴムのバンドなんで、ちょっと伸ばすとはまる。

あと、インストールした直後、バッテリー消費がいっときひどかった。ただ、アプリを再起動した上で、OSの設定でアプリについている各種権限を全部外したら普通になりました、現在位置の権限はバンドとのペアリングで必要っぽく、ペアリングが終わったあとはOS設定から外してもちゃんと動いています。まぁ、原因がこれとはかぎりませんが…。GPSがつかえないとランニングとかそういうやつの距離データはおかしくなるかもですが、私はアクティビティはどうでも良いので気にしてません。

f:id:uzulla:20190623001617p:plain

ということで、「アクティビティ」機能のことはさっぱりわからないですが(正直、万歩計はめちゃめちゃ上振れしている気がする、引きこもってるのに2000歩はないやろ…)、通知と睡眠トラッキングはいい感じでお薦めです。

だめだったやつ、その1

ここからは蛇足気味ですが、チョイスの参考になるかと気に入らなかったモデルを紹介します。

f:id:uzulla:20190623000632p:plain

https://amzn.to/2IYmFt7

これは最初に買ったやつです。USBから充電できるし、比較的時計のUIもまともです。

目的だった通知機能もちゃんと動いていて、画面はカラーで、日本語も基本的に化けないで表示されていました。

これのすごい(?)ところは生体データについて(正確とはおもってないが)計測できる項目が多いことで、血中酸素濃度や血圧が測れるところでしょうか。とりあえず血圧については正確ではないように思います(家にある、ちゃんとした血圧計と比較して)。血中酸素濃度は正確性はわからんですが、98か99以外を見たことがないです。

でも、時計自体の機能としては非常に多機能で、見た目も軽さもよろしい感じ。最初に買った製品ですので比較がなかったですが、3000円のUSB充電できるLINEやFacebookメッセンジャーGmailの通知バンドとしては不満はなかったものでした。

まあ…日付がDD/MMなのはどうかとおもったけどな!

f:id:uzulla:20190623001209p:plain

ただ、当時はあんまり意識してなかったんですが、アプリが本当にしょぼくて…近年のアプリとは思えない見た目です…。データ同期もアプリ起動しないとだめっぽく、なれてきたら同期を忘れて欠落するのも微妙なところでした。

f:id:uzulla:20190623001241p:plain

まあでも、最初のものなので「まあ中華製品なんてこんなもんだろ」と思ってましたね。軽くて安いし。

バンドとして使う予定がなかった二代目

f:id:uzulla:20190623002157p:plain

https://amzn.to/2ITPlmQ

実はこれはバンドとしては使う予定がありませんでした。

何で買ったのかと言われれば、Nordicのチップが乗っていて、自作のファームウェアが書き込めるという海外のブログを見たからです。

https://github.com/micooke/arduino-nRF5-smartwatches

当時某カンファレンスの謎ガジェットネタを探していたのですが、このサイズのものを自作するのは個人では不可能だとおもったので、流用できないかな〜と調査目的に試しに買ってみたのでした。

で、いじって壊す前に(公式のファームは当然公開されてないので、自作ファームを焼いたらもどせない)一瞬本来の用途でつかってみるかなーと思って使ってみたところ、スマホアプリの出来が最初のものよりも断然よかったという。

f:id:uzulla:20190623001328p:plain

上と比較してみてくださいよ、段違いすぎる…。

実際iOSのフィットネス機能とも連携できるし、アプリの見た目がよくて見やすいし、「あっ、一個目はだめな製品だったんだな!!」と気づいてしまったのです…。まあ、通知は相変わらずLINEとかFacebookとか指定アプリだけでしたけど。

しかしながら、見た目の通りハードは微妙な出来です。まず、画面はモノクロだし安っぽい、測定できることも少ないし、通知の日本語も文字化けしまくり、ギリギリ意味は読めるのですが、末尾の絵文字がばけたりすると「?」がおしりについて連絡なのか質問なのかわかりづらい。モーションセンサによるウェイクアップの感度も微妙で時計としても地味に使いづらい。

でも一番きびしいのが、通知が2秒くらいで消えるところですねw2秒とかよめねえww。 一度消えちゃうとすっごいつかいづらいUIでメッセージを見る必要があり、スマホ開いたほうがマシじゃん!となります…。

ただ、本体がめちゃめちゃチープ=すっごく軽い、という所が逆によかったです。説明し辛いですが、センサー部分の作りもなめらかな感じになっていてつけ心地が良い。ということで本来の目的をわすれて普通につかっていましたね。

とはいえ、私はSlack通知が欲しかった

で、その後知り合いが高級なバンド(Apple watchであったり、Fitbit Ionicであったり)を使っているのをみると、やっぱりSlack連携ほしいんですよねー。

かといって、そこらへんはやっぱり高いんすよ!Ionicでも2〜3万くらいする!必要か必要でないかといえば必要でないものだし無理!しかも独自のチャージャーケーブルがいる!旅行とかで絶対持っていくの忘れる!枕元と机にもう一本づつ買うとか出費が必要!あとデカイくて重いし、ぶつける事に気遣いたくない!

…という負け惜しみ的な悔しさを元に、先日深センにいってたときに電気屋でたくさんのスマートウォッチをチェックしたんですけど、謎のOSが搭載されたやつばっかりで、素朴に時計と通知だけほしいってのはあんまり無いんですよねえ…日本語対応してるかもわからんし。しかも店頭のおねえさんは製品については詳しくないw(展示している本体はすぐにみせてもらえるけど、箱や説明書は引っ張り出してもらうのが大変)。

f:id:uzulla:20190623002334p:plain

でも諦めきれず、改めてネットをしらべたら…見つかったんですねー。

というところで、最初のものに戻ります。

大体どれも3000〜4000円くらいだったから、三本買って1万位かな?これで合計2万使ってたらもう負けって感じですけど、1万ならガチャ勝利という感じではないでしょうか(オチ)。

以下は余談です。

ポイントについて

蛇足なんですけど、いくつか私の思うポイントを挙げておきます。

充電方法を気にしたほうが良い

電池の持ちもそうなんですが、充電の方式がやっぱり安物は重要かなと。ケーブル方式がまだまだ一般的っぽいんですが、こういう激安製品ってケーブルが手抜きだったり、後から手に入らないんですよね。似た形が売ってても極性逆くらいまでありうるので結構注意が必要。

だもんで、やはりUSB直接挿せるやつは最高だと思うんですよね〜。

f:id:uzulla:20190623000844p:plain

でも、バンドから外すのがやったらめったら硬い製品もあります、最初に買ったのがそうでした。樹脂製のバンドがのびたりちぎれたりするんじゃないかと少々ヒヤヒヤします。まあ少しくらいのびてくれたほうが取り外ししやすくて良い気もしますが…(?)

あと、端子が結構サビ(?)ます。特に最初の製品は水がたまりやすいらしくて、拭けばおちる緑青みたいなのができてました。洗面程度ではこわれてませんけど、風呂では外してますね…。

アプリを見たほうが良い。

正直、Amazonの商品詳細ページでなんてアプリをDLする必要があるのかは記載されていない事が多いんですが、なんとしてもしらべてアプリの出来をみたほうがよいです。

中華アクティビティトラッカーってちょっと前の山寨機みたいなもんで、中身のチップ(というかnRF5系?)は多分全部おんなじなんですよね、多分リファレンスファームもあるんでしょうね、それをちょいちょいとみなさんカスタマイズして売っている感じ。

ところが、何故かしらないがスマホアプリはどれもこれも全然違うんですよ。なんでなんですかね…?(勿論、多少は似てますが…Android2.4とAndroid5くらい違う(??))

あ、勿論ですが、同じようなメーカーの製品であっても別のアプリにはつながらないみたいです(N=3の感想)

レビューを見たほうが良い

レビューは結構重要です、レビュー詐欺とかよく言われますけど「よくできてます!」みたいなのは全く参考にしなくていいんですが、機能とかはそこまで嘘が書いていない印象です。

マニアックな情報が(Slack連携するとか)乗ってることもありますし。

ただ、Amazonのレビューは違う商品のレビューがかいてあることがあるのでそこは注意が必要ですね。

また、レビューは「カスタマー Q&A」のところの検索欄から検索ができるので、それも活用すると良いと思います。

ちなみに、どうしてこのバンドがSlackに対応しているとわかったかといえば、Amazonのレビューにそのように記載されていたからですね。Amazon商品説明はなんだかんだで不足しがちですが、レビューには結構そういうことが記載されているので。(とはいえ、信頼性が微妙だったり、前のモデルの記述だったりすることもありますが…}

まとめ

「ダサくね?」「それを気にしたら負けよ!」

Google Photoの写真がたくさん掲載されたエントリをちょっとだけ楽に書く手法

はてなブログへの不満なのですが…Google Photosを貼り付けできるのは最高なんですけど、実際編集しだすとUI的に厳しい。

  • 一枚一枚はるのは、大変すぎる
  • 一度に全部はると、(エディタ上では)それぞれのimgがどの写真かわからなくなる
  • あきらめてGoogle Photosでなく、はてなフォトライフで貼り付けると容量が減る(つかいきれないと思うけど、まあ気分)

きええ!!!

ということで私のやり方

  • Google Photosで、使う写真をまとめたテンポラリなアルバムをつくる。あとで追加は面倒なので、いるかいらないかわからないものはとりあえず入れておく
  • はてなブログで新規エントリをつくり、Google Photoの選択画面を開き、作ったアルバムを開く
  • クリックポチポチ、あるいは左キーとスペースキーで全部選択する(全部選択というキーがほしすぎる…)
  • 選択しおえると、ブログ記事に全部のIMGタグリストが反映される。
  • 本文を、手元のMarkdown Editorにコピペする(編集と画像表示が同時にできるTyporaなどがおすすめ)
  • (エディタによるが)無事、画像をみながらテキスト編集できる!!
  • 書き終わったら、Markdownのソースをはてなブログに戻す
  • Happy!!!

いろいろ難しいのはわかるのですが…

  • Google Photosの画面を閉じない(別窓に維持)とか
  • ごく簡単でいいので、2ペインモードをサポートするとか

してほしいなあ〜、っておもう。

他にも

  • 指定のアルバム全部のIMGをはりつけるとか(選択面倒)

あればいいけど、これGoogle Photoが提供している画面だからなあ…、変更できないよね…。

余談のTypora

Typoraなんですけど、これは本当に最高のmdエディタだとおもいますのでおすすめです。

typora.io

軽さだけみれば他にも強いmdエディタはあるんですけど、画像やUMLが混ざったドキュメント書いたり、アウトライン書いたり、そういうことのためには本当に便利。

こちらからは以上です。