意外と知らないMacの便利なショートカットキー

Macではショートカットを覚えておくと作業が結構楽になることが多々あります。どこにも載っていないような、そんな便利なショートカットを紹介したいと思います。「」で囲まれているのはショートカットキーの組み合わせです。

ファイルを開く

Finderでファイルを選択後、 「Cmd + O」

ファイルを削除

Finderでファイルを選択後、「Cmd + delete」

画像のプレビュー

Finderで画像ファイルを選択後、「スペース」
スクリーンショット 2013-01-13 3.11.25

Dockで画像をプレビュー

Dockでフォルダを開き、画像にカーソルをあわせて「スペース」
スクリーンショット 2013-01-13 1.30.21

タブでアプリケーションを切り替え中にウィンドウも切り替える

「Cmd+Tab」でアプリケーションにフォーカスを合わせた後、そのままキーを離さずに矢印キーの「↑」または「↓」を押してウィンドウを選択し、全てのキーを離し「Enter」

スクリーンショットの部分撮影

「Shift+Cmd+4」のあと部分選択

スクリーンショットの部分クリップボードコピー

「Ctrl+Shift+Cmd+4」のあと部分選択

ウィンドウの部分撮影

「Shift+Cmd+4」のあと、カーソルをウィンドウに合わせ「スペース」を押し「クリック」

ウィンドウの部分クリップボードコピー

「Ctrl+Shift+Cmd+4」のあと、カーソルをウィンドウに合わせ「スペース」を押し「クリック」

スクリーンショット部分撮影時に撮影サイズを維持したまま領域のみ移動する

部分選択を維持しながら(マウスを離さずに)「スペース長押し」

行の最初に移動

テキストエディタ上で、「Cmd+←」

行の最後に移動

テキストエディタ上で、「Cmd+→」

一行選択

テキストエディタ上で、「Cmd+←」、「Shift+Cmd+→」
もしくはマウスで「トリプルクリック」

ファイル保存画面でデスクトップにジャンプ

「Cmd+D」
スクリーンショット 2013-01-13 3.22.42

Finderまたはファイル保存画面で指定したディレクトリに移動

「Shift+Cmd+G」 または「/」、「~」(保存画面のみ)
スクリーンショット 2013-01-13 3.23.21

しかもタブキーでディレクトリを補完してくれます。
~/Destop というように~を使うとホームディレクトリからパスが開始されます。

  • ~/des と入力後「Tab」(Cmd+D) → デスクトップ
  • ~/dow と入力後「Tab」→ 書類
  • ~/pic と入力後「Tab」→ ピクチャ
  • ~/use と入力後「Tab」→ ユーザーフォルダ一覧
  • ~/mus と入力後「Tab」→ ミュージック
  • ~/dow と入力後「Tab」→ ダウンロード
  • ~/mov と入力後「Tab」→ ムービー
  • ~/lib と入力後「Tab」→ ライブラリ

Fiderで目的のファイルまたはフォルダにフォーカスを当てる

Finderを開き、「Mail」というフォルダにフォーカスを当てたい場合は、早く「ma」とキーボードで入力するとそのファイルまたはフォルダにフォーカスが当たる。そして「Cmd+O」を押せばすぐにファイルまたフォルダが開ける

フォルダのサイズを簡単に調べる

Finderでフォルダにフォーカスを当て、「スペース」
スクリーンショット 2013-01-13 3.42.02

保存しますか?確認画面ですぐキャンセルする

「Cmd+D」
スクリーンショット 2013-01-13 3.43.38

タブ切り替えでアプリケーションを終了

「Cmd+Tab」でアプリケーション切り替え中にタブキーを離して「Cmd+Q」
Cmdを押しながらタブキーとQを連打すれば全てのアプリケーションを消せる。

アプリケーションの強制終了

「Option」を押しながらドックでアプリケーションを右クリックし、メニューから強制終了。

画像のサムネイル一覧表示

Finderで画像のたくさんあるフォルダを開き、「Cmd+A」のあと「スペース」。
そして、このボタンを押す。
スクリーンショット 2013-01-13 3.47.49

フルスクリーンで画像のサムネイル一覧表示

Finderで画像のたくさんあるフォルダを開き、「Cmd+A」のあと「Option+スペース」。
フルスクリーンでプレビューが表示されるので、すぐに下のボタンを押す。 
スクリーンショット 2013-01-13 3.50.53

再生ボタンを押せばスライドショーも楽しめる。

スライドショー

Finderで画像のたくさんあるフォルダを開き、「Cmd+A」のあと「option+Cmd+Y」

ショートカット(エイリアス)の作成

Finderでファイルを選択後、「Cmd+L」

ファイルの複製

Finderでファイルを選択後、「Cmd+D」

エイリアスのオリジナル表示

Finderでエイリアスを選択後、「Cmd+R」

新しいフォルダを作る

Finderで「Shift+Cmd+N」

Finderのサイドバーにフォルダ、アプリケーション、ファイルを追加する

Finderでフォルダ、ファイル、アプリケーション、ファイルを選択後、「Cmd+T」
通常のドラッグ&ドロップではファイルは、ファイルとアプリケーションが追加できないので大変便利です。

Finderでファイルの表示方法を切り替える

アイコン表示「Cmd+1」
一覧表示「Cmd+2」
カラム表示「Cmd+3」
プレビュー表示「Cmd+4」

ウィンドウを隠す

「Cmd+H」

デスクトップにジャンプ

Finderで「Cmd+Shift+D」

マイファイルにジャンプ

Finderで「Cmd+Shift+F」

ルートディレクトリにジャンプ

Finderで「Cmd+Shift+C」

ホームディレクトリにジャンプ

Finderで「Cmd+Shift+H」

アプリケーションディレクトリにジャンプ

Finderで「Cmd+Shift+A」

ユーティリティディレクトリにジャンプ

Finderで「Cmd+Shift+U」

ネットワークディレクトリにジャンプ

Finderで「Cmd+Shift+K」

AirDropにジャンプ

Finderで「Cmd+Shift+R」

書類にジャンプ

Finderで「Cmd+Shift+O」

ダウンロードディレクトリにジャンプ

Finderで「Option+Cmd+L」

現在アクティブなウィンドウのみ表示し、他のウィンドウを隠す

「Cmd+Option+H」

前の階層のディレクトリに移動

Finderで「ウィンドウタイトルを右クリック」
スクリーンショット 2013-01-13 4.00.41

スリープせずにディスプレイだけオフにする

「Ctrl+Shift+⏏」
ホットコーナーに設定する方法も便利です。

スリープする

「Cmd+Option+⏏」

テキストエディタでカーソルのある位置にウィンドウをスクロールする

「Ctrl+L」

テキストエディタで周囲の書式設定に合わせてペーストする

「command+shift+option+V」

細かくボリュームを調整する

「shift+option」を押しながらボリューム調整キーを押す。

細かく明るさを調整する

「shift+option」を押しながら明るさ調整キーを押す。

出力装置と入力装置を切り替える

「option」を押しながらスピーカーアイコンをクリック
スクリーンショット 2013-01-13 4.38.08

番外編: ファイルのパスを取得

ターミナルを起動後 ターミナルにファイルまたはフォルダをドラッグ&ドロップ
スクリーンショット 2013-01-13 3.38.43

番外編: ターミナルでファイルまたはフォルダまたはアプリケーションを開く

open を使う。フォルダの場合はFinderでフォルダが開かれ、ファイルの場合はファイルに関連付けられたアプリケーションが起動する。アプリケーションの場合はそのままアプリケーションが起動する。
スクリーンショット 2013-01-13 3.39.38

Mac OSXでタイムマシーンのバックアップが重い

Macで作業をしているとタイムマシーンが自動的に実行されて、タイムマシーンでバックアップ中にPCがよく固まるようになってきました。バックアップ先の外付けHDDの性能が悪いのか、単純にMacの内蔵HDDの性能が悪いのかわかりませんが、ひとつ気づいた点として毎回新たにバックアップされるサイズが多いことに気づきました。

スクリーンショット 2013-01-20 18.32.15

一時間毎の実行にもかかわらず 4.46GBくらいバックアップしようとします。さすがこれは多すぎますよね。ファイルをダウンロードしたわけでもないのになぜかこの容量。なぜだろうと重い考えてみると、一つピンとくることが。
もしかしてキャッシュまでバックアップしてるんじゃないか・・・
実際、タイムマシーンの設定を見ると、除外フォルダがSpotlightのデータと、タイムマシーンのバックアップ先ディスクのみになっていました。つまりライブラリフォルダが復元作業のために必要としても、そのライブラリフォルダの中にある、Chromeのキャッシュフォルダやテンポラリフォルダまでもがバックアップの対象になっていたようです。

つまり、タイムマシーンでのバックアップ作業中にChromeが動かなくなったり固まったりするのは、キャッシュフォルダをタイムマシーンがロックをかけChrome上からキャッシュにアクセスできなくなっていたのが問題だったのではないかと予測することができます。もちろんあくまで推測ではあるのですが、この考えを元にタイムマシーンからキャッシュフォルダ、テンポラリフォルダ、そして忘れてはいけないのがlogフォルダを除外対象にします。

キャッシュ、一時、ログフォルダの除外

スクリーンショット 2013-01-20 18.32.48

タイムマシーンを開いたら「オプション」をクリック。

スクリーンショット 2013-01-20 18.33.56

何にも除外対象になっていません、キャッシュもログもおもいっきりバックアップされてしまいます。これはひどいですね。「+」を押して除外したいフォルダを選択します。

スクリーンショット 2013-01-20 18.35.11

ディレクトリを 「Machintosh HDD(SSD)」 にしたあと、検索バーに「cache」と入力します。

検索対象に 「Machintosh HDD(SSD)」 を選択します。

種類でフォルダを選択します。

スクリーンショット 2013-01-20 18.36.30

スクリーンショット 2013-01-20 18.36.56

キャッシュフォルダがたくさん出てくるので全て選択して除外をクリックします。

これをあと二回繰り返して「logs」、「temp -template」を除外対象にします。

すると、一時間ごとのバックアップ時にバックアップされるサイズがかなり減ります。
さすがにバックアップ時に少しだけ重くなってしまうは仕方ないですが、すぐにバックアップが終わるようになるのでかなり快適に操作することができるようになります。

最後に

バックアップから除外したことによって復元に失敗してしまう可能性も少なからずあるので、自己責任でお願いします。

HTML5 History API を徹底的に試してみる

History APIとは

History APIはHTML5の機能のひとつで、ブラウザの戻る進むボタンのイベントを取得してページの内容を動的に変えることができるものです。
なかなか便利な機能ではあったのですが、ちょっとつまずきポイントが多く、癖もかなりあるっぽいので徹底的に試してみようと思います。

スタック

ブラウザの履歴の一つ一つの記録をスタックといい、一つ履歴が増える度にスタックが増える。そしてHistoryAPIを使うとこのスタックをページ推移を行わなくても増やすことができます。

HistoryAPIによってスタックが増えた場合は、ブラウザの戻るボタンを押してもページ遷移が発生せず、何も起きない。それがHistory API。

スタックの追加

スタックを追加するには、このようにやります。

history.pushState("hoge", null, "/hoge");

これで、履歴が一つ分増えます。これを実行すると、ブラウザの戻るボタンを一度押しても何も起きません。(ページ推移が発生しません)
しかし、二回ボタンを押すといつもどおりページ推移が発生します。

しかし、このhistory.pushStateを使う前に必ず

history.replaceState("index");

とします。

これをすることでGoogle Developer Tools もしくは Firebug上で

history

と入力し、エンターを押すと、historyオブジェクトが見えるので、historyオブジェクトを展開し、stateプロパティを確認すると、そこがindexになっていることが確認できます。

スクリーンショット 2013-01-18 0.11.15

必ずこれをやっておかないとめんどくさいことになります。

ブラウザの戻る・進むイベントを監視する

jQueryを使っていますが、jQueryの場合はこのように書くことでイベントが実装できます。

$(window).on("popstate", function(_event){ var state = _event.originalEvent.state; console.log("_event", _event); console.log("state", state);
});

これをソースコード内に記述して、

history.pushState("hoge", null, "/hoge");

を実行して、ブラウザの戻るボタンをクリックします。

Google Developper Tools または Firebugのコンソールで確認すると、 state がindexになっていることがわかります。

スクリーンショット 2013-01-18 0.16.45

これがハマりポイントでした。最初に history.replaceState(“index”); をしたことで、index と表示されたわけですが、これをやらないとここが、 null になってしまいます。

最初このstateの値は hoge になるものだと思っていたのですが、実はそうではなく、戻るページに設定されているstateらしいです。

構造的には「index -> hoge」 という構造になっています。

このあたりがややこしいので注意が必要です。

Tips

History APIは基本的に以上の仕組みさえ理解すればそれだけですんなりと使えますが、他にも覚えておくと便利な点をあげておきます。

history.state

現在のstateを確認できます。indexなのかhogeなのか。

history.back()

これを実行するとブラウザを戻ります。ページ内に戻るボタンを設置する場合はこのメソッドを使用します。ただ、対応していないブラウザもあるので機能を切り分ける必要があります。

history.forward()

これを実行するとブラウザを進みます

history.go(num)

指定した履歴へジャンプします。

history.length

現在の履歴の数を表示します

location.pathname

現在のドメインを覗いたディレクトリパスを取得します

location.search

現在のURLの?の後ろのパラメータを取得します

個人情報を持たないWebサービスの設計

Webサービスを作るにあたってどうしても考えなければならないのが、個人情報の取り扱いについて。メールアドレスや名前をもらっただけでもそれだけでも個人情報になるし、IDやパスワードを与えても個人情報になる。Webサービス自体はとてもおもしろいものなのに、信頼できないサイトだった場合使ってくれないこともしばしばある。

このめんどくさい個人情報の取り扱いをなんとかしてパスする方法はないものかとこの記事では考えてみることにする。

個人情報とは何か

Webサービスを作るにあたって最低限必要なものを上げてみる。

  • お名前
  • メールアドレス
  • ID
  • パスワード
  • セッションID

最低限、このくらいは必要になるだろう。最後のセッションIDというのは、今ログインしているかどうかを判断するのに使う。クッキーで保持されているもののことです。

では、この5つの項目をパスすれば、個人情報をもたないWebサービスを作ることができるのではないかと考えてみる。

コンセプト

そもそもなぜ個人情報が必要なのかというと、自分専用のページを持つということにつきると思う。マイページ。Webサービス上で自分という存在をアピールすることができることがどんなWebサービスにおいても共通解であると思う。例えば、はてなブックマークも自分がはてブしたりすることで、そのWebページに対してコメントを書き加えられる。チラ裏みたいなことができる。

そういったものを提供できなくては、Webサービスとして成り立たないのではない。だけども、それをやってしまうと個人情報を持つことになる。ではどうすべきか・・・、はい、考えました。

限定公開URLという考え方

ユーザーに対してあなた専用のページを与えるのではなく、URLさえ知らなかければ誰もアクセスできないページを提供するということです。IDやPWなどは一切必要とせず、その限定公開URLをユーザーに対して提供する。しかも、仕様上何個でも限定公開URLを発行できる。ボード

誰にもURLさえ教えなければ、それを自分専用に使うこともできるし、他の人に共有することもできる。

限定公開URLにとどまらない

しかも、URLに限定することも実はなくて、「アイデンティケーションコード」を一つだけ持っていればそのコードに対してAPIによってPOSTしたりGETしたりすることによって、例えばChrome拡張にそのコードを登録するだけで、使えるようなサービスが運用できる。ちなみに、アイデンティフィケーションはIDの略なので、IDCODEという意味になってしまうが、ここはIDとは差別化したいのでそう呼ぶことにした。

ボード

この限定公開URLによって運用するWebサービスのことをこの記事では以下「ボード」と呼ぶことにします。限定公開URLがひとつのボードとして提供されるという意味からです。

問題点

ただ、ボードを提供するにあたって問題点となるのはやはり、限定公開URLなのにもかかわらず、第三者によって傍受されてしまう危険性があるということ、なので日記サイトとか、個人情報に密接に関わりそうそうなWebサービスはできるだけ避けたほうがいい。HTTPSを使わない場合は、更にそのリスクが高まる。

ではどんなWebサービスがいいのか

診断メーカーなどは、名前(ニックネーム)を入力するだけですぐに結果がもらえる。そういったWebサービスであれば、個人情報を持つリスクが最小限に抑えられる。

OAuthというやり方も

実はHTTPSを使わくてもある程度個人情報を保護できるやり方があり、OAuthを使ってログインすると、限定公開URLを使わなくてもある程度安全なWebサービスを運用できる。診断メーカーもそのうちの一つ。

プロジェクト管理ソリューション「JIRA」を試す

前から気になっていたプロジェクト管理ソリューションJIRAを試してみようと思います。プロジェクト管理と言えばRedmine等のBTSとかリポジトリ管理とかそんなかんじのものを思い浮かべますが、JIRAもそれと同じようなものであるようです。

しかも高機能な上に、他のプロジェクト管理ツールからチケットをインポートできる機能も併せ持つ今話題のプロジェクト管理ツールだということですから、これは見逃せません。しかも安い。

というわけで使ってみた。

お試しということで体験版をダウンロードします。

# ダウンロード先
http://www.atlassian.com/ja/software/jira/download
cd /usr/local/src/
wget http://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-5.2.4.tar.gz
tar zxvf atlassian-jira-5.2.4.tar.gz
cd atlassian-jira-5.2.4-standalone/

中身を見てみる

.
├── NOTICE
├── README.html
├── README.txt
├── atlassian-jira
├── bin
├── conf
├── external-source
├── lib
├── licenses
├── logs
├── temp
├── tomcat-docs
├── webapps
└── work

README.txt があるので vim で開いてみます。

vim README.txt
BRIEF INSTALL GUIDE
-------------------
1. Install Oracle's (formerly Sun's) Java Development Kit (JDK) or Java Runtime Environment (JRE) version 1.6 or above: http://www.oracle.com/technetwork/java/javase/downloads/index.html
2. Set the JAVA_HOME variable to where you installed Java. The Windows and Linux installers will do this for you. See the following instructions for details: http://docs.atlassian.com/jira/docs-052/Installing+Java
3. Set your JIRA Home Directory. Instructions on how to set your JIRA Home Directory can be found here: http://docs.atlassian.com/jira/docs-052/Setting+your+JIRA+Home+Directory
4. Run 'bin\start-jira.bat' (for Windows) or 'bin/start-jira.sh' (for Linux/Solaris) to start JIRA. Check that there are no errors on the console. See below for troubleshooting advice.
5. Point your browser at http://localhost:8080/ You should see JIRA's Setup Wizard.
Full documentation is available online at:
http://docs.atlassian.com/jira/docs-052/Installing+JIRA

なるほど・・・。これは敷居が高そうだ・・。
とりあえず1番から進めていきます。

JREのインストール

JIRAを動かすにはJavaが必要なので、 ここからJDKかJREをダウンロードします。私はJDKを選択しました。

そしてインストール

tar xzvf jdk-7u10-linux-x64.gz
cd jdk1.7.0_10/
mv jdk1.7.0_10 /usr/local/
vim $HOME/.bashrc
# 追加
export PATH=/usr/local/jdk1.7.0_10/bin:$PATH
#vimを保存して終了し、実行
export PATH=/usr/local/jdk1.7.0_10/bin:$PATH

ここまでやって、javaを実行できるか確認します。

java -version
java version "1.7.0_10"
Java(TM) SE Runtime Environment (build 1.7.0_10-b18)
Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)

これでjavaが実行できるようになりました。

JAVA_HOMEを定義

echo JAVA_HOME="/usr/local/jdk1.7.0_10/" >> /etc/environment
JAVA_HOME="/usr/local/jdk1.7.0_10/"
echo $JAVA_HOME

を実行して、環境変数にJAVA_HOMEを定義します。

実行

cd atlassian-jira-5.2.4-standalone/
bin/start-jira.sh

これでもう実行できるようです。

JIRAへアクセス

http://localhost:8080/

へアクセスします。

なんかでてきた

スクリーンショット 2013-01-03 7.29.43

どうやらJIRAのホームディレクトリがないというエラーのようです。JIRAのホームディレクトリを指定するには環境変数としてJIRA_HOMEを定義してやればいいようです。

echo JIRA_HOME="/home/nacika/JIRA/" >> /etc/environment
JIRA_HOME="/home/nacika/JIRA/"
echo $JIRA_HOME

環境変数の設定が完了したら、一度JIRAのプロセスをキルします。

ps aux | grep JIRA
kill (JIRAのpid)

あらためて実行

スクリーンショット 2013-01-03 7.41.26

おお!来ました!

データベースの選択では、とりあえずお試しなので内部を選択しました。この操作には少し時間がかかります。

アプリケーションの設定

スクリーンショット 2013-01-03 7.43.33

評価ライセンスの発行

スクリーンショット 2013-01-03 7.45.51

アンケートが求められる

スクリーンショット 2013-01-03 7.46.42

管理者アカウントの登録

スクリーンショット 2013-01-03 7.48.08

送信メールサーバーの設定

スクリーンショット 2013-01-03 7.49.05

ようこそ画面

スクリーンショット 2013-01-03 7.49.57

ダッシュボード

System Dashboard - test

プロジェクト管理画面

Demonstration - test

これには驚きました。メニュー部分がAJAXの非同期読み込みで、ぬるぬる画面が切り替わります。
スクリーンショット 2013-01-03 7.52.57

課題作成画面(チケット作成画面)

スクリーンショット 2013-01-03 7.54.09
なんとモーダルウィンドウ!

スクリーンショット 2013-01-03 7.56.29

スクリーンショット 2013-01-03 7.54.09
課題(チケット)の作成は非同期通信で一瞬で終わる。早い!

課題リスト(チケットリスト)

スクリーンショット 2013-01-03 7.58.16
見て分かる通り、検索結果のフィルタは保存できる機能がすごい!

スクリーンショット 2013-01-03 7.59.33
ネジマークをクリックすれば、各チケットにダイレクトに状態の変更が可能!

課題のクローズ

スクリーンショット 2013-01-03 8.00.33スクリーンショット 2013-01-03 8.00.42

課題のクローズもほんの一瞬で終わる。

一目瞭然なチャート

スクリーンショット 2013-01-03 8.01.41
まだあまり課題を登録していないのでわかりずらいですが、課題数ベースで作成した課題と完了済みの課題との比較チャートです。一目均衡表の雲のようです。

誰の作業量が一番多いのか、少ないのかわかる課題画面

スクリーンショット 2013-01-03 8.03.43
この機能こわいですね。作業量ベースで差が丸見えというか・・。力を魅せつけられるというか・・。

プラグインの導入

スクリーンショット 2013-01-03 8.06.26

gitプラグイン(有料)

スクリーンショット 2013-01-03 8.09.27
これはマイナスポイントかなぁ・・。デフォルトではCVSだけなのかなリポジトリ管理は。

スクリーンショット 2013-01-03 8.12.02
あれっ・・コミットしてたはずなのですが・・・。

まとめ

メリット

  • AJAXによる非同期読み込みで画面遷移をほとんど発生させずストレスのない挙動
  • 非プログラマな職の人でも使いやすい。直感的でわかりやすい
  • グラフが見やすい
  • 進捗管理しやすい
  • 他の人の作業状況が把握しやすい
  • シンプル
  • プラグインが簡単にいれられる
  • 最初のインストールが少しむずかしいが、インストールさえできれば簡単

デメリット

  • リポジトリ管理がヒドイ
  • シンプルすぎて細かいところに手がとどいてない感じがする
  • バージョンやマイルストーン・・・どこ・・?
  • やっぱりバーンダウンチャートがほしい
  • RedMineのTime Trackerプラグインには勝てない
  • Redmineのチャートプラグインのほうが高機能

追記(2013/07/20)

JIRAのの最新版では下記の機能が追加されていました。かなり使えるようになっており、RedMineを超えているかもしれません。

  • マイルストーン(バージョン管理)
  • bitbacket連携によるgitの利用(5userまで無料)
  • TEMPOプラグインによるタイムトラッキング機能(1000円買いきりの機能)

Javascriptテストフレームワーク Jasmineを試す

日頃からJavascriptで開発をしているのにも関わらずあまりテストを書かないので、ここは本格的にテストを書こうと調べてみました。JavascriptのテストフレームワークといったらJsUnitなのかなーと思っていたが、調べてみると結構いろんな種類のテストフレームワークがあったりして、その中で得に人気なのかどうやらJasmineらしい。

Jasmine ~ JavaScript Test フレームワーク より引用:
今回は, JavaScript のテストを行うためのフレームワークJasmine の紹介です。
JavaScript のテストといえば, JSUnit が有名です。
JSUnit は, JUnit とに似たような, Matcher が利用できたりしてわかりやすいのですが,
開発やメンテナンスがストップしており, またWebプロジェクトに組み込まないと利用できないことが
ちょっと残念です。

JUnit のページでも紹介があるように, 今後は Jasmine というフレームワークを開発していくようです。

なるほど、JsUnitはかつて人気だったテストフレームワークだったけども開発ストップしてて古くなっており、今はJasmineということなのか。

とりあえずやってみた

しかし、初めて触るライブラリというのはどうにもこうにも敷居が高い。少しずつ調べてみる。なんにせよまずはインストールだ。

公式ページ: http://pivotal.github.com/jasmine/
公式ページ(の翻訳): http://mitsuruog.github.com/jasmine/
ダウンロード: https://github.com/pivotal/jasmine/downloads

ダウンロードしたファイルは、jasmine-standalone-1.3.1.zip です。

二種類の使い方

Jasmineは使い方が二種類あるそうです。

  • standalone
  • rubygems + rake

standaloneはRubyを使わずにJavascriptだけで使える環境、 rubygems + rake はテストを自動化させたい場合はこちらを選ぶ。ただ、standaloneでもPhantomJSを使えば自動化できるようです。

ここではstandaloneについて実験

rubygemsを使った方法だと、テストを実行させるためのHTMLを自動生成してくれて大変便利らしいのですが、ここでは導入としてstandaloneを使ったやりかたについて試してみたいとおもいます。

ディレクトリの構造はこのようになっています。

.
├── SpecRunner.html
├── lib
│   └── jasmine-1.3.1
│   ├── MIT.LICENSE
│   ├── jasmine-html.js
│   ├── jasmine.css
│   └── jasmine.js
├── spec
│   ├── PlayerSpec.js
│   └── SpecHelper.js
└── src ├── Player.js └── Song.js

これをWebサーバーにあげて、SpecRunner.html を開いてみます。

スクリーンショット 2013-01-03 3.24.32

なるほど、わからん。

見た感じ、上の5つの丸がテストの成功か失敗かを表し、下段の黒字がテストスイート(カテゴリ分け)、緑の文字がテストケースという具合のようです。

Specの意味

「仕様」 = テストケース

Suiteの意味

「一式(分類)」 = テストスイート

SpecRunner.htmlの中身を見る

SpecRunner.htmlの中身を見てみます。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head> <title>Jasmine Spec Runner</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png"> <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> <!-- include source files here... --> <script type="text/javascript" src="src/Player.js"></script> <script type="text/javascript" src="src/Song.js"></script> <!-- include spec files here... --> <script type="text/javascript" src="spec/SpecHelper.js"></script> <script type="text/javascript" src="spec/PlayerSpec.js"></script> <script type="text/javascript"> (function() { var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000; var htmlReporter = new jasmine.HtmlReporter(); jasmineEnv.addReporter(htmlReporter); jasmineEnv.specFilter = function(spec) { return htmlReporter.specFilter(spec); }; var currentWindowOnload = window.onload; window.onload = function() { if (currentWindowOnload) { currentWindowOnload(); } execJasmine(); }; function execJasmine() { jasmineEnv.execute(); } })(); </script>
</head>
<body>
</body>
</html>

よくわからなかったのでコメントを振ってみる

よくわからなかったのでコメントを振ってみます。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head> <title>Jasmine Spec Runner</title> <!-- ショートカットアイコン --> <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png"> <!-- スタイルシート --> <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> <!-- Jasmineの読み込み --> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <!-- Jasmineの読み込み --> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> <!-- テストを行う対象ファイルを読み込み --> <script type="text/javascript" src="src/Player.js"></script> <script type="text/javascript" src="src/Song.js"></script> <!-- 独自定義のマッチャを読み込み --> <script type="text/javascript" src="spec/SpecHelper.js"></script> <!-- テストケースファイルを読み込み --> <script type="text/javascript" src="spec/PlayerSpec.js"></script> <script type="text/javascript"> (function() { // Jasmineの環境設定オブジェクトを読み込み var jasmineEnv = jasmine.getEnv(); // アップデートの間隔を1秒に設定? jasmineEnv.updateInterval = 1000; // テスト結果を取得するためのオブジェクトを取得 var htmlReporter = new jasmine.HtmlReporter(); // 不明・・・ jasmineEnv.addReporter(htmlReporter); // 個別のテストスイートやテストケースをクリックすることで、テストスイートの一部分のみを実行して結果を取得する jasmineEnv.specFilter = function(spec) { return htmlReporter.specFilter(spec); }; // ウィンドウをリロードするオブジェクト生成 var currentWindowOnload = window.onload; window.onload = function() { if (currentWindowOnload) { currentWindowOnload(); } // テスト実行 execJasmine(); }; // テスト実行関数の定義 function execJasmine() { jasmineEnv.execute(); } })(); </script>
</head>
<body>
</body>
</html>

このファイルは、テストを行いたいJavascriptを読み込み、それに対してテストを定義し実行するというもののようです。しかし、「jasmineEnv.updateInterval = 1000;」という行が気になった。これはもしかして1秒ごとにテストを実行するということなのだろうか。もしそうなら、このHTMLファイルを開きっぱなしで常にテスト結果を確認できるということなのでとても嬉しいのだけれど・・。

バグらせてみる

まだなんだか感覚がつかめないので、テスト対象のファイルをバグらせてみることにします。

Player.jsをバグらせる。

function Player() {
}
Player.prototype.play = function(song) { this.currentlyPlayingSong = song; this.isPlaying = true;
};
Player.prototype.pause = function() { this.isPlaying = false;
};
Player.prototype.resume = function() { if (this.isPlaying) { throw new Error("song is already playing"); } this.isPlaying = true;
};
Player.prototype.makeFavorite = function() { this.currentlyPlayingSong.persistFavoriteStatus(true);
};

中身はとてもシンプルで、本当にプレイヤー機能が入っているのかと思えばそんなことはなくて、模倣したオブジェクトのようです。

コメントを振ってみます。

function Player() {
}
// 再生する
Player.prototype.play = function(song) { this.currentlyPlayingSong = song; this.isPlaying = true;
};
// ポーズする
Player.prototype.pause = function() { this.isPlaying = false;
};
// 途中から再生する
Player.prototype.resume = function() { if (this.isPlaying) { throw new Error("song is already playing"); } this.isPlaying = true;
};
// お気に入りに登録
Player.prototype.makeFavorite = function() { this.currentlyPlayingSong.persistFavoriteStatus(true);
};

お気に入りに登録のコメントが合っているのか不安ですが、これの途中から再生するをバグらせてみます。

...
// 途中から再生する
Player.prototype.resume = function() { if (this.isPlaying) { throw new Error("song is already "); } this.isPlaying = true;
};
...

「song is already playing」からplaying を消してみました。これでいったいなにが起きるか・・・。
先ほどの SpecRunner.html を開いて確認してみます。

バグがある場合のテスト結果

スクリーンショット 2013-01-03 4.10.31

おお! なんか出ました!エラーがでました!これはエラー詳細ページで、全体のテストケース一覧を見るには「5 specs」をクリックします。

スクリーンショット 2013-01-03 4.11.31

エラーがあるテストケースは赤字になっています。
また、自動的にリロードしてくれるのか期待していたのですが、自動リロード機能はないようなので、ブラウザの自動リロード機能を使って常に監視しておくといいです。

テストのほうを弄る

テストケースが定義されているファイルをいじってみます。弄るファイルはspecフォルダにある「PlayerSpec.js」です。

中身はこんなかんじ

describe("Player", function() { var player; var song; beforeEach(function() { player = new Player(); song = new Song(); }); it("should be able to play a Song", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); //demonstrates use of custom matcher expect(player).toBePlaying(song); }); describe("when song has been paused", function() { beforeEach(function() { player.play(song); player.pause(); }); it("should indicate that the song is currently paused", function() { expect(player.isPlaying).toBeFalsy(); // demonstrates use of 'not' with a custom matcher expect(player).not.toBePlaying(song); }); it("should be possible to resume", function() { player.resume(); expect(player.isPlaying).toBeTruthy(); expect(player.currentlyPlayingSong).toEqual(song); }); }); // demonstrates use of spies to intercept and test method calls it("tells the current song if the user has made it a favorite", function() { spyOn(song, 'persistFavoriteStatus'); player.play(song); player.makeFavorite(); expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); }); //demonstrates use of expected exceptions describe("#resume", function() { it("should throw an exception if song is already playing", function() { player.play(song); expect(function() { player.resume(); }).toThrow("song is already playing"); }); });
});

うおお..よくわからない…

RSpecというruby界隈の方にはお馴染みの書き方のようです。そしてこの書き方がテストをやる上ですごいやりやすいんだとか。
また同じようにコメントを振ってみます。

// テストスイート定義
describe("プレイヤー", function() { // テストを行うJavascriptのオブジェクトを読みこませる変数の宣言 var player; var song; // テストを開始する準備を行う beforeEach(function() { // テスト対象となるオブジェクトの読み込み player = new Player(); song = new Song(); }); // テストケースを定義 it("テストケース名", 無名関数) it("曲を再生することができる", function() { // 音楽を再生 player.play(song); // player.currentlyPlayingSong が song と同値であることを期待する expect(player.currentlyPlayingSong).toEqual(song); // プレイヤーが再生中であることを期待する -> SpecHelper.js に定義されています expect(player).toBePlaying(song); }); // テストスイート定義(階層になっていて見やすい) describe("曲が一時停止されたときの挙動", function() { // テストを開始する準備を行う beforeEach(function() { // プレイヤーを再生 player.play(song); // プレイヤーをポーズ player.pause(); }); // テストケース定義 it("曲が一時停止しているか確認する", function() { // player.isPlaying が false になっている expect(player.isPlaying).toBeFalsy(); // プレイヤーが再生されているマッチャに対しnotメソッドによって評価が逆になり、プレイヤーは音楽を再生していない となる expect(player).not.toBePlaying(song); }); it("途中から再生可能かどうか", function() { // プレイヤーをレジュームする player.resume(); // player.isPlaying は true になっている expect(player.isPlaying).toBeTruthy(); // player.currentlyPlayingSong は song と 同じ値であることを期待する expect(player.currentlyPlayingSong).toEqual(song); }); }); // テストケース定義 it("ユーザーがお気に入りにしていた場合、その曲を教える", function() { // オブジェクトのメソッドの呼び出しをスパイを使って監視 spyOn(song, 'persistFavoriteStatus'); // プレイヤーを再生 player.play(song); // プレイヤーがお気に入りに設定(内部で this.currentlyPlayingSong.persistFavoriteStatus(true); が実行されます ) player.makeFavorite(); // song.persistFavoriteStatusメソッド は 引数に true を伴って実行されたか expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); }); // テストスイート定義 describe("#レジューム", function() { it("曲が既に再生されている場合に例外をスルーする", function() { // プレイヤーを再生 player.play(song); // プレイヤーをレジュームしてその例外が同じものであることを期待する -> player.resume(); では throw new Error("song is already"); として例外が送出されている。 expect(function() { // レジューム player.resume(); }).toThrow("song is already playing"); }); });
});

頭を使いすぎた・・・。だいたいこんなかんじで合っているとおもいます。
流れはコメントの通りで、expect の期待が外れると、そのテストケースは「失敗」となるようです。describeによるテストスイートのカテゴリ分けや階層化もなかなかおもしろいと思いました。

そして、とくに気になった点を2つあげると、独自定義マッチャと、スパイ機能です。これについては次に書きます。そしてその前に、マッチャってなんだ・・・・・。

マッチャ

マッチャとは、「AはBであることを期待する」というように正しいかどうかを評価するためのものです。
マッチャはいくつかの種類があります。

expect(x).toEqual(y);xとyが等しいことを期待する
expect(x).toBe(y);xとyが同じオブジェクトであることを期待する
expect(x).toMatch(pattern);文字列または正規表現パターンでxと比較し、一致することを期待する
expect(x).toBeDefined();xがundefinedではない場合ことを期待する
expect(x).toBeUndefined();xがundefinedであることを期待する
expect(x).toBeNull();xがnullであることを期待する
expect(x).toBeTruthy();xがtrueであることを期待する
expect(x).toBeFalsy();xがfalseであることを期待する
expect(x).toContain(y);配列化か文字列であるxに対して、yが含まれていることを期待する
expect(x).toBeLessThan(y);xがy未満であることを期待する
expect(x).toBeGreaterThan(y);xがyよりも大きいことを期待する
expect(function(){fn();}).toThrow(e);無名関数が実行された時に関数fnが例外eを投げることを期待する
.not.(matcher)(matcher)に他のマッチャを指定することでそのマッチャを逆に評価します(trueをfalseに falseをtrueに)

独自マッチャの定義

マッチャは予め定められたものだけではなく、「SpecHelper.js」によって独自に定義することもできます。

// テストを実行する準備をする
beforeEach(function() { // マッチャの追加 this.addMatchers({ //toBePlayingを追加 toBePlaying: function(expectedSong) { // this.actual は expect(player).toBePlaying(song); の player の部分 var player = this.actual; // 評価 return player.currentlyPlayingSong === expectedSong && player.isPlaying; } });
});

スパイ

次はスパイを見ていきます。

 // テストケース定義 it("ユーザーがお気に入りにしていた場合、その曲を教える", function() { // オブジェクトのメソッドの呼び出しをスパイを使って監視 spyOn(song, 'persistFavoriteStatus'); // プレイヤーを再生 player.play(song); // プレイヤーがお気に入りに設定(内部で this.currentlyPlayingSong.persistFavoriteStatus(true); が実行されます ) player.makeFavorite(); // song.persistFavoriteStatusメソッド は 引数に true を伴って実行されたか expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); });

スパイのテストケースはこうなっています。「spyOn(song, ‘persistFavoriteStatus’);」で song オブジェクトの persistFavoriteStatusメソッドを監視します。

songのpersistFavoriteStatusメソッドはこのようになっています。

function Song() {
}
Song.prototype.persistFavoriteStatus = function(value) { // something complicated throw new Error("not yet implemented");
};

「player.play(song);」でプレイヤーを再生するときには特に内部でスパイは活動を行いませんが、「player.makeFavorite();」で活動を行います。

makeFavoriteメソッドは内部ではこのようになっており、「this.currentlyPlayingSong」には 「player.play(song);」内部で定義された songオブジェクトが入っているため、「persistFavoriteStatus」が実行できます。

// お気に入りに登録
Player.prototype.makeFavorite = function() { this.currentlyPlayingSong.persistFavoriteStatus(true);
};

このときに、スパイが活動を始め、このメソッドが実行されたことをしっかりと記憶します。

そして、

// song.persistFavoriteStatusメソッド は 引数に true を伴って実行されたか expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);

スパイの活動を見るために、toHaveBeenCalledWithメソッドがスパイから songオブジェクトがpersistFavoriteStatusメソッドを実行したときに引数にtrueを使用したかを聞き出します。

true が引数に使われたことがわかったので、これで期待通りの結果になっているということがわかります。

スパイのためのマッチャ

このように toHaveBeenCalledWith のようなスパイのためのマッチャがいくつか用意されています。

expect(x).toHaveBeenCalled()xメソッドがスパイ中に呼び出されていたメソッドであることを期待
expect(x).toHaveBeenCalledWith(arguments)xメソッドがスパイ中に呼び出された時にそのメソッドに使用していた引数がargumentsであることを期待
expect(x).not.toHaveBeenCalled()xメソッドがスパイ中に呼び出されなかった
expect(x).not.toHaveBeenCalledWith(arguments)xメソッドがスパイ中に呼び出された時にそのメソッドに使用していた引数がargumentsでないことを期待

さまざまなスパイの呼び出し方法

スパイが監視しているメソッドの実行を検知したときに細かい挙動を指定することができます。

spyOn(x, ‘method’).andCallThrough();デフォルト機能。スパイ活動を開始します。
spyOn(x, ‘method’).andReturn(arguments);スパイが呼び出されたときに決められた引数であるargumentsを返します。
spyOn(x, ‘method’).andThrow(exception);スパイが呼び出された時に渡された例外をスルーします。
spyOn(x, ‘method’).andCallFake(function);スパイが呼び出された時に指定された関数へ実行を移譲します。

最後に

とても使いやすくて、ひとつのHTMLページに詰め込むこともできるし、デバッグモードをONにした場合にのみ表示なんてことも不可能ではないので、いろんな応用が効くテストフレームワークだと思いました。こんなに柔軟性のあるテストフレームワークだということに今更ながら驚いています。

スクリーンショット 2013-01-03 5.57.54

Pythonでエスケープとアンエスケープを行う

Escape Unescape

s = unicode(raw_input(), "utf-8").encode('unicode_escape')
print s
print s.decode('unicode_escape')

これでいけた。Python3からは文字コード周りがすごい修正かかってるらしいんだけども、これがあれば問題ない気がする。raw_inputのところをリテラルに変えてもいいし、エラーも吐かずいい仕事してくれる。

ただ改行コードが・・

しかし改行コードまではエスケープしてくれない。しかも先頭の\が消えるときがある。

追記

import cgi
cgi.escape("ほげほげ");

こんなのもあった。

PythonでMeCab実行時に文字化けを治す

文字化けの原因

文字化けの原因はどうやら一緒にインストールした辞書が原因らしい。UTF-8としてmakeしたはずなのになぜかeuc-8になってしまうらしく。これもまた混乱の原因になってしまうようです。

辞書の変更

sudo update-alternatives –config mecab-dictionary

とすることで辞書を変更できるようですが、これでutf-8を指定してるのにも関わらず文字化けする始末。どうしてなんだろうと調べてみると原因は全く関係ないところにありました。

mecabrcファイルを探す

find / -name *mecabrc*

mecabrcファイルにdecDirというディクショナリのディレクトリを指定する変数があるのでこれをutf-8対応のものに書き換えます。ややこしい・・。

/usr/local/etc/mecabrc
/etc/mecabrc

自分の環境だと2つでてきたのでこれらをviで開いてdecdirを書き換え。
書き換える元の辞書のファイルパスがわからない場合は、

find / -name *ipadic-utf8*

で探します。

/var/lib/mecab/dic/ipadic-utf8

こんなかんじになってるのがだいたい正解です。

Mac OS X で cc,gcc,clang をインストール

clangがない

OSX上でPythonを使い画像を加工しようと思ってPILをインストールしようとしたら、clangがないと怒られたので調べてみると、Xcodeをインストールしてもこれらコンパイラは自動的にインストールされなくなったみたい。

cc,gcc,clangを一括インストール

Xcode > Performance… > Downloads > Command Line Tools

のインストールボタンをクリックするだけで全てが一括インストールされる。