Enjoy Architecting

Twitter: @taisho6339

「知識ゼロから学ぶソフトウェアテスト」を読んで

「知識ゼロから学ぶソフトウェアテスト」を読んだので、まとめ兼書評的なものを書いてみる。

この本を読んだ目的

いま自分が所属する開発チームには以下のような課題がある。

  • 手動テストはあるが、そのテスト項目の決め方にガイドラインのようなものがない。(何をテストするのかが決める個人の裁量によって決まっている)

  • 手動テストなので人によってテストの質が違う。

  • 自動化テストなどがまったくない。

  • APIに至っては、レスポンスの目視チェックとクライアント側を開発するときに作りながらチェック。

このような状況なので、僕のようなプロダクトの仕様を把握しきっていない新人にとってはバグを生み出しやすく、またテストも不十分な状態でリリースされてしまう問題がある。

まずは、自分がテストによってどうやって品質を担保するのかをちゃんと知ろうと思い以下ブログを参考に読んだ。

新卒ソフトウェアエンジニアのための技術書100冊

内容について

前半

前半は、ホワイトボックステストブラックボックステスト、同値分析、境界値分析、ディシジョンテーブルなど既知の内容だったので特に学びにはならなかった。 また、独自に「探索的テスト」なるものを提唱していたが、 テストスキルの高い人が、プロダクトを触りながらテストを行うというもので、そのスキルの高い人がいない現状なのであまり興味を惹かれなかった。

後半 -非機能要求テスト-

まず非機能要求で担保するべきなのは、

  • パフォーマンス
  • 機密性
  • 信頼性

の3つだそうだ。

パフォーマンスのテスト

パフォーマンステストで見つかるバグは最悪(最悪設計のし直しが発生する)なため、なるべく最初の方にやることを推奨される。

このテストを行う上で重要なのは、まずはパフォーマンスの定義を明確にすることで、例えば「1分以内に20MBのデータを処理できる」など。ただし、「1分で20MB処理できるけど、30秒だと1MBしか処理できない」といったケースも考えられるので、様々な状態やケースでテストするべきであるとのこと。

また、パフォーマンステストは以下の手順で行うと良いらしい。

1 設計が間違っていないかの確認

データベーステストなどの場合、DBとデータが用意できた時点でそこにアクセスするプロトタイプを作り、処理能力が要求に合致しているかをテスト。

2 パフォーマンスベンチマーク

実際に開発されたソフトウェアがパフォーマンステストできる状態になったら即要求に照らし合わせてテスト。

3 パフォーマンス回帰テスト

開発していて、変更などがあった段階で、変更によってパフォーマンス低下が発生しないようにすぐテストする。

4 最終確認

5 本番のデータなどになるべく近い環境でテスト

機密性のテスト

セキュリティのテストのこと。 攻撃手法はどんどん増えていくのでいたちごっこ。 ツールとか使うといいかもね、というお話。

信頼性のテスト

信頼性とは、「ある条件下で安定して要求された機能を果たす能力」らしい。その指標としてMTBFというものが使われる。 これは、平均故障時間と呼ばれるものらしく、どのくらいの時間ソフトウェアを使用するとバグやハングアップ、リブートなどが発生するかの平均時間。

Web系の会社でこういうのちゃんと測ってリリースしてるところってあるのだろうか・・・

今後自分がどうしていくか

テストの質がばらついているのと、何をテストするの?というのが曖昧なので、まずはテストのガイドラインなんかを作ってチームに浸透させるのが当面な目標なのかな・・・

ガイドラインについて

ガイドラインとしては以下の内容がちゃんと伝わればいいのだろうか。。。

  • なんでテストしなくちゃいけないのか
  • 何をテストしなくちゃいけないのか
    • 機能テスト
      • 正常ケース
      • 異常ケース、例外
    • 非機能テスト
      • セキュリティ
      • パフォーマンス
      • 信頼性

導入に関する懸念点

  • ガイドラインを明確にしたうえで、どうやってチームに定着させていくか。
  • あまり複雑にしすぎると、障壁が高い。
  • 決めるのはいいが、全部一気にやらせるより一部分だけやらせていくのはどうか。

余談

ブログ書いてる途中で結構面倒くさくなった。

入社後5ヶ月を過ごしてみて

入社後5ヶ月を過ごしてみて

本記事の目的

今の会社に新卒入社してから5ヶ月経ったので、現在抱えてる思いや課題意識を整理するために日記のようの書き綴ってみる。(社外秘が多いので具体的には書けません。。。)

今までやってきたこと

新卒研修(4月)

技術研修などではなく、ビジネスマナーやコンプライアンスや企画研修。内容が暑苦しかったり、やたらコミュニケーション取らせようとしたり、人が苦手な自分には若干うざい研修だったが、ディスカッションの進め方やアイデアの出し方など勉強にはなる部分は多かった。

データの配信スクリプトのリニューアル

アプリや、Webページのコンテンツに使用する元データをサーバに配信する仕組みをレガシーなものからリニューアルした。シェルスクリプトなんかを駆使して書いてた。

スマホ版Webページのリニューアル

スマホ版のWebページのデザインリニューアルにともなって、フロントのPHP部分の対応を行ってた。 ガイドラインなんかが多く、最初仕上げた時はそこに全然マッチしたものになっていなくて何度もやり直した。

Androidアプリの機能追加、改修

Android版アプリの機能追加や、細かなバグ修正を行ってた。 レガシーな部分やセキュリティ的にやばそうな部分が多くフラストレーションを溜めながら頑張ってた。

大規模な機能追加(バックエンド、アプリ両方)

大規模な機能追加にともなって、APIの開発とAndroid版アプリの開発に携わった。 APIは新規に開発するものだったので、「オブジェクト指向のこころ」などで勉強したデザインパターンをふんだんに応用してみた。 オブジェクトを「継承」ではなく「移譲」することによってカプセル化する という思想をベースに設計した結果、かなり拡張性や見通しの良いコードになった。 その思想、設計をその機能の開発チーム全体に適用できたのも幸いだった。

また、アプリ側はそのままではとても機能が追加できる状態ではなかったので、機能追加に関わる部分をすべてリファクタリングした。 (大体3週間位工数かかった) TabをActivityで切り替えるような古い設計から、Fragmentに置き換えたり、重い処理を非同期処理にしたり、一つのActivityに3つの画面の管理が書かれていたりしたので分離したりした。 DBの更新が行われたらActivityに通知を行ったりしたかったので自前でObserverパターンを適用してみたりもしたが、ここはRxJavaなどを使ってみてもよかったのかもしれない。。。(ここくらいしかちゃんと理由づけして導入するチャンスはなかったかも)

自分の評価ポイント

  • 新規API開発時に、開発を仕切ってる先輩に設計思想を説明して全体のルールとして適応できたのは良かった。
  • 何かをチーム全体に適応したいとき、人を選んでその人から説得するのは大事だとおもった。

自分の課題ポイント

  • 開発スピードは早かったが、品質の担保がまったくできていなかった。仕上げて突っ込まれたり、あとから問題になるケースが多かった。

  • 人に何かの判断を仰ぐとき、自分はきくだけで自ら提案をすることが少なかった。次からはある程度自分で仮説や提案を作ってから聞きに行きたい。

  • チームの人すべてが、先進的で向上心のあるエンジニアなわけではない。 だから、プロダクトを良いものにするために他社が普通にやっているようなことも(自動化テストや、より良い設計で開発してもらうことなど)、どう説得すればやってもらえるのかをしっかり考えて動かなくてはならない。

まとめ

チームで開発している以上、自分が技術力をつけるだけじゃだめで、コミュニケーション能力を駆使してチームを動かすことが大事なのだということを痛感した。 そしてここでいう「コミュニケーション能力」とは、

  • チームを動かすための説得力、交渉力
  • 簡潔に物事を伝えられる説明力

のことで、これらを技術力と並行して磨いていきたい。

【Android】MenuTabのFragmentの遷移を自前のスタックで管理する

はじめに

概要

仕事の開発の方でタブメニューを使っていて壁にぶつかったので備忘録として残しておきます。
トピックとしては、「Tabメニューの一つのタブで、Fragmentの遷移をさせた時の管理」でぶつかった課題とその解決策について記します。

具体的なエラー

Tabに紐付いたFragmentから、別のFragmentに遷移させ、BackStackに前の状態を積んだ時に、特定ケースで何故かアプリが落ちるといった問題に悩まされました。

成果物

問題解決の成果物としてTabのFragment遷移管理を簡単にするためのモジュールを作ったので公開しておきます。
(お粗末ですが。。。)
使い方はREADMEを見てくれれば。。。
https://github.com/taisho6339/tab_fragment_operation

問題にぶつかったケース

たとえばこういう設計のタブメニューの画面を作ります。

f:id:taisho6339:20140701214608p:plain

つまり、


・一つのActivityにタブのコンテンツとしてFragmentをぶら下げる。
・さらにそのFragmentから何らかのタイミングで別のFragmentに遷移させる。
・遷移は、Fragmentから一旦Activityにコールバックして、Activityで管理する。

といった仕組み。

ぶつかった問題

  1. まずFragmentA-1が開いている状態で、FragmentA-2を呼び出す。
  2. 親のActivity側で、コンテナをFragmentA-2にreplaceする。
  3. 呼び出す前の状態を保存するためにaddToBackStackを呼ぶ。
  4. タブを切り替える。
  5. 再び元のタブに戻る(この時点でFragmentA-2に帰る)
  6. バックキーを押して、FragmentA-2を呼び出す前の状態に戻す。
  7. 落ちる。

エラーログは、

java.lang.IllegalStateException: Fragment already added

こんな感じのが・・・

問題の考察

フレームワーク側のTabを管理するコードを読んでみるとわかりますが、

https://code.google.com/p/android/issues/attachmentText?id=40035&aid=400350000000&name=FragmentTabHost.java&token=WHVg3x7dbNNznLcPPSwWxnin_X0%3A1367343846708

タブを切り替えた時に、タブに紐づけてあるFragmentのインスタンスがnullだと新しく生成し、スタックにaddするという制御を行っています。
つまり、

  1. 親のActivity側で、コンテナをFragmentA-2にreplaceした時点でFragmentA-1のインスタンスが消える。
  2. タブを切り替えてからまた元のタブに戻る
  3. 消えていたFragmentA-1のインスタンスを再度生成
  4. FragmentA-2をattach
  5. バックキーを押すと、FragmentA-2を呼びだす前の状態にロールバックするために、FragmentA-1を再生成しスタックに積もうとする
  6. が、すでに3で蘇らせているのでバッティング(同じバックスタックに同じFragmentのインスタンスは積めない)

という問題っぽい。

解決策

バックキーによるロールバック時、タブの切り替え時にインスタンスを自動で復旧させてることで、バッティングしてしまっているので、

FragmentManagerのBackStackではなく自前でスタックを用意して管理する

ことで回避できるのではないかと考えました。
つまり、それぞれのタブに対してスタックを紐付けておいて、タブそれぞれでFragmentの遷移を管理しようってお話しです。

というわけで汎用的なモジュールを実装してみました。
https://github.com/taisho6339/tab_fragment_operation

仕様

・FragmentからFragmentに遷移した時に、バックキーで元の状態に戻れるようにする。
・タブのルートまで言った時にさらにバックキーを押すとActivityを終了させる。

ようはこれを満たせばいいわけです。

実現方法

・TabInfoというデータクラスを作り、Fragmentを積むスタックを持たせる。
・格タブにTabInfoをタグとしてくっつけておく。(これでTabInfoがタブから取得できる)
・FragmentからFragmentに遷移するときは、このTabInfoの持つスタックに積む(現在表示されているFragmentが一番上にくる)
・バックキーを押したら、スタックをポップして、その時の一番上のFragmentをコンテナに差し込む
・タブ切り替えは切り替え先のタブのスタックの一番上のFragmentをアタッチ

こんな感じ。

Tabを使うActivityで、

  @Override
    public void onBackPressed() {
        if (backControl())
            return;
        try {
            super.onBackPressed();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
    }

    private boolean backControl() {

        TabInfo tabInfo = (TabInfo) getCurrentTab().getTag();
        Stack<Fragment> stack = tabInfo.getBackStack();
        if (!stack.isEmpty()) {
            stack.pop();
            if (stack.isEmpty()) {
                finish();
                return true;
            }
            Fragment newFragment = stack.peek();
            mManager.beginTransaction().replace(mFragmentContentId, newFragment).commit();
            return true;
        }
        return false;
    }


また、ActionBarのTabを使ったので、TabListenerの実装クラスに、


 @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
        Stack<Fragment> stack = ((TabInfo) tab.getTag()).getBackStack();
        Fragment fragment = null;
        if (stack.isEmpty()) {
            fragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.add(mContentId, fragment);
            stack.add(fragment);
        } else {
            fragment = stack.peek();
            ft.attach(fragment);
        }
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
        Stack<Fragment> stack = ((TabInfo) tab.getTag()).getBackStack();
        if (!stack.isEmpty()) {
            Fragment fragment = stack.peek();
            ft.detach(fragment);
        }
    }

詳しくはgithubで。。。

「エンジニアとしての生き方」を読んで、働くことについて考える

最近、一週間ごとに一冊本を読むことを習慣にしてます。(´・ω・`)
今週は、中島聡さんの「エンジニアとしての生き方」という本を読んでみました。

Amazon.co.jp: エンジニアとしての生き方 IT技術者たちよ、世界へ出よう! (インプレス選書): 中島 聡: 本



NTT研究所、マイクロソフトを経て世界で活躍されている方だけあって、ものすごく率直で過激な意見の内容が印象的な本でした。日本は全然ダメ、国内に留まらず海外へ出て活躍していこう!みたいなことをとても推してましたね、、、
海外か、、、、、お外こわぃ、、、、、、、

それはさておき、ここでこの本の個人的に印象に残った部分を挙げて一度自分の中にある「働く」ことに対する考え方を整理しておこうと思います。


一番大切なのは「好きだから頑張れる」
(出典:中島聡(2011) 『エンジニアとしての生き方』 インプレスジャパン

 社会的地位だとか、学歴を活かしてとか給料とかそういうものでやりたくない仕事、楽しめない仕事をやるのはもったいない。自分のしたいことが会社の利益につながる、という意味でベクトルがマッチしていることが大事で、例えば残業させられる、ではなくそれが苦にならないような仕事につくべし。みたいな内容でした。

これ、今まで悩んでたことに大して直球な内容・・・・・・・・
自分は今、やりたいことに挑戦できる仕事か、安定してお給料もそこそこなお仕事かで結構迷ってました。
今自分が飛び込もうとしてるお仕事って、常に勉強したり努力重ねていかなければならないし、変化の大きい世界なので、のほほんと暮らしてたら間違いなく置いてかれるわけでして、、、、
今後その中で頑張っていくとしてモチベーション保ち続けられるのか、結果出せるのかっていうのが100%自信がない、、、、
あとせっかく良い大学入ったんだからもっと給与が高くて安定した仕事があるんじゃないかとか、、、(夢物語かも)


とまあこんなことを考えて色々悩んでたんだけど、なんだか迷いが晴れてきたのかなと思います。
この本読んで。というか元々心決まってたのかも、、、思えば最初から自分が何をしたいのか?っていうのを常に考えて就活してきてましたし、、、、
元々自分は「生きることを目的にして生きていたくない、それくらいなら死にたい」を常に心の中に掲げているので、たとえお金たくさんもらっても、安定してても、この先の人生で長く関わらなければならない仕事っていうものをつまらないと思って過ごしたくないと思ってて。
だから、単に大手の内定を目的にした就活の風潮とか、内定貰った企業の知名度とか規模とか、社会的地位とかで比較されるのもめっちゃ違和感覚えてました。
だからES何社も出して選考受けまくる、とかしないで実際に働いてる人の声とかを個人的に聞きに行ったりして、一つ一つの企業の仕事内容について理解深めた上で、働きたい企業を絞ったりしてきました。(働いてみないとわからないことのほうが多いと思うけど)
つまり結局は最初から自分の心は決まってたんですね。。。。。


結論!

自分のやりたいと思った仕事で、達成したいとおもった目標のためにがんばる!
で、だめだったらその時は思い切って死のう!笑


\( 'ω')/ウオアアア

【アルゴリズム+Ruby】Rubyで単体テスト 【test-unit】

備忘録。
イナリサーチRubyで実装しつつ単体テストを行ってみました。
使用したテストフレームワークはとりあえずtest-unitで。。。

テストケースは、

・通常の探索でヒットする場合
・通常の探索でヒットしない場合
・数字じゃないオブジェクトを与えた場合(例外テスト)

です。
とりあえずてきとーです。

require 'test/unit'

class Searcher

    def self.binary_search( x , left , right , array )

        while left <= right do
            mid = (left + right) / 2

            if array[mid] == x then
                return mid
            end

            if array[mid] < x then
                left = mid + 1
            else
                right = mid - 1
            end
        end

        return nil

    end

end

class SearchTest < Test::Unit::TestCase

    @@array = [1,2,3,4,5,6,6,8,9,10]
    
    def testHitSearch
        ans = Searcher.binary_search( 9 , 0 , @@array.length-1 , @@array)
        assert_equal(8,ans)
    end

    def testMissSearch
        ans = Searcher.binary_search( 34 , 0 ,@@array.length-1 , @@array)
        assert_equal(nil,ans)
    end

    def testillegalArgument
        begin
            Searcher.binary_search( "String" , 0 ,@@array.length-1 , @@array)
            flunk('Failed')
        rescue 
        end
    end
end

実行結果

Run options: 

# Running tests:

Finished tests in 0.004678s, 641.2997 tests/s, 427.5331 assertions/s.
3 tests, 2 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.0.0p247 (2013-06-27 revision 41674) [universal.x86_64-darwin13]


おー、、、、
、、、、、はい。
ちゃんとすべてのテストが通りました。

今回はクラス変数、クラスメソッド、Javaでいうtry-catchにあたるbegin構文なんかもついでにおぼえました(。・・。)
明日もがんばります。

【アルゴリズム+Ruby】とりあえずバブルソートの実装【一日目】

備忘録。
アルゴリズムとデータ構造を復習がてらついでにRubyを習得しようと思って、今日は試しにバブルソート書いてみました。
初めてのRuby、、、まだ思うように使えないから少しの間こっちがメインになりそう、、、

  1 =begin
  2     バブルソートプログラム
  3 =end
  4 
  5 def bubble_sort(array)
  6     size = array.length
  7     for i in 0...size
  8         for j in 0...(size-(1+i))
  9             if array[j] > array[j + 1] then
 10                 tmp = array[j+1]
 11                 array[j+1] = array[j]
 12                 array[j] = tmp
 13             end
 14         end
 15     end
 16 end
 17 
 18 def print_array(array)
 19     for i in 0...array.length
 20         printf("%d ",array[i])
 21     end
 22     print("\n")
 23 end
 24 
 25 array = [23,45,1,2,455,667,875,5,0,34]
 26 print_array(array)
 27 bubble_sort(array)
 28 print_array(array)


実行結果

23 45 1 2 455 667 875 5 0 34 
0 1 2 5 23 34 45 455 667 875 

個人的に

0...size

この範囲オブジェクトとかいうのがすごく新鮮でした。
これ

0..size

こうするとsizeを含むようになるとか、、、、
早く慣れたい、、、、

【Androidアプリ開発】Google Maps Android API v2を使う

こんにちは。

バイトの方で地図関連の案件に携わっているので今日は予習も兼ねて、Google Maps Android API v2の導入をやってみたいと思います。

ここを参考にしました。

Google Maps Android API v2 — Google Developers

導入手順

  1. GooglePlayServicesの導入(本記事では割愛します)
  2. GoogleAPIキーの取得
  3. Manifestファイル記述
  4. 実際に使用してみる

APIキーの取得

まず、

https://cloud.google.com/console/project

にてプロジェクトを作成。(自分はすでに作成済みだったのでここはスルー)

作成したらそのプロジェクトのページへ。

左側のメニュー「APIs&auth」の中の「APIs」に飛んで、ここで「Google Maps Android API v2」をONにする。

「APIs&auth」の中の「Credentials」に飛んで「CREATE NEW KEY」ボタンを押してAndroidを選択。

入力するのは、

{SHA1 fingerprint};{パッケージネーム}

っていう形式になります。

SHA1 fingerprintの確認方法は、

Eclipseの環境設定の、Android→ビルドで確認できます。


f:id:taisho6339:20140111205333p:plain

こんな感じ。

これ入力してCreateすればOK!


Manifestファイルの記述

Manifestに追加するのは、4点になります。

1.OpenGL ESの依存明示

 <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />


なんかGoogleMapは内部でOpenGL ESを使用してるっぽいので書いておくらしい。
ちなみにこのタグがなんなのか調べたところ、端末と機能の依存を明示するタグで、この機能が端末にない場合インストールを防ぐことができるみたい。例えばカメラ持ってない端末へカメラアプリいれても使えないので、そういった場合フィルタリングかけられるみたいです。 なるほど・・・。

2.パーミッションの記述

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>


これらは上から順に、

 インターネットアクセス:マップのダウンロードに使う
 ネットワーク状態チェック:これも上に同じ
 外部ストレージへの書き込み:マップのキャッシュを保存しておくのに使う
 GoogleAPIへのアクセス:省略
 
残り2つは、ネットワーク経由での位置情報取得、GPS経由での位置情報取得。Mapを表示するだけならなくても平気だけどまあ絶対つかうだろう、、、ってことで、、、

3.GooglePlayServicesのバージョン情報

  <meta-data
          android:name="com.google.android.gms.version"
          android:value="@integer/google_play_services_version" />

4.APIキーの記述

  <meta-data
        android:name="com.google.android.maps.v2.API_KEY"
        android:value="自分のAPIキー"/>

これをすべて記述してマニフェストは完了になります。

実際に使用してみる

ここまで来たらあとは使うだけ!
ってことで試しに以下のコードを書いてみました。


activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>

</LinearLayout>

MainActivity.java


package com.taishonet.mapsample;

import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.app.Activity;

import com.google.android.gms.maps.MapFragment;


public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		FragmentManager manager = getFragmentManager();
		FragmentTransaction transaction = manager.beginTransaction();
		MapFragment map = new MapFragment();
		transaction.add(R.id.content, map);
		transaction.commit();

	}

}
 

実行結果

f:id:taisho6339:20140111231305p:plain


おー出た出た。

なにも表示されないって場合はAPIキーが間違っているとかかもです。
今日はここまで。