mokky14's IT diary

IT関係の仕事メモ、勉強会の感想など書いてます。

ExpectedExeptionを使ってみる

JavaでExceptionが発生する事を検証するテストは以下のように書く事が出来る。

    @Test(expected = IllegalArgumentException.class)
    public void Exceptionの検証() {
        throw new IllegalArgumentException("このメッセージは検証できない");
    }

ただ、このテストケースでは、意図したExceptionが発生する事は検証できるが、そのExceptionの内容の検証を行う事が出来ない。
Exceptionの検証にExpectedExceptionクラスが使えるという情報を貰ったので使い方を確認。

使い方

  1. @Ruleアノテーションを付与し、ExpectedExceptionクラスのインスタンスを取得
  2. テストケースの先頭で、Exceptionについて検証する内容を設定
  3. Exceptionが発生する処理を実行

サンプル

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.Matchers.endsWith;

public class ExpectedExceptionTest {
    // @Ruleを付加するフィールドはpublicフィールドとする
    @Rule
    public ExpectedException expected = ExpectedException.none();

    @Test
    public void ExpectedExceptionを使ったException検証() {
        // 検証する内容を設定
        expected.expect(IllegalArgumentException.class);
        expected.expectMessage("sample");   // 部分一致検証
        expected.expectMessage(endsWith("exception"));

        // Exceptionが発生する処理を実行
        new SampleClass(null);
    }

    static class SampleClass {
        SampleClass(String str) {
            if ( str == null ) {
                throw new IllegalArgumentException("sample exception");
            }
        }
    }
}

このコードで、Exception#getMessage()の内容を検証する事が出来る。
なお、ExpectedException#expectMessage(String)メソッドは部分一致の検証である事に注意。

JUnit実践入門にも載ってた。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

TDDBC仙台 4thに行ってきた

昨年に続きTDDBC仙台に参加してきました。
@t_wadaさん、スタッフ、および参加者の皆さん、どうもありがとうございました。

当日のtogetter
TDDBC仙台 4th - Togetterまとめ

基調講演

@t_wadaさんによる基調講演。
講演資料は後日SlideShareにアップするとの事。

TDDの目標

f:id:mokky14:20141013042949p:plain
動作するキレイなコードがTDDの目標。
テストコードが存在することで、改善が可能になる。

ソフトウェア工学は予測性が低い。コードを書き始めることで初めて問題に気がつくことが多い。
なので、完璧な設計をしてから実装を始めるのではなく、とりあえず実装し、きれいなコードにするのはその後にする。TDDはその手助けをしてくれる。

TDDのサイクル

f:id:mokky14:20141013043644p:plain

  1. 目標を示すテストコードを書く
  2. テストを成功させるプロダクションコードを書く
  3. プロダクションコードをリファクタリングする

このサイクルが重要。
納期等の理由でリファクタリングされなくなる事があるけどちゃんとやりましょう。

テストコードのメンテナンスコストが高いと、「テストコード修正が大変だからプロダクションコードの改造はしない」という本末転倒な事になる。
なので、テストコードにもリファクタリングが必要。
プロダクションコードと同じく、テストコードもDRYにする。コピペしまくったりとかやってはいけない。

TDDを導入することで、実装時間は2割弱増えるが、その後の不具合検出数が減るため、トータルでの開発工数は減る。

TDDの対象範囲

f:id:mokky14:20141013042939p:plain
テストの目的は対象によって異なる。
TDDはDeveloper Testing。開発者の開発促進が目的。

f:id:mokky14:20141013184616p:plain
TDDの"T"はTestというよりCheckingに近い。(TDDはプロダクションコードが動作する事の確認でしかないため)
要件のテストとして、BDDという方法も出て来ているが、RSpecを使用したBDDとScenarioを使用したBDDの二派に別れているのが現状。

テストを動かすだけでは品質は上がらない。テストは品質を明確にするだけ。
品質を高めるのはリファクタリング
体重計に乗っただけでは体重は減らないのと同じ。

なぜTDDをするか

f:id:mokky14:20141013043346p:plain
「動いているコードは触るな」とは今は言えない。
コードを触らなくても周り(OS、フレームワーク等)が変わっていく。直さざるを得ない。
何もしなければコードは腐っていく。

テストコードがあることで、コードへのフィードバックが即座に得られ、書いたコード、これから書くコードに自信が持てる。

TDDライブデモ

@135yshrさんと@nnasakiさんによるgo言語によるライブデモ。
go言語は全く知らなかったけど、@135yshrさんのIDE(Sublime text)の使いこなしに感心したり、「テストコード書くのにコピペは使わないようにしてる」の発言になるほどと思ったりしながら見させて頂きました。

昼飯

はらこめし。うま。
http://instagram.com/p/t_xzB5K_OL/

ペアプロ

昨年に続きJavaで参加。
Pythonやりたかったけどペア居なかった。。

課題と自分らのチームで実装したソース

TDD Boot Camp(TDDBC) - TDDBC仙台04/課題
実装ソース

感想KPT

Keep
  • IntelliJコーディング楽しい。
  • 「あれどうやるんだっけ?」と詰まった時に備えて「JUnit実践入門」持っていったのは正解だった。
  • GitHubでコード共有して演習進めたのは、お互いに自分のマシンが使用できて効率良かった。
  • コードレビューで、設計や改造について、色々なチームの意見が聞けたのが面白かった。
Problem
  • GitHubの使い方あんまり分かってなくてペアの方に世話になりっぱなしだった。
  • テストコードのリファクタリングやったとき、リファクタリング後のテストが動作するかの確認が雑だった。
  • 仕様追加時、作成済みテストコードの作り直しが多く発生した。実務で既存コードの修正量を抑える方法をもう少し考えた方がいいかも。
Try
  • ExpectedExceptionでExceptionの検証を行うテストが書ける事を教えてもらったので、後で機能確認しておく。
  • 残りの演習やってみる。

QAとか

和田さんへの質問と回答。

Q) 基調講演の中で、プロダクションコードとテストコードの比率が同じになるような例が出ていたが、テストコードはそんなに減らせるものなのか?
A) プロダクションコードとテストコードの比率が1:1になるようにリファクタリングし、プロダクションコードとテストコードの比率が1:1~1:2の範囲に収まるようにする。
この比率を超えるようならテストコードの見直しが必要。

Q) テストコードで、Mockは使うべきか、使わないべきか?
A) Mockは使う派と使わない派がいるが、@t_wadaさんはMock使わない派。
実物のコードが使えるなら、出来るだけ実物を使うようにしている。実物のコードを使うことで時間がかかるのは仕方ないと考えている。

最後に

テスト書きましょう。
f:id:mokky14:20141011110405p:plain

pipのインストール(Python3.3 Windows7)

Windows7の環境でpipをインストールしたときのメモ。

参考:python - How to install pip on windows? - Stack Overflow

環境

手順

get-pip.pyをダウンロード

https://raw.github.com/pypa/pip/master/contrib/get-pip.py
からget-pip.pyをダウンロードして保存する。拡張子は.pyで。

コマンドプロンプトからget-pip.pyを実行

なお、Pythonのインストール先をProgram Files配下にしている場合、コマンドプロンプトは管理者権限で起動する必要がある。

C:\>python C:\Downloads\get-pip.py
Downloading/unpacking pip
Downloading/unpacking setuptools
Installing collected packages: pip, setuptools
Successfully installed pip setuptools
Cleaning up...

これで、Pythonインストールディレクトリ下のScriptsディレクトリにpipコマンドがインストされる。
PATH環境変数にこのディレクトリを追加しておけば、コマンドプロンプトからpipコマンドが実行出来る。

(後日追記)

Proxy配下でのpipインストール

Proxy配下の環境では上記手順だとインストール出来なかった。
公式のPythonの代わりにnimicondaをインストールした環境で、以下手順でProxyの設定を行ってインストールした。

C:\>set HTTP_PROXY=http://ID:PASS@PROXY-HOST:PROXY-PORT
C:\>conda install pip
Fetching package metadata: ..
Solving package specifications: .
Package plan for installation in environment C:\Programs\Miniconda3:

The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    conda-3.7.1                |           py34_0         201 KB
    pip-1.5.6                  |           py34_0         1.3 MB
    requests-2.4.3             |           py34_0         607 KB
    setuptools-5.8             |           py34_0         738 KB
    ------------------------------------------------------------
                                           Total:         2.9 MB

The following NEW packages will be INSTALLED:

    pip:        1.5.6-py34_0
    setuptools: 5.8-py34_0

The following packages will be UPDATED:

    conda:      3.7.0-py34_0 --> 3.7.1-py34_0
    requests:   2.4.1-py34_0 --> 2.4.3-py34_0

Proceed ([y]/n)? y

Fetching packages ...
conda-3.7.1-py 100% |###############################| Time: 0:00:01 160.19 kB/s
pip-1.5.6-py34 100% |###############################| Time: 0:00:04 328.15 kB/s
requests-2.4.3 100% |###############################| Time: 0:00:01 501.35 kB/s
setuptools-5.8 100% |###############################| Time: 0:00:08  84.44 kB/s
Extracting packages ...
[      COMPLETE      ] |#################################################| 100%
Unlinking packages ...
[      COMPLETE      ] |#################################################| 100%
Linking packages ...
[      COMPLETE      ] |#################################################| 100%

pip実行する時もproxy指定が必要。

C:\>pip install pycrypto --proxy=http://ID:PASS@PROXY-HOST:PROXY-PORT

参考:
Windows & プロキシ配下でPythonを開発する君へ - Qiita
Installing Python's easy_install using ez_setup.py from behind a proxy server - Stack Overflow
pythonメモ proxy下でのpip、easy_install - Qiita

Java8 lambda式の復習

桜庭祐一さん(@skrb)のlambda式 ハンズオンの復習を兼ねてまとめ。

ハンズオンの教材:
skrb/LambdaDojo · GitHub

lambda式を使うメリット

lambda式を使用する意味は、処理のパラレル化。
処理を複数CPUで並列に処理することで、高速化を図る事が出来る。

Functional Interface

lambda式はfunctionを書くもの。
stream apiiteratorを書くもの。

lambda式は、Functional Interfaceのメソッドをimplementsする。

Interface method
Runnable void run()
Callable T call()
Comparator boolean compare(T t1, T t2)

メソッド名を覚えておく必要はあまりなく、引数と戻り値さえ覚えておけばOK。
他にも、java.util.function packageに色々ある。

Functinal Interfaceを自作する場合は、@FunctionalInterfaceアノテーションを付与すると、コンパイラがFunctionalなInterfaceになっているかをチェックしてくれる。

演習

Functional Interfaceの実装

基本的なlambda式の書き方から。

    Comparator<Integer> comparator1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer x, Integer y) {
        return x - y;
        }
    };

ComparatorはFunctinal Interface。
このComparatorの無名クラスの定義は以下のように書き換えられる。

       Comparator<Integer> comparator1 = (x, y) -> x - y;

なお、引数の型省略は、片方のみ(例えば(x, Integer y)のような)は出来ない。型省略時は両方の引数の型を省略する。
余談として、Intellijでは、特に操作しなくてもラムダ式に書き換えられる。

引数がない場合のlambda式の記述方法。引数指定がない場合でも()は省略不可。

    Callable<Date> callable1 = () -> new Date();

下記のような記載も可。(コンストラクタリファレンス)

    Callable<Date> callable2 = Date::new;

ループ処理

    List<String> strings = Arrays.asList("a", "b", "c", "d", "e");
    
    StringBuilder builder = new StringBuilder();
    for (String s: strings) {
        builder.append(s);
    }

forループは、forEachメソッドでのループに置き換える。

    List<String> strings = Arrays.asList("a", "b", "c", "d", "e");

    StringBuilder builder3 = new StringBuilder();
    strings.forEach(t -> builder3.append(t));

メソッドコールをメソッドリファレンスの形式にすると、引数の記載も不要。

    List<String> strings = Arrays.asList("a", "b", "c", "d", "e");

    StringBuilder builder4 = new StringBuilder();
    strings.forEach(builder4::append);

filterメソッド

    List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    for (Integer x : numbers) {
        if (x % 2 == 0) {
            System.out.print(x);
        }
    }
    System.out.println();

forはforEachに、elseを持たないifはfilterに置き換え。

    numbers.stream().
            filter(x -> x % 2 == 0).
            forEach(System.out::print);
    System.out.println();

数値計算

    List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    double ave = 0.0;
    for (Integer x : numbers) {
        ave += x;
    }
    System.out.println(ave / numbers.size());

forEachメソッドを使用して置き換え。
なお、無名クラスと同様に、finalでないローカル変数にはラムダ式からはアクセス出来ない。ave2変数はクラス変数とする必要がある。

    // ave2はクラス変数
    numbers.forEach(x -> ave2 += x);
    System.out.println(ave2 / numbers.size());

数値の合算をreduceメソッドに置き換え。(reduceは値を集約するメソッド。第一引数に初期値、第二引数に値集約を行うラムダ式を指定する)

    int ave3 = numbers.stream()
            .reduce(0, (x, y) -> x + y);
    System.out.println(ave3 / numbers.size());

Integer<->intのオートボクシング回数を削減するため、mapToIntでintに置き換えを行い、Integerへのオートボクシングを抑止。

    int ave4 = numbers.stream()
                .mapToInt(x -> x)
                .reduce(0, (x, y) -> x + y);
    System.out.println(ave4 / numbers.size());

単純な合計値計算は、sumメソッドで出来る。

    int ave5 = numbers.stream()
                .mapToInt(x -> x)
                .sum();
    System.out.println(ave5 / numbers.size());

指定回数分の実行

        // 乱数のリストを作成
        Random random = new Random();

        List<Double> numbers = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            numbers.add(random.nextDouble());
        }

forループをIntStream.range関数に置き換え。range関数は、指定した数値の範囲(回数)だけ処理を行う。

    List<Double> numbers2 = new ArrayList<>();
        IntStream.range(0, 100)
            .forEach(i -> numbers2.add(random.nextDouble()));

予め空リストを作って値を追加しくていくのではなく、collectメソッドでコレクションに変換する。collectメソッドの引数にList変換関数を指定する事で、Listへの変換を行う。

    List<Double> numbers3 =
        IntStream.range(0, 100)
            .mapToObj(i -> random.nextDouble())
            .collect(Collectors.toList());


桜庭さんのIT Proの連載。

Java技術最前線 - Java技術最前線:ITpro

Pacemaker+Heartbeatを試してみる(起動編)

Pacemaker+Heartbeatを試してみる(インストール編)の続き。
サーバの構成は以下。

ノード名 外向けIPアドレス インターコネクト1 インターコネクト2
ha1.localdomain 192.168.56.10(eth1) 172.35.10.10(eth2) 172.40.20.10(eth3)
ha2.localdomain 192.168.56.20(eth3) 172.35.10.20(eth4) 172.40.20.20(eth5)

それぞれのサーバでha.cfを設定。
ha.cfは雛形になるファイルがあるので、そのファイルをコピーして使用する。
必要な部分のみコメントアウトを外していく。
ucast、nodeは環境に合わせて設定。nodeは、uname -nで出力される名前を指定する。
auto_failbackはoffに変更した。(系切替後に故障サーバが復旧したとき、自動で切り戻る事を防止する)
「pacemaker on」は自分で追加。
両方のサーバの違いはucastの指定部分のみ。

[root@ha1 /]# cp -p /usr/share/doc/heartbeat-3.0.5/ha.cf /etc/ha.d/ha.cf
[root@ha1 /]# vi /etc/ha.d/ha.cf

[root@ha1 /]# cat /etc/ha.d/ha.cf | egrep -v '^#' | egrep -v '^$'
debugfile /var/log/ha-debug
logfile /var/log/ha-log
logfacility     local0
keepalive 2
deadtime 30
warntime 10
initdead 120
udpport 694
ucast eth2 172.35.10.20
ucast eth3 172.40.20.20
auto_failback off
node    ha1.localdomain
node    ha2.localdomain
pacemaker on
[root@ha2 /]# cp -p /usr/share/doc/heartbeat-3.0.5/ha.cf /etc/ha.d/ha.cf
[root@ha2 /]# vi /etc/ha.d/ha.cf

[root@ha2 /]# cat //etc/ha.d/ha.cf | egrep -v '^#' | egrep -v '^$'
debugfile /var/log/ha-debug
logfile /var/log/ha-log
logfacility     local0
keepalive 2
deadtime 30
warntime 10
initdead 120
udpport 694
ucast eth4 172.35.10.10
ucast eth5 172.40.20.10
auto_failback off
node    ha1.localdomain
node    ha2.localdomain
pacemaker on

authkeysの設定。こちらは両方のサーバで共通。
コピー後にファイルの権限変更が必要なので注意。
今回はsha1アルゴリズムを使用している。

# cp -p /usr/share/doc/heartbeat-3.0.5/authkeys /etc/ha.d/authkeys
# chown root:root /etc/ha.d/authkeys
# chmod 600 /etc/ha.d/authkeys
# vi /etc/ha.d/authkeys

# cat //etc/ha.d/authkeys | egrep -v '^#' | egrep -v '^$'
auth 2
2 sha1 secret

両系でheartbeatを起動してみる。

# service heartbeat start

で、起動してみたが、crm_monがいつまでも接続できない。
/var/log/ha-logを見ると、以下のようなエラーが。

Oct 07 17:23:52 ha1.localdomain heartbeat: [5107]: ERROR: glib: ucast: error binding socket. Retrying: Permission denied
Oct  7 17:23:52 ha1 heartbeat: [5107]: ERROR: glib: ucast: error binding socket. Retrying: Permission denied
Oct 07 17:23:53 ha1.localdomain heartbeat: [5107]: ERROR: glib: ucast: error binding socket. Retrying: Permission denied
Oct  7 17:23:53 ha1 heartbeat: [5107]: ERROR: glib: ucast: error binding socket. Retrying: Permission denied
Oct 07 17:23:54 ha1.localdomain heartbeat: [5107]: ERROR: glib: ucast: unable to bind socket. Giving up: Permission denied
Oct 07 17:23:54 ha1.localdomain heartbeat: [5107]: ERROR: make_io_childpair: cannot open ucast eth2
Oct  7 17:23:54 ha1 heartbeat: [5107]: ERROR: glib: ucast: unable to bind socket. Giving up: Permission denied
Oct  7 17:23:54 ha1 heartbeat: [5107]: ERROR: make_io_childpair: cannot open ucast eth2
Oct 07 17:23:55 ha1.localdomain heartbeat: [5111]: CRIT: Emergency Shutdown: Master Control process died.
Oct 07 17:23:55 ha1.localdomain heartbeat: [5111]: CRIT: Killing pid 5107 with SIGTERM
Oct 07 17:23:55 ha1.localdomain heartbeat: [5111]: CRIT: Emergency Shutdown(MCP dead): Killing ourselves.
Oct  7 17:23:55 ha1 heartbeat: [5111]: CRIT: Emergency Shutdown: Master Control process died.
Oct  7 17:23:55 ha1 heartbeat: [5111]: CRIT: Killing pid 5107 with SIGTERM
Oct  7 17:23:55 ha1 heartbeat: [5111]: CRIT: Emergency Shutdown(MCP dead): Killing ourselves.
Oct  7 17:26:59 ha1 heartbeat: [5179]: info: Pacemaker support: on

SELinuxが悪さをしているという情報があった。SELinuxiptablesを無効にしてみる。

# getenforce
Enforcing
# setenforce 0
# getenforce
Permissive
# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere            state RELATED,ESTABLISHED 
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:ssh 
REJECT     all  --  anywhere             anywhere            reject-with icmp-host-prohibited 

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere            reject-with icmp-host-prohibited 

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
# service iptables stop

再度チャレンジ。
両方のサーバでheartbeatを起動。

# service heartbeat start

crm_monの内容を確認。

# crm_mon -1r
============
Last updated: Tue Oct  7 18:09:46 2014
Stack: Heartbeat
Current DC: ha2.localdomain (8b5fa310-2ad6-6504-5874-35e498f88c61) - partition with quorum
Version: 1.0.13-30bb726
2 Nodes configured, unknown expected votes
0 Resources configured.
============

Online: [ ha1.localdomain ha2.localdomain ]

Full list of resources:

サーバ2台がOnlineになった。起動出来たっぽい。

SELinuxは特に要らないので、iptablesともどもOFFにする。

# vi /etc/selinux/config

SELINUX=enforcing
↓
SELINUX=permissive
# chkconfig iptables off
# chkconfig --list iptables
iptables        0:off   1:off   2:off   3:off   4:off   5:off   6:off

(追記)
/etc/ha.d/ha.cfの内容を変更してpacemaker再起動を繰り返すと、crm_monの結果に

Node ha1.localdomain (994f9fdb-49d2-458f-a26f-3d7ace82063b): UNCLEAN (offline)
Node ha2.localdomain (701f93e2-b2e2-4c22-b5e7-57f88fd864b6): UNCLEAN (offline)

のような出力が現れる。
これを削除するには、crm configure edit でcrm設定を開き、該当のnode定義を削除する必要がある。
参考:
OSごとコピーしてLinux-HA環境作りTai! « Linux-HA Japan

crm resource migrateの罠

Pacemakerのクラスタ切替方法調べててハマったのでメモ。

crm resource helpに以下の記載があるのを見つけた。

migrate,move migrate a resource to another node
unmigrate,unmove unmigrate a resource to another node

migrateならクラスタ切替出来るな、と思ったのが罠。

早速コマンドを実行。

# crm resource move vip
WARNING: Creating rsc_location constraint 'cli-standby-vip' with a score of -INFINITY for resource vip on host1
        This will prevent vip from running on host1 until the constraint is removed using the 'crm_resource -U' command or manually with cibadmin
        This will be the case even if 0270-host1 is the last node in the cluster
        This message can be disabled with -Q

WARNINGが出たが、目論見通り系切替が出来た。
(今見ると、このWARNINGの内容をちゃんと読んでおくべきだった。。)

が、その後、vipと同一グループのリソースにエラーが起きても、クラスタ切替が走らなくなった。
なぜだと思って色々調べていたら、crm configure showで見慣れない定義が追加されてたのを見つけた。

location cli-standby-vip vip \
        rule $id="cli-standby-rule-vip" -inf: #uname eq host1

この定義がある限り、vipはhost1には絶対に切り替わらない。(-infは"絶対に〜してはならない"という定義)
いつ追加された定義かを確認したら、crm resource moveを実行した時に追加されたことが分かった。

対処として

# crm resource unmigrate vip

を実行することで、上記のlocation定義は消えた。
(ちなみに、moveとmigrate、unmoveとunmigrateはそれぞれ同じ動作をするオプションになる)

とは言え、unmigrateをクラスタ切替後に実行したかは、crm_monの結果からは確認出来ないので、実行忘れが怖い。毎回crm configure showするのも非現実的だし。

クラスタ切替は

# crm node standby host1
# crm node online host1

という手順で、ACT側のノードを一旦standbyにすることでリソースを反対のサーバに移動させて、その後、standbyのノードをonlineにすることで実施することにした。

もう少し綺麗なクラスタ切替の方法はないんかなぁ。。

minttyをインストールしてみた

Windows標準のコマンドプロンプトが使いづらくて何か代替手段ないかと思って探してたら、minttyがいいという情報があったのでインストールしてみた。
環境はWindows7 Professional。

参考:
http://tanakh.jp/posts/2011-11-15-windows-terminal.html
http://dogmap.jp/2011/11/15/mintty/

MinGW/MSYSのインストール

以下のURLからinstallerをダウンロードして実行。

http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/

インストール後、MinGW Installation Manager起動。
f:id:mokky14:20140727145532p:plain

All Packages->MSYS->MSYS Base Systemを選択して、msys-baseを右クリックし、Mark for Installationを選択。
要るのか分からなかったけど、All Packages->MinGW->MinGW Base Systemのmingw32-baseも選択した。
メニューのInstallation->Apply Changesを選択してパッケージインストール。

環境変数の設定

msysからmingw-getを実行するため、path変数に以下のディレクトリを追加する。
C:\MinGW\bin

minttyのインストール

C:\MinGW\msys\1.0\msys.bat を起動し、起動したウィンドウで以下を実行

$ mingw-get mintty

これでminttyのインストールは出来た。

mintty起動用のショートカット作成

Windowsの右クリックメニューより、ショートカットを作成。
以下のコマンドパスを設定する。

C:\MinGW\msys\1.0\bin\mintty.exe /bin/bash --login -i

起動後の設定

ウィンドウのカスタマイズ

mintty起動したら、ウィンドウの右クリック->optionsから好みの設定が出来る。
自分が設定したのは以下。後はお好みで。

  • Looks -> Colours (文字色、背景色)
  • Looks -> Transparency (ウィンドウの透過度)
  • Text -> Font (フォント)
  • Window -> Default Size (起動時のウィンドウサイズ)

Text -> Localeの設定をja_JP, SJISに設定しても日本語が使えなかった(lsで日本語名のファイルが表示できなかった)のは謎。
何か他に設定やら追加パッケージが居るんかな?

PATH設定

minttyのPATHはWindowsのpathの設定を引き継ぐため、他にインストールしてたコマンドが実行されてエラー発生してたので、ホームディレクトリに.bash_profileを設定して、最小限のディレクトリのみPATHに設定するようにした。

export PATH=.:/usr/bin:/usr/local/bin:/mingw/bin:/bin:/c/MinGW/bin

他のパッケージインストール

sshとviを使いたかったので、open-sshvimもインストールした。
msys.batを起動して、

$ mingw-get install msys-openssh
$ mingw-get install msys-vim

でインストール。