JDBな人生  専門的なことから日常的なことまで~ まぁ自由きままに書いていきます。
2017年04月 / 03月<< 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 >>05月

アクセスランキング

[ジャンルランキング]
コンピュータ
356位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
34位
アクセスランキングを見る>>

Kotlin + Mockitoでany<T>()やeq<T>()を使いたい

今年頭から、APIの開発をMojolicious/PerlからSpring Boot/Kotlinに移行しています。今回は初のKotlinの記事です。

KotlinはJava完全互換を謳っているので、当然Java向けのモックライブラリであるMockitoもきちんと動くということになるはずです。しかし、実際には以下のような状況ではうまく動きません。

java - Is it possible to use Mockito in Kotlin? - Stack Overflow
http://stackoverflow.com/questions/30305217/is-it-possible-to-use-mockito-in-kotlin

まずモック対象のメソッドがこんな感じだとします。
ここでのポイントは引数の`String`がNon-Nullになっていることです。
class UserService{ 
    fun findUserByToken(token: String) {
    // 定義
    }
}

そしてモックを実行します。「特定の値が指定されたとき」であれば次の書き方ができます。
Mockito.doReturn(dummyUser).`when`(userServiceMock)
    .findUserByToken("token string")
Mockito.doReturn(dummyUser).`when`(userServiceMock)
    .findUserByToken(Mockito.eq("token string"))

条件を特に指定しない場合、次の書き方ができます。
Mockito.doReturn(dummyUser).`when`(userServiceMock)
    .findUserByToken(Mockito.anyString())
Mockito.doReturn(dummyUser).`when`(userServiceMock)
    .findUserByToken(Mockito.any())
Mockito.doReturn(dummyUser).`when`(userServiceMock)
    .findUserByToken(Mockito.anyObject())

さて、この記事にたどり着いた人ならお分かりかと思いますが、これらの5つの記法のうち、実際に動くのは最初の一つのみです。
こんな例外が発生します。
java.lang.IllegalStateException: Mockito.any() must not be null

その原因はMockitoの実装にあります。MockitoのSourceを見ればわかるのですが、`any()`や`anyObject()`を呼ぶと何が起きるのかというと、
①内部で条件の登録を処理する
②メソッドの戻り値としては`null`を返す
という具合です。
https://github.com/mockito/mockito/blob/master/src/main/java/org/mockito/ArgumentMatchers.java
public static <T> T any() {
    return anyObject();
}

public static <T> T anyObject() {
    reportMatcher(Any.ANY);
    return null;
}

この戻り値`null`はどこに行くかというと、mockされた`findUserByToken(token: String)`に行きます。が、問題はこのtokenはNon-NullなStringだということです。親切なKotlinは、このようなNon-Nullな引数を持つメソッドを呼び出すときに、事前に「渡す予定の」引数がnullかどうかチェックしてくれます。

つまり、テストコードがKotlinのSourceからJavaのコードにコンパイルされるときに、このようなメソッド呼び出しは
①引数として渡す予定の値がnullかどうかチェックする、nullなら例外を投げる
②実際にメソッドを呼び出す
という2段階の処理として生成されるということです。KotlinのNon-Nullの実装の機構がそうなっている、という話ですね。
そのため、any()の戻り値がmockされたuserServiceのインスタンスに届く前に、Kotlinのnullチェックに引っ掛かってしまい、敢え無く例外で落ちる、ということになるわけです。

解決策は、このnullチェックを何とかしてくぐり抜ければ良いということになりますが、そのための方法が、「総称型扱いでnullを渡す」というものです。この場合だと、「なぜか」上記のコンパイル時の処理で①(渡す引数のnullチェック)が省略されます。Kotlinの実装依存の解決策といえばそう、ということになりますね。
fun <T> nullNotNull(): T {
    return null as T
}

// 落ちる
userServiceMock.findByToken(null);

// 落ちる
userServiceMock.findByToken(null as String);

// 落ちない
userServiceMock.findByToken(nullNotNull());
※Kotlinでは「渡す予定の引数のnullチェック」と「引数としてもらった変数のnullチェック」の両方を行うので、これがmockでなければ三つとも落ちます。ここではMockitoが中身をオーバライドするので、引数としてもらった変数のチェックは実行されません。

一般的な解決策としては、次のようなclassを作っておいて、Mockito.any()やMockito.eq()の代わりに使えばなんとかなります。
class KotlinMockitoHelper {
    companion object {
        fun <T> any(): T {
            return Mockito.any()
                    ?: null as T
        }

        fun <T> eq(value: T): T {
            return if (value != null)
                Mockito.eq(value)
            else
                null
                        ?: null as T
        }
    }
}

なんだかすっきりしない結論ですね…。
とりあえず、Stack Overflowで出ていた解決策でどうして解決できるんだ??と調べてみた次第です。改めてポイントをまとめると、
①Mockito.any()の戻り値はnullである、
②<T>に対してはnullチェックをサボる、というところです。
これだと、今後の実装でまた動かなくなる、ということもあり得ると思います。まあ、モックするというテスト手法は下火になりつつあるという話もあるので、モックせずにうまく書いていく、という方針をとるのも手なのかもしれません。
   Kotlin    TB(0)    CM(0)    EDIT    ページ↑

Picture of the day: しらすのピザ

お久しぶりです。またまた広告が賑やかになっています。

ここ数週間、気分転換にピザのようなものを作っています。ピザとナンの中間のような粉ものといいますか、ピザ風のパンといいますか。ナンとピザって材料はほとんど一緒なんですよね。

ピザというとトマトのイメージがありますが、トマトとというのはアメリカ大陸発祥の食物ですから、使われるようになったのはアメリカが発見されてからということになります。しかも、初めのうちはあしらい・装飾だけで、食用にはしていなかったそうな。それまでは、ピザといえばしらすのような小魚を載せていたそうです。
とイタリア語の先生が言っていました。

ということで、それっぽく、しらすのピザを作ってみました。しらすはイタリア語でbianchettoと言います。それというのも、白はbiancheで、そこに小さい(可愛らしい)もの、という接尾辞のettoをつけた語です。つまり直訳すると「白くてちっちゃいやつ」です。

IMG_20160720_213019.jpg
「しらすのビザ」にあたる「Pizza ai bianchetti」を検索すると、なんか違った感じのものが出てきますが気にしない。

材料はふつうのピザ生地(強力粉、ドライイースト、…)に加えて、しらす、チーズ、塩、粗挽きこしょうです。極めてシンプルですね。調理時間は発酵も含めて2時間といったところです。

味はなかなか悪くなかったです。ずっと食べていると飽きがきますが、その時はなんかスパイスでもかければいいんじゃないでしょうか。

ピザじゃなくても、うどんでもナンでも良いですが、生地をこねるというのはなかなか良いストレス解消になります。外に出て走り回るほどでもないけど何かしたい、というときにはちょうど良いと思います。
   Cooking    TB(0)    CM(0)    EDIT    ページ↑

メモ?コメント?備考?

最近少し疑問に思ったのですが、「メモをする」とか「コメントを書く」とかって、結構ニュアンスが近くてややこしいですよね。もはや使い分けをする必要があるのかも怪しいくらいです。

ということで、一通り辞書を見てみました。メモもコメントも英語由来なので、まずは英和辞書(リーダーズ第3版)から。

memo
memoはmemorandumから来ているとのことでこちらを参照。
覚書、備忘録、回覧

comment: 論評、批評、解説、説明

note: 覚書、メモ、酒器、印象記、草稿


noteとmemoはなかなか近いですが、この辞書を見る限り、noteのほうが中身がある感じがします。memoはパッと書くような感じでしょうか。commentは自分のために書くというより、誰かに意見を伝えるという意味が強いようです。

では国語辞典。(広辞苑)

メモ: 忘れないように簡単に書き留めること。また、その記録。

コメント: 事件・問題について、解説や意見を述べること。

ノート: 書きとめること。また、書きとめたもの。手記。

備忘録: 忘れたときの用心のために書き留めておくノート。手控え。

手記: 体験したことなどを自ら書き綴ったもの。


せっかくなので和英もみてみましょう。(ジーニアス和英第3版)

備忘録: notebook

ノート
帳面: notebook
記録やメモ: notes

メモ: note
彼にメモを残す leave a note for him, メモなしで話す speak without memo notes

コメント: comment

手記:
体験記: memoir
印象記: notes

備考: note
備考欄: remarks column


最後にremarkというのが出てきました。「remarkable(注目すべき)」という形容詞がありますが、動詞のremarkは「述べる、書く、一言する、論評する、感想を言う」という意味だそうです。

日本語では、自分向けの覚え書きは「メモ」と言いますが、英語では「彼にメモを残す」が「leave a note for him」となるように、「note」を使うようです。

英語に合わせるなら、まあ「ノートをとる」と言えば良いんでしょうけど、あんまり言いませんね…。

memoもnoteも覚え書きならどう使い分けるんだ、という話ですが、英英辞典をみると答えがありました。noteは「a short piece of writing to help you remember something」で、「自分が」何かを覚えておくための紙片のことです。

対してmemoは「formal」で、「an official note from one person to another in the same organization」だそうです。つまり、noteのうち、同僚などに渡す公式なものを、memoと呼んでいるようです。

どこまでも発散していきそうなので、ここらでまとめましょう。

まず、英語での使い分け。
自分用ならnote、誰かに渡すためならmemo、誰かに意見を伝えるためならcomment、という具合です。
日本語でふつうメモというものはnoteにあたり、ちょっとした連絡事項はmemo、コメントはほぼそのままcommentです。

日本語では自分のために「コメントを書いておく」という表現もできないではないですが、広辞苑でも「解説や意見を述べること」と出ているので、やはり「誰かに伝えることを前提にした」という意味合いは忘れちゃいけないですね。

ノートは中学校の英語の授業で習ったとおりnotebookですが、「ノート」と同じ意味の「備忘録」もそのままnotebookになります。

ちなみに本文には関係ありませんが、「捻挫」は英語で「sprain」、では「つき指は?」というと「sprain one's finger」、つまり「指を捻挫する」と表現するようです。

カタカナ語はもともとの語とニュアンスが微妙に変わっているものが多く、調べてみると面白いですね。

今日から本格的に梅雨っぽくなってきました。
片頭痛持ちには嫌な季節ですね。水素水でも飲んで体調を整えますか。(?)
   English    TB(0)    CM(0)    EDIT    ページ↑

順列を書き並べる

お久しぶりです。
突然ですが、今日は訳あって任意の要素数の順列を書き並べるコードを考えていました。

せっかくなので関数型プログラミングっぽく。と思っていたのですが、要素の重複があった場合のことを考えると、indexを使って実装せざるを得ないですね。

[1, 2, 3, 4, 5]から[1, 2, 3]を抽出するコードは別で準備するということで。といってもslice(0, 3)するだけです。

//重複があると動かない
var f = function(a, p, c) {
  if (p.length == a.length) return c(p);
  a.forEach(function(i) {
    if (p.indexOf(i) != -1) return;
    f(a, p.concat([i]), c);
  });
};

//重複があってもいい
var f = function(a, p, c) {
  if (a.length == 0) return c(p);
  a.forEach(function(item, index, a) {
    f(
      a.slice(0, index).concat(
        a.slice(index + 1, a.length)
      ),
      p.concat([item]),
      c
    );
  });
}

f([1, 2, 3], [], function(row) {
  console.log(row);
});


階乗の性質上、どんどんどんどん総数が大きくなっていくので、実際にこの関数が使い物になるのはほんの小さな範囲だけです。JSだとせいぜいn=10ちょっとくらいだと思います。

そんな使い物にならないコードをわざわざ載せるのはどうなんだという気もしつつ、せっかく考えたので載せておきます。
   プログラミング/開発全般    TB(0)    CM(0)    EDIT    ページ↑

Picture of the day: 猿渡公園の芝桜…?

一昨日、刈谷市の猿渡公園(ミササガパーク)というところに行ってきました。

なぜここかって、特に理由もないといいますか…

「蒲郡ほどは遠くない名古屋の東のどこか」ということで刈谷に行こうという話になり、たまたまこれを見つけたからです。

(刈谷市観光協会HP)
160501.png

HPによれば、こんな芝桜が4月下旬に咲きごろだそうです。



実際は…
IMG_20160429_104426.jpg


だいぶ寂しいですね。全然ふっさふっさじゃないです。

まだ満開じゃないのか、しおれてるのか、枯れてるのかよくわかりませんが、まあ近頃は寒暖差がやたらと大きかったですし仕方ない…のでしょうか。そういうことにしておきましょう。はい。

ちなみにこのミササガパークというのはその名の通りですが、刈谷市の姉妹友好都市の、カナダのミササガ市からとっているそうで。そこから贈られたという、こんな熊の置物がありました。

1024500.jpg

説明によれば、作品名は「THE WATER ROAD」で、「カヌーの位置は現在を暗示すると同時に、過去を確認しながら将来へ向かう道を示す」、そうです。
うーん

わかるようなわからないような…


この後、この公園から徒歩45分ほどの場所にある亀城公園というところまで歩いていきました。
合わせて3、4時間ほど。名古屋市内から数十分で行けますし、休日にちょっと散策したい(歩き回りたい?)というときにはちょうど良いと思います。

#刈谷市の魅力のほんの一部なんでしょうが… どこか良いところあったら教えてください

またまた小学生の日記のような記事ですね。
近頃イタリア料理を作っているので、そのうちまたレポートでも載せようかと思います。

そろそろ(数日おきに)本格的に暑くなってきましたが、体調には気を付けていきましょう。
   Photo    TB(0)    CM(0)    EDIT    ページ↑

LINE BOT API に Mojolicious::Lite & LWP::UserAgent で接続してみる

今朝Qiitaでみて知ったのですが、いまLINEがこんなサービスを試験しているようです。

BOT API Trial Accountのご紹介 | LINE BUSINESS CENTER
https://business.line.me/services/products/4/introduction

JSONベースのAPIを使ってBOTを実装することで、(いまのところ)50名までのユーザと、任意のタイミングでメッセージを送受信することができるというものです。 実装自体は非常に簡単で、実用化されればメールに代わる通知の手段としても使えそうです。

具体的には、
(1) ユーザからのコンタクト受諾時、ブロック時、およびメッセージ受信時のコールバックの実装
(2) ユーザへメッセージを返信するためのUserAgentの実装
という具合です。

ということで、Mojolicious::Liteで簡単に実装してみました。とは言ってみたものの、DB接続を頻繁に行うということなどがなければ、ただの一枚CGIでも十分に実装可能な内容です。
use strict;
use warnings;
use utf8;

use Mojolicious::Lite;
use JSON qw(decode_json encode_json);
use Digest::SHA qw(hmac_sha256_base64);

use LWP::UserAgent;

use Data::Dumper;

app->config(hypnotoad => {listen => ['http://*:8080']});

#アカウントの管理画面から取得
my $channel_id = 'CHANNEL ID';
my $channel_secret = 'CHANNEL SECRET';
my $bot_mid = 'BOT MID';

sub generate_msg_json_text {
    my ($mids_to_arrayref, $body) = @_;
    return {
        to => $mids_to_arrayref, 
        toChannel => 1383378250, #FIXED
        eventType => '138311608800106203', #FIXED
        content => {
            contentType => '1', #FIXED
            toType => '1', #FIXED
            text => $body
        }
    };
}

sub post_msg_json {
    my ($channel_id, $channel_secret, $bot_mid, $body) = @_;
    my $ua = LWP::UserAgent->new;
    my $res = $ua->post(
        'https://trialbot-api.line.me/v1/events', 
        'Content' => $body, 
        'Content-Type' => 'application/json', 
        'X-Line-ChannelID' => $channel_id, 
        'X-Line-ChannelSecret' => $channel_secret, 
        'X-Line-Trusted-User-With-ACL' => $bot_mid
    );
    #ToDo: think what to do
}

post '/line/callback' => sub{
    my $self = shift;

    my $key = $channel_secret;
    my $request_body = $self->req->body;
    my $signature_should_be = hmac_sha256_base64($request_body, $key);

    my $signature_got = $self->req->headers->header('x-line-channelsignature') || "";
    $signature_got =~ s/=//g;

    if ($signature_should_be ne $signature_got) {
        $self->render(json => {}, status => 470);
        return;
    }

    my $json = decode_json($request_body);
    my $results = $json->{'result'};
    foreach my $result (@$results) {
        my $content = $result->{'content'};  
        my $op_type = $content->{'opType'};
        my $new_msg_json = undef;

        #コンタクト受諾時・ブロック時
        if (defined $op_type) {
            my $msg_from = $content->{'params'}->[0];
            if ($op_type == 4) {
                $new_msg_json = generate_msg_json_text([$msg_from], 'The tutorial to enable LINE notifications is available at: http://--');
            } elsif ($op_type == 8) {
                #GOOD BYE
            }
        } else {
            my $msg_body = $content->{'text'};
            my $msg_from = $content->{'from'};
            #やまびこ
            $new_msg_json = generate_msg_json_text([$msg_from], 'hello. this is what you said: ' . $msg_body);
        }

        app->log->debug(Dumper($result));

        if (defined $new_msg_json) {
            post_msg_json($channel_id, $channel_secret, $bot_mid, encode_json($new_msg_json));
        }
    }

    $self->render(json => {});
};

app->start;


運用中のサービスへの実装はまだ検討中ですが、料金によっては(おそらくユーザ数ごとに課金される形になるかと思います)利用していきたいと考えています。
   Perl    TB(0)    CM(0)    EDIT    ページ↑

画面横幅によってviewportを分ける

久々にWEB製作関連の記事です。

最近、某CSSフレームワークとそのテンプレートを使ったWEBページのスマホ対応を行ったのですが、その中に最小横幅420pxの要素が含まれていました。具体的には横幅が420px未満の端末ではoverflow扱いの横スクロールで表示、420px以上であれば画面サイズに合わせて伸縮するという仕様です。
ここ数年でスマホが巨大化しているとはいえ、実表示領域の横幅が420pxにもなる端末はそう多くはありません。
#まあ画面解像度だけみればとっくの昔に超えていますが。。

要は、ほとんどの端末ではみ出るわけです。

というわけで、viewportの指定を使って調整することにしました。これまではおまじないのようにdevice-widthを指定してたwidthの値を、今回は少しいじってみます。

width=420にした場合、タブレット端末等で超拡大された表示になるので、見にくくなります。
それで解決として良い場合もありますが、今回はそれを避けるために少々面倒なことをします。

#そもそもviewportってなんだっけ?という方向けに念のためざっくり書いておくと、「任意の横幅の仮想的な描画領域を作って」「その仮想的な描画領域を拡大・縮小したものを、実際の端末に描画する」ことで、「画面サイズが違ってもいい感じに表示できる」という機能です。わかりにくいですね。ググるともっといい説明がたくさんあります…

はじめは、「screen.widthをとってsetAttribute/document.write」で実装しました。
しかし、ここで問題が。Android4.xではこの値が端末の実解像度になってしまう(devicePixelRatio乗じた値になる)そうです。
今回はUAで判断してどうこうという小細工は避けたかったので、もっと一般的に使える方法を検討することにしました。

その結果、window.matchMediaを使うことになりました。CSSのmediaクエリをJSから扱えるという代物で、互換性はAndroid 3.x〜、iOS5.x〜なので、今時の端末のほぼ全てに対応していることになります。

window.matchMedia - Web API インターフェイス | MDN
https://developer.mozilla.org/ja/docs/Web/API/Window/matchMedia

方法は、①width=device-widthを指定した上で、②max-widthのmediaクエリに該当するかどうかを判断する、という流れです。

コードはここまでの文章をそのまま記述したような内容です。

<meta  name='viewport'  content='width=device-width'>
<script  type="text/javascript">
(function(){
    if  (window.matchMedia && window.matchMedia("(max-width:  420px)").matches)  
        document.write("<meta  name='viewport'  content='width=420'>");
})();
</script>

#setAttributeでもいいですが、こっちのほうがちゃんと動いてくれる感じがするので(?)昔ながらのdocument.writeを使っています

あまりきれいな解決策とは言えないですね。
まあ、「工期・テンプレートの都合上どうしても」という場合にはこういう手もありだと思います。
   WEBページ制作    TB(0)    CM(0)    EDIT    ページ↑

プロフィール

JDB Luigi

Author:JDB Luigi
どこにでもいるようなありふれた人間・・・という訳でもなく、かと言って怪しい宗教を信仰する変人という訳でも無い。

基本的に掲載しているコード等は煮ていただいても焼いていただいても結構ですが、利用は自己責任にてお願いいします。
また、バグ・アドバイス等もしあればよろしくお願いします。