Blogs

    in  

    udevでAuto Mount

    USBカードリーダーにSDカードを刺すたびに、mountコマンドを打つのが億劫なので、 udevでAuto Mountするように設定してみました。

    他のディストリビューションなら、そういうところまで整備されてるんでしょうけど、 未整備が故に色々調べれるのもGentooのいいところですね。

    udevは “userspace device management” の略で、kernelが検出したデバイスに対してデバイスファイルを割り当てる仕組みです。 /sys/classを検索してdevファイルを探し、/devに対応するデバイスファイルを生成します。

    私の環境では下記のudev関連のdaemonが動いていました。

    1
    
    root      2120     1  0 15:33 ?        00:00:00 /usr/lib/systemd/systemd-udevd --daemon
    

    このdaemonはデバイスが追加されたり取り除かれたりした際のイベント、ueventをkernelから直接受け取ります。 この様子はudevadmコマンドで見ることができます。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    # kernelとudevイベントをモニター
    $ udevadm monitor
    monitor will print the received events for:
    UDEV - the event which udev sends out after rule processing
    KERNEL - the kernel uevent
    
    KERNEL[152621.041326] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    KERNEL[152621.055301] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    KERNEL[152621.065291] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    UDEV  [152621.900706] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    UDEV  [152621.913050] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    UDEV  [152621.924742] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    KERNEL[152626.399018] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    UDEV  [152626.429341] change   /devices/pci0000:00/0000:00:12.0/usb3/3-2/3-2.4/3-2.4:1.0/host7/target7:0:0/7:0:0:0/block/sdc (block)
    

    さて、SDカードを挿すと /dev/sdc1 にデバイスファイルが作成されました。 /dev/sdc1の情報を udevadmで調査します。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    # USBをさして/dev/sdc1にマッピングされた時、/dev/sdc1の情報を取得する
    sudo udevadm info --query=all --name=/dev/sdc1
    .
    .
    .
    E: ID_PATH=pci-0000:00:12.0-usb-0:2.4:1.0-scsi-0:0:0:0
    E: ID_PATH_TAG=pci-0000_00_12_0-usb-0_2_4_1_0-scsi-0_0_0_0
    E: ID_REVISION=1.00
    E: ID_SERIAL=Multiple_Card_Reader_058F63666438-0:0
    E: ID_SERIAL_SHORT=058F63666438
    E: ID_TYPE=disk
    E: ID_USB_DRIVER=usb-storage
    E: ID_USB_INTERFACES=:080650:
    E: ID_USB_INTERFACE_NUM=00
    E: ID_VENDOR=Multiple
    E: ID_VENDOR_ENC=Multiple
    E: ID_VENDOR_ID=058f
    E: MAJOR=8
    E: MINOR=33
    E: SUBSYSTEM=block
    E: USEC_INITIALIZED=218061
    

    ルールファイルを作成します。/etc/udev/rules.d/99-local.rulesです。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    # start at sdb to ignore the system hard drive
    KERNEL!="sd[c-z][1-9]", GOTO="exit"
    # flash card
    ENV{ID_VENDOR_ID}=="058f", GOTO="mount"
    # USB Storage
    ENV{ID_VENDOR_ID}=="13fe", GOTO="mount"
    
    LABEL="mount"
    ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"
    ENV{ID_FS_LABEL}=="", ENV{dir_name}="flash_drive_%k"
    
    ACTION=="add", RUN+="/bin/mkdir -p '/media/%E{dir_name}'"
    
    # filesystem-specific mount options (777/666 dir/file perms for vfat)
    ACTION=="add",ENV{mount_options_vfat}="gid=100,dmask=000,fmask=111,utf8,flush,rw,noatime,users"
    
    # mount device
    ACTION=="add", ENV{ID_FS_TYPE}=="vfat", RUN+="/bin/mount -t auto -o %E{mount_options_vfat} /dev/%k '/media/%E{dir_name}'"
    
    # exit
    LABEL="exit
    

    作ったルールをテストしてみます。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    # ルールをテストする
    udevadm test $(udevadm info -q path -n /dev/sdc1)
    .
    .
    .
    ID_VENDOR=Multiple
    ID_VENDOR_ENC=Multiple
    ID_VENDOR_ID=058f
    MAJOR=8
    MINOR=33
    SUBSYSTEM=block
    USEC_INITIALIZED=218061
    dir_name=Eye-Fi
    mount_options_vfat=gid=100,dmask=000,fmask=111,utf8,flush,rw,noatime,users
    run: '/bin/mkdir -p '/media/Eye-Fi''
    run: '/bin/mount -t auto -o gid=100,dmask=000,fmask=111,utf8,flush,rw,noatime,users /dev/sdc1 '/media/Eye-Fi''
    unload module index
    Unloaded link configuration context.
    

    /media/Eye-Fi にmountされました。


    インフラチームにジョブチェンジした

    今年を振り返るという意味で、今年からプログラマからインフラチームにジョブチェンジして感じたことを書こうと思います。 本格的には10月からインフラチームで仕事しているので、3ヶ月くらい経った事になります。

    自分も既に38才にもなったのでジョブチェンジっていうのはなかなか過酷な道だとも思いましたが、 うちの会社は開発から運用まで一気通貫でやっているので、まだジョブチェンジしやすい環境でした。 むしろアプリケーションの事を余り知らず、低レイヤーしか知らないっていうのは、うちのインフラチームでは通用しないと感じています。

    これまでの仕事

    プログラマと一口に言っても色々あると思いますが、これまでは基盤寄りの仕事をやってました。 アーキテクチャ考えたり、基盤プログラム書いたり、進捗管理したり。 アーキテクチャを考えたりする上ではインフラにもいくらか足をつっこむので、インフラなんてまったく知らないわけではなかったです。 その点はジョブチェンジには有利でした。 後、Linuxは好きで色々いじってたのでその辺のハードルは低かったです。

    インフラチームになってから

    タイミングよくプライベートクラウドの再構築に携われました。 仮想化はVirtualBoxとかKVMとかは扱ったことがあったのですが、お遊びとか開発環境とかライトなものでした。 業務で扱う仮想化は、機器もごついし、可用性・信頼性・パフォーマンス、どれをとっても別次元でした。

    後は、何気なく使っている用語とかコマンドの理解があやふやだったと痛感する毎日です。 これが一番痛い。ほんとにいろんなことをもっとちゃんと理解するように日々心がけるべきでした。

    ちなみにプログラムをまったく書いていないわけでなく、AWS周りのプログラムとかも書いてます。

    どんな人がインフラに向いているのか

    最近良く考えるテーマです。 技術的な探究心がどうとか言うのは、プログラマも同じだと思うのでもうちょっと心構え的な所で。

    • 石橋をこわれない程度に叩ける
    • 動いて当たり前な周囲の雰囲気に飲まれない
    • インフラを動かしてるのは僕らだよと、そこはかとなくアピールできる

    これから

    AWSとかWindows Azureを扱った事があるので、パブリッククラウドの経験を生かして、 うちのインフラを飛躍させていきたいなと思います。 特にAWSのストレージ系のサービスは、色々割り切ってて素敵なサービスだと思います。 うまく連携させていきたいです。

    あとはchefとかAWSの様々なサービスとか、色々やりたいことは山積みですが、 どうしてもL4より下のレイヤーが弱いことは明白で、そこが弱いままなのはさすがに致命的です。 この弱点を埋めていくのが今後の課題です。


    in  Clojure

    CDDBサーバーに検索クエリーを投げる

    WAVファイルからCDDBへ検索クエリーを投げるプログラムを作ってみました。 色々試しながらプログラミングするには、EmacsでClojureな環境はホントいいですね。

    CDDBへクエリーを投げる方法はここを参考にさせてもらいました。

    CDDBへクエリーを投げるには曲長を基に算出したdiscIdが必要になります。 さらに各曲のフレームオフセット数も必要です。フレーム数とは1秒辺り75フレームとなる値になります。 フレームオフセットとは、各曲が先頭から何フレーム目から開始されるかを表します。 これらを算出するためにjavax.sound.sampled.AudioSystemクラスを利用しました。 こんなクラスがあったんですね。 曲長ををだして、1秒辺りのフレーム数75をかけています。

    1
    2
    3
    4
    5
    
    ;; オーディオファイルのフレーム数を出す
    (defn get-frame [file]
      (let [audio (javax.sound.sampled.AudioSystem/getAudioFileFormat file)]
        (* (/ (.getFrameLength audio)
              (.getFrameRate (.getFormat audio)))  75)))
    

    まずdiscIdを算出します。 曲長を秒で算出して、桁毎に足しあわせます。 例えば185秒の曲長だったら、1+8+5=14 という具合です。 これをアルバムの全曲に対して行なって、足しあわせたあとに255で割ったあまりをだします。(A)

    次に全曲長を算出します。(B)

    最後に曲数を算出します。(C)

    これらをビット演算します。 A « 24 | B « 8 | C

    discIDを算出するClojureのコードです。HelloweenのTime Of The OathのCDをwavファイルに落としているものをCDDBへ問い合わせています。 拡張子wavのWAVファイルがトラックナンバーをファイル名として保存されている前提です。 例えば1曲目はTrack1.wavというファイル名で保存されている前提です。

     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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    
    ;; 1曲目のオフセット
    (def init-offset 187)
    
    ;; .wavファイルでフィルター
    (def files (filter #(re-find #".*wav$" (.getName %))
                       (file-seq (clojure.java.io/file "/home/takeshi/scorpions_eye2eye/"))))
    
    ;; Track numberでソートする
    (defn sorted-files [file]
      (sort-by #(Integer. (re-find #"d+" (.getName %))) file))
    
    
    ;; フレームオフセットを計算する
    (defn get-frame-offset [seq]
      (letfn [
              (sum [seq sum-num sum-seq]
              (if (= (count seq) 1)
                (sort sum-seq)
                (let [res (+ (first seq) sum-num)]
                  (sum (rest seq) res (cons res sum-seq)))))]
      (sum (cons init-offset seq) 0 '())))
    
    
    
    ;; フレーム数から10進数の桁毎に足した合計を算出する
    (defn sum-frame [frame]
      (letfn [
            (digit  [div-frame sum]
              (let [int-div-frame (bigint div-frame)]
                (if (< div-frame 1)
                  (math/floor  sum)
                  (digit (/ int-div-frame 10.0) (+ sum (- int-div-frame (* (math/floor (/ int-div-frame 10.0)) 10)) ) ))))]
        (digit frame 0)))
    
    ;;
    (def A
      (mod
       (reduce + (map #(sum-frame (/ % 75))
                      (get-frame-offset (map #(get-frame %) (sorted-files files))))) 0xff))
    
    ;; 曲長
    (def B
      (reduce + (map #(/ (BigDecimal. (.getFrameLength %))
             (.getFrameRate (.getFormat %))) (map #(javax.sound.sampled.AudioSystem/getAudioFileFormat %) files))))
    
    ;; 曲数
    (def C
    (count files))
    
    
    ;; discid
    (def discid
      (Long/toHexString (bit-or (bit-shift-left (long A) 24) (bit-shift-left (long B) 8) C)))
    

    フレームオフセットを+でつなぎあわせた文字列を生成します。 ;; オフセット

    1
    2
    
    (def offset-string
      (clojure.string/join "+" (map long (get-frame-offset (map #(get-frame %) (sorted-files files))))))
    

    discId、フレームオフセット、全曲長、曲数をつなぎあわせて、CDDBへの検索クエリーを生成します。

    1
    2
    
    (with-open [rdr (clojure.java.io/reader (str "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=cddb+query+" (clojure.string/join "+" (list discid C offset-string (int B))) "&hello=username+hostname+clripper+R0.1&proto=6"))]
      (prn "%sn" (str "n" (line-seq rdr))))
    

    クエリーの結果はこんなかんじです。

    1
    
    "%sn" "n("200 rock d510710e Helloween / The Time Of The Oath [Japan Bonus Tracks]")"
    

    ちゃんとした実行形式として作れてないですが、CDDBを検索するときに何をやっているかよくわかりました。 疑問点としては1曲目のオフセットが150で固定にしていますが、187のアルバムもあるようです。 一度wavファイルに落としちゃうと、算出は難しいようです。


    音楽ファイルに日本語のid3タグをつける

    CDからリッピングする時は、コマンドラインで動作するripを使っているのですが、こいつが日本語のアルバム名とか曲名をうまく扱えなかったので調べて見ました。

    リッピングの流れは、最初にCDDBと呼ばれるアーティスト名やアルバム名をWEBサービスで取得する仕組みを使います。 CDDBで取得したアーティスト名やアルバム名から、リッピングしたファイルを保存するディレクトリを決定します。 ripでは[アーティスト名]/[アルバム名]の形式で作成されたディレクトリの下に、ファイルを置きます。 各ファイルにはid3タグと呼ばれる形式で、音楽ファイルにアーティスト名やアルバム名、曲名等の情報を加えます。 このid3タグの情報を、ituneやAmarok等の音楽プレーヤーが読み取って、曲名を表示したりしているわけです。

    文字化けの問題を調査するにあたり、まずCDDBで取得した情報が文字化けしていないかを調べました。 freedb.freedb.orgにクエリを投げると、CDの情報を取得するこの仕組みはCDDBと呼ばれ、ライブラリが提供されている言語もあります。 さてCDDB、プロトコルを指定することができ、プロトコルの値が6でないと日本語がうまく扱えないことがわかりました。 ripはperlで実装されており、CDDBへのアクセスはCDDB_get.pmを用いていました。CDDB_get.pmへプロトコルを指定する口があるので、簡単にプロトコルを変更することができました。

    1
    2
    3
    4
    5
    6
    7
    
    # CDDBに渡す設定を生成するときに、プロトコルを6で指定する。
    my %config;                                       # Configuration passed to CDDB/CDDB_get
    $config{PROTO_VERSION}      = 6;                  # CDDB protocol version
    ....
    
    # CDDBに設定を渡す
    %cd = get_cddb( %config );
    

    プロトコルの変更を行うことで、rip実行時に日本語が文字化けしていたのが直りました。

    これで解決かと思いきや、実際に音楽プレーヤーに表示されている情報は相変わらず文字化けしていました。 次に疑うべきは、id3タグを書き込んだ際の文字化けです。 id3にはバージョンがあり、バージョン1は日本語文字コードの問題が起こりやすいそうです。 という訳でUTF8がサポートされているバージョン2で書きこむように修正しました。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    BEGIN { $ENV{MP3TAG_USE_UTF_16LE} = 1; }
    MP3::Tag->config(id3v23_unsync => 0);
    my $mp3 = MP3::Tag->new($file) or die;
    $mp3->get_tags() or die;
    
    my $id3v2 = (exists $mp3->{ID3v2}) ? $mp3->{ID3v2} : $mp3->new_tag("ID3v2");
    
    # TIT2 : Title/songname/content description
    # TALB : Album/Movie/Show title
    # APIC : Attached picture Keys: MIME type, Picture Type, Description, _Data
    
    print STDERR "DEBUG: ID3V2 add framen" if $debug;
    $id3v2->add_frame("TIT2", $song);
    $id3v2->add_frame("TPE1", $artist);
    $id3v2->add_frame("TALB", $title);
    $id3v2->add_frame("TYER", $year);
    $id3v2->add_frame("TRCK", $track);
    $id3v2->add_frame("TCON", $genre);
    
    print STDERR "DEBUG: write ID3V2 tagn" if $debug;
    $id3v2->write_tag or die("E: tag write error, $filen");
    print STDERR "DEBUG: close mp3n" if $debug;
    $mp3->close();
    

    これで無事に日本語の情報が音楽プレーヤーで表示されました。 確認したのはituneとAmarokです。 いやー、Perl久しぶりでしたよ。


    in  Clojure

    Clojureの開発環境構築

    今年はClojureを頑張ることにしてます。

    以前触ったときはswank-clojureがemacsでのClojure開発では鉄板のようでしたが、時は変わって今はnreplが良いようです。 Clojure + Emacsの開発環境は流れがよく変わるので注意が必要ですね。 今のところはnreplが一番将来性が有りそうということのようです。

    インストール方法はEmacs標準のパッケージインストーラーでお手軽にインストールできます。 package-list-packageでnreplがリストアップされるのでインストールします。 Emacs24からはパッケージのインストールが本当に便利になったもんです。

    nreplはleingenと連動して動くので、leingenのインストールも必要です。 leingenのインストールは以前やってるので省略

    インストールが終わったら、M-x nrepl-jack-in すればreplが起動します。 後は C-x C-e でガシガシ開発する感じ。

    ほんと簡単になったもんだ。


    in  Linux

    Nicが2つあるLinuxマシンのルーティング設定

    前回、デスクトップPCにNASを接続してファイルを高速に転送できるようにしました。 クロスケーブルを用意しなくてよかったのですが、ルーティングの設定は行わないと通信できませんでした。

    無線LANの親機に有線接続していたNASを、そのままデスクトップ機に有線で接続しました。 デスクトップについてる無線と有線のLanのIPとNASが、同じセグメント(192.168.3.0/24)で接続されることになります。 このままデスクトップとNASを通信しようとすると、疎通できません。 有線を経由で通信してくれませんでした。

    そこで、ipコマンドでルーティングの設定を行いました。

    1
    2
    3
    4
    
    $ ip rule show
    0:      from all lookup local
    32766:  from all lookup main
    32767:  from all lookup default
    

    ルーティングルールを表示させています。 数字が若いほうが優先的に適用されるルールテーブル。 上記だとルールテーブル"local"が優先度0で一番優先的に扱われています。

    NAS(192.168.1.100)へのルールをテーブル100に優先順位200で追加します。

    1
    2
    
    $ sudo ip rule add from 192.168.1.100 table 100 prio 200
    $ sudo ip rule add to 192.168.1.100 table 100
    

    NAS(192.168.1.100)へのルートを有線LANであるeth0経由のルーティングにしています。

    1
    2
    3
    
    $ sudo ip route add 192.168.1.100 dev eth0 table 100
    $ ip route show table 100
    192.168.1.100 dev eth0  scope link
    

    設定後のルールテーブルとルーティング設定です。

    1
    2
    3
    4
    5
    6
    
    $ ip rule show
    0:      from all lookup local
    199:    from all to 192.168.1.100 lookup 100
    200:    from 192.168.1.100 lookup 100
    32766:  from all lookup main
    32767:  from all lookup default
    
    1
    2
    3
    4
    5
    
    $ ip route show
    default via 192.168.1.1 dev wlan0  metric 2003
    127.0.0.0/8 via 127.0.0.1 dev lo
    192.168.1.0/24 dev wlan0  proto kernel  scope link  src 192.168.1.3
    192.168.1.0/24 dev eth0  proto kernel  scope link  src 192.168.1.30
    

    これで通信できるようになりました。 ただし、この設定は再起動すると消えてしまいます。


    クロスケーブルはいつの間にオワコンになったのか

    ここ最近で一番驚いた。

    PC同士をつなぐときにはクロスケーブルが必須だと思ってました。

    事の発端は、デスクトップPCにノートPCのファイルを転送しようとした時でした。

    デスクトップPCにsambaサーバーを立てて、ファイルを転送しようとしたのですが、無線Lan環境ではなかなか速度が出ず困っていました。 デスクトップのWifiは1IEEE 802.11bgなんですよね。そりゃ遅いわけです。

    じゃぁデスクトップの有線Lanポートが空いてるし、デスクトップとノートPCを直接つないじゃおうと思うわけですが、てっきりクロスケーブルがいるものだと思ってました。 ところが、GigabitなNICはオートMDI/MDI-Xという機能でPC同士をつないでいるのかハブとかをつないでいるのかを自動で判別してくれるそうで。 ストレートケーブルでPC同士をつないでも、NICが自動でクロスケーブル相当の事をやってくれるわけです。

    ちなみにNICの相性もあったりするらしいので、今回つないだNICを書いときます。

    NICOS
    Realtek RTL8111/8168 PCI Express GigabitLinux
    Intel 82577 GigabitWindows7

    in  Java

    DIが普通になった時代のモックツールはMockitoで決まりですね

    ここ2,3年、お仕事ではmockitoが鉄板のモックツールになっています。

    4年くらい前にはjMock とかはEasymock とかを試してたけど、mockitoの使いやすさには敵わない感があります。

    モック対象のクラスにインターフェースがなくても簡単にモック化できるのがお手軽なところ。 最近ではDIを使うことがごく普通になりましたが、インジェクションされるオブジェクトがインターフェースを持たないことが多くなりました。 モック化されるオブジェクトはDIされているオブジェクトであることがおおいので、DIと親和性があることは使いやすさに直結しますね。

    Mavenを使っている場合はpom.xmlの設定は下記のようにすればよいです。

    1
    2
    3
    4
    5
    6
    
    <dependency>
           <groupId>org.mockito</groupId>
           <artifactId>mockito-all</artifactId>
           <version>1.9.5</version>
           <scope>test</scope>
    </dependency>
    

    フィールドインジェクションしている時のモック化

    まずはフィールドインジェクションの場合。 例えば以下のようなテスト対象があるとします。 よくあるアノテーションベースでインスタンスをインジェクションするようなソースです。 CommentDaoのインスタンスがフィールドインジェクションされています。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    public class CommentService {
        @Inject
        private CommentDao commentDao;
    
        /**
         * データを再読み込みする。
         */
        public void refleshData() {
            commentDao.loadData();
        }
    }
    

    テストコードは下記のようになります。 テスト対象と同じフィールドシグネチャーでテストクラスに記述し、@Mockアノテーションをつけます。 これだけでフィールドに宣言されたクラス(CommentDaoクラス)をモック化できます。

    テスト対象のクラスには@InjectMocksアノテーションを付与し、 setupメソッドでMockitoAnnotations.initMocks(this)を実行しておきます。 これだけでモックオブジェクトがテスト対象のクラスにインジェクションされます。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    public class CommentServiceTest {
        @Mock
        private CommentDao commentDao;
        @InjectMocks
        private CommentService commentService;
    
        @Before
        public void setup() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void verifyDaoCall() {
            commentService.refleshData();
            verify(commentDao, times(1)).loadData();
        }
    
    }
    

    フィールドインジェクションでがしがし実装しているのと同じ感覚で、テストクラスをかけるのが非常にリズムがいいですね。

    コンストラクタインジェクションしている時のモック化

    モック対象のクラスがコンストラクタインジェクションされている場合は下記のように作ることもできます。

    1
    
    commentDao = mock(CommentDao.class);
    

    このインスタンスをコンストラクタの引数に渡してnewしちゃえばいい訳です。