おっさんエンジニアの備忘録

ここは主におっさんエンジニアが技術的な事を調べたり、試してみたりしたことを備忘録的に書いていくものです。忘れっぽいからね。誰かの参考になったら嬉しい。

Padrinoをインストールしたよ

Padrinoを動かしてみるよ。

前回はRubyをインストールしたところまででしたが、今回はRubyのFWを入れて、Web開発を進めていきたいと思います。
今回選んだFWはPadrinoです。
http://jp.padrinorb.com/

PadrinoはSinatraをベースにしたFWで、ActiveRecordとか使えて便利みたいです。上述の公式(翻訳)サイトのドキュメントは途中まで日本語になっているのですが、まだまだ英語の領分が大きい・・・頑張ります。
インストール自体は簡単です。

$ mkdir ${work}
$ cd ${work}
$ bunlde init
$ echo 'gem "padrino"' >> Gemfile
$ bundle install --path vendor/bundle

Gemを使うと基本的にグローバルにインストールされてしまうのですが、--pathを指定することで、ローカルに閉じた状態でインストールすることができるようです。
ただ、この場合、グローバルに入っていないので、インストールしたモジュールへのアクセスがbundler経由じゃないと呼び出せないので注意が必要です。

Padrinoには便利なGeneratorがあるよ。

次に、サンプルのアプリを作成してみます。
今回はRestfulなAPIを作りたかったので、必要そうなものを最小限で構成してみます。
その他、いろんなモジュールを組み合わせてアプリを作ることができたります。詳しくは公式を見ていただけると大丈夫かと。

$ bundle exec padrino g project study_project -d activerecord -t rspec -e none 

これでアプリのひな形が出来ました。
ソース管理するときはこちらをルートにしてpushしてあげると良い感じです。

さて、実際に動かす準備を進めます。
まずは依存モジュールを落としてきます。こちらもグローバルが何となく気持ち悪かったので、ローカルに配置してあります。

$ cd study_project
$ bundle install --path vendor/bundle

無事に配置が終わったら、コントローラを作ります。
この辺もGeneratorがたくさんあるので、うまく活用していきます。

$ bundle exec padrino g controller Hello

先程も触れましたが、padrinoをグローバルに入れていないので、bundle execから呼び出します。これによりapp/controllers/hello.rbが出来ます。何やらコメントが書いてあるので、何となく書き方はわかるかと思いますが、一応以下のような記述を追記します。

get :world do
  "Hello World!"
end

これで準備は万端です。早速動作確認してみましょう。
RubyではWEBrickというサーバが同梱されているらしいので、そちらで動作確認できます。

$ bundle exec padrino s

「s」は「start」の意味です。
無事立ち上がったら、http://localhost:3000/hello/worldで動作確認してみます。

文字列が表示されたら成功です!
通常のWeb開発であれば、erbなどテンプレートエンジンを組み合わせて、画面描画を行ったりするのでしょうけど、今回はRestfulAPIを作りたいので、その辺は不要ですね。

APIにしたいのだけど・・・

RestfulなAPIを作るときにGrapeというものがあるのですが、どうもRailsを基準にしているのか、Padrinoではうまく動かないっぽいです・・・
Padrinoとのつなぎ込みのモジュールもあったのですが、PadrinoのVerが「0.11」に準拠しているらしく、おっさんの落としてきた「0.12」だとエラーになってしまいました・・・

次回はこの辺をしっかり調査して、何とか動かせるようにしたいです。
※もしくは妥協案を探したい・・・

Jenkins入れようとしてCentOSに新しいgitが入ってくれなくてハマった話

改めてJenkinsなんぞを入れてみようとしたのですが、Jenkins内部で想定しているgitのバージョンと、CentOSyumで入れられるgitのバージョンに差異があってエラーで落ちまくってしまいました・・・

男は黙ってソースコンパイル!かとも思ったのですが、yumで新しいのを入れる方法をネットで拾ったのでメモメモです。

$ sudo rpm --upgrade  http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm
$ sudo yum install --enablerepo=rpmforge-extras git

以上、小ネタでした。

Rubyの環境をMacで作ってみるよ

Rubyを触ってみるよ。

すっかりAngularJSにご執心のおっさんですが、業務でRubyを使うことになったので、自分で触ってみるか、ということになってしまいました。
まぁフロントから受けるAPI部分を作ると思えば、Rubyも楽しいはずですよね。

Rubyをインストールする。

手元のMacには、Rubyの2.0.xが標準で入っているようですが、既存のライブラリととの相性がまだよくないみたいなので、人気の高い1.9.3系を入れたいと思います。

Ruby自体のVerを管理してくれるrbenvというものがあるみたいなので、まずはそちらをインストールするところから始めます。

インストール自体はHomebrewでサクッとインストールできます。

$ brew update
$ brew install rbenv ruby-build

インストールできたら、~/.bash_profileに以下の記述を足します。

eval "$(rbenv init -)"

~/.rbenv/shimsがPATHに追記されるようになります。
※ちょっと不思議だったのは、Macの場合の.bash_profileってLinux系でいうところの.bashrcと同じ意味なんですね・・・bash bashするとパスがどんどん追加されていきます・・・

次にお目当てのRubyのVerを探します。

$ rbenv install -l

1.9.3系の新しいものをインストールしてみましょう。

$ rbenv install 1.9.3-p547

インストールできたら、切り替えを行います。

$ rbenv versions
* system (set by /Users/xxx/.rbenv/version)
  1.9.3-p547
$ rbenv global 1.9.3-p547
$ ruby -v
ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0]

おぉ、できた!
どうやらrbenv~/.rbenv/shims/配下のRuby系モジュールをスイッチしてくれるものみたいですね。

$ gem install bundler
Fetching: bundler-1.6.4.gem (100%)
Successfully installed bundler-1.6.4
1 gem installed
Installing ri documentation for bundler-1.6.4...
Installing RDoc documentation for bundler-1.6.4...

あ、入りました。
ちなみに今回はrbenv経由でのgemでインストールされているようなので、以下のコマンドを発行しないと使えるようになりません。

$ rbenv rehash

あと、rbenv経由であった場合は以下のコマンドでも同じことが起こるようです。(こっちが本線かな?)

$ rbenv exec gem install bundler

まぁどっちでも大丈夫っぽいです。

本日はとりあえずここまで

とりあえずインストールできましたが、「そもそもRubyってなんぞ?」っていうレベルのおっさんはきっとこれからたくさん落とし穴にはまっていくんだろうなぁ・・・
次回以降はフレームワークの選定やら、イケてる開発フローなんかを調べていけたらなぁと思っています。

ng-includeなどについてリクエストがどのくらい発生するのか?

ふと疑問に思ったこと

directiveやng-includeなどで、外部ファイルを読み込むようなことがあると思います。
$templateCacheでは、スクリプトタグで書くか、ロジックで設定するようにすると良いよ、と書かれています。

https://docs.angularjs.org/api/ng/service/$templateCache

でも実際、パーツとしてのHTMLを開発するにしても、追加するたびに1つのファイルに編集が集中してしまうのはなんかイヤな気がします。パーツとなるHTMLは開発中のモジュールと合わせて独立して追加ができたほうが効率は良さそうだなぁと感じるわけです。また、スクリプトタグでHTMLを書くと、IDEとかのハイライトが効かないんじゃないかとか思ってしまいます。
ちなみに別ファイルとして作成したパーツを自動でキャッシュに乗せてくれるgrunt-angular-templatesというものがあります。

https://github.com/ericclemmons/grunt-angular-templates

これを使っても良いのですが、livereloadに対応してくれるの?とかminifyさせるにはどこにタスクを差し込むの?とかいろいろ考えることが多そうです。

で、そもそもパーツのHTMLを直接叩いたら何が起こるか?を一応確認しておこうと思いました。
ネットの記事などでは、「アクセスするときにリクエストが発生してヤな感じ」みたいなことが書かれていましたが、実際はどう動いているんでしょうか?
とりあえず、ng-includeを使った簡単なサンプルを作ってみました。chromeの開発者ツールなのでリクエストの発生を見てみたいと思います。

テストのソースコード(同一階層に適当なa.htmlとb.htmlを置いてください)

<!DOCTYPE html>
<html lang="ja" ng-app>
  <head>
    <meta charset="utf-8">
    <title>テスト</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
  </head>
  <body>
    <h3>ng-includeのテスト</h3>
    <div>
      <label>
        表示する
        <input type="checkbox" ng-model="visible" ng-init="visible=true"></input>
      </label>
    </div>
    <div>
      <label>
        a.html<input type="radio" ng-model="incTemplate" ng-value="'a.html'"></input>
      </label>
      <label>
        b.html<input type="radio" ng-model="incTemplate" ng-value="'b.html'"></input>
      </label>
    </div>
    <hr />
    <div ng-show="visible" ng-init="visible = true">
      Show Area!
      <div ng-include="incTemplate" ng-init="incTemplate = 'a.html'"></div>
    </div>
  </body>
</html>

確かにb.htmlに切り替えたタイミングで通信は発生しているようですが、以降は通信は発生していませんでした。最初の通信時にキャッシュに乗ったので、通信はこれ以上発生しないみたいですね。

動作としては、遅延初期化のようなイメージでしょうか。すべてのテンプレートを読み込む前提であれば、1ファイルにまとまったテンプレートを一括で読み込んだほうがトータルの処理時間は短くなるかもしれませんが、最初のローディング処理が長くなってしまう気がしますね。個人的にはこの方法も大幅に間違っていない気もします。ケースによって使い分けても良いかもしれませんね。

protractorでのE2Eテスト観点でng-bindを使うべきかどうか。

APIマニュアルを見ていて気になりました。

こちらの記述を見ていてちょっと気になったことがあったので少し調べてみたメモです。

サンプルとして以下のようなソースがありました。

<span>{{person.name}}</span>
<span ng-bind="person.email"></span>
<input type="text" ng-model="person.name"/>

ていうソースにおいて。

// Find element with {{scopeVar}} syntax.
element(by.binding('person.name')).getText().then(function(name) {
  expect(name).toBe('Foo');
});

// Find element with ng-bind="scopeVar" syntax.
expect(element(by.binding('person.email')).getText()).toBe('foo@bar.com');

// Find by model.
var input = element(by.model('person.name'));
input.sendKeys('123');
expect(input.getAttribute('value')).toBe('Foo123');

と書きましょうとありますが、ちょっと「ん?」って感じ

expect(element(by.binding('person.name')).getText()).toBe('Foo');

じゃダメ?
と思ったので、試しに実行。ちゃんと動きますね。
ただし、{{person.name}}spanタグで囲っておかないと取得されるテキストデータがページ全体に広がってしまい大変なことになるので注意が必要でした。

{{person.name}}
<span ng-bind="person.email"></span>
<input type="text" ng-model="person.name"/>

これだと、{{person.name}}の一つ上のタグが対象範囲として取得されてしまうので、同階層のテキストが全部取れちゃいます。

どうもng-bindの利用が推奨っぽいのでそっちで書いておくのが安全そうですね。
※どのみちSPANで囲むのであればそっちの方がキレイかな?

generator-angular環境に手を加えて、protractorでE2Eテストを動かしてみる。

e2eテストってカッコ良いですよね

AngularJSの公式チュートリアルにはE2Eテストのチュートリアルも含まれています。
これを動かすと、ブラウザが立ち上がって、文字入力や画面遷移が自動的に実行され、テストされていきます。
個人的にはユニットテストだけでテストを担保するのに若干の不安がありましたので、ここまで外のレイヤーでのテストが継続的に行われていると安心できるかなぁと思っています。(もっともテストを書く手間が大変そうではありますが・・・)

是非ともこのテストも標準で行っていきたいのですが、generator-angularでは標準で動かす準備がないようです。
ng-scenarioを使ったやりかけの部分はあるようですが、AngularJSの公式では「ng-scenarioじゃなくてprotractor使いなよ」って言われていますので、そちらを使えるようにいろいろ調べて行きたいと思います。

protractorを動かすためのサンプルページを作る

今回はprotractorを動かすだけなので簡単なページです。
yo angularでできたアプリを簡単に改造しました。
なおデータの取得はソースベタになっていますのであしからず。

<div class="container" ng-controller="MainCtrl">
  <div class="row">
    <div class="col-xs-3">
      <form class="well" role="form">
        <div class="form-group">
          <label for="searchText">Test Search</label>
          <input id="searchText" type="text" class="form-control" ng-model="query.$" placeholder="input search text">
        </div>
        <div class="form-group">
          <label for="sortKey">Sort Key</label>
          <select id="sortKey" class="form-control" ng-model="sort.key">
            <option value="id">Default</option>
            <option value="title">Title</option>
          </select>
        </div>
        <div class="form-group">
          <label for="sortOrder">Sort Revers</label>
          <input id="sortOrder" type="checkbox" ng-model="sort.revers"></input>
        </div>
      </form>
    </div>
    <div class="col-xs-9" class="items-panel">
      <div class="panel panel-default" ng-repeat="e in items | filter:query | orderBy:sort.key:sort.revers">
        <div class="panel-heading">
          <div class="item-title" ng-bind="e.title"></div>
        </div>
        <div class="panel-body">
          <div class="item-description" ng-bind="e.description"></div>
        </div>
      </div>
    </div>
  </div>
</div>

以下はJSのコード。

'use strict';

angular.module('testApp')
  .controller('MainCtrl', function ($scope) {
    $scope.items = [
      {
        id:1,
        title:'HTML5',
        description: '説明文は省略'
      },
      {
        id:2,
        title:'AngularJS',
        description: '説明文は省略'
      }
    ];

    $scope.query = {
      $: ''
    };
    $scope.sort = {
      key:'id',
      revers:false
    };
  });

とりあえず、サンプルで動かすテストコードです。
AngularJSのサンプルから拾ってきたものです。(画面の挙動とは一切関係ありませんね・・・)

describe('angularjs homepage', function() {
  it('should greet the named user', function() {
    browser.get('http://www.angularjs.org');

    element(by.model('yourName')).sendKeys('Julie');

    var greeting = element(by.binding('yourName'));

    expect(greeting.getText()).toEqual('Hello Julie!');
  });
});

protractorを動かす

Gruntprotcatorを動かすモジュールを探したら、grunt-protractor-runnerなるものがヒットしたので、そちらを使ってみます。

https://github.com/teerapap/grunt-protractor-runner

インストールはいつも通りコマンド一発。

$ npm install grunt-protractor-runner --save-dev

Gruntfileにタスクを追記します。

protractor: {
  options: {
    configFile: "node_modules/protractor/referenceConf.js",
    keepAlive: true,
    noColor: false,
    args: {
    }
  },
  sample: {
    options: {
      configFile: "e2e.conf.js",
      args: {
        browser: 'chrome'
      }
    }
  },
},
// 中略
grunt.registerTask('e2e', function(){
  grunt.task.run([
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'protractor'
  ]);
});

protractorの設定ファイルは以下のとおりです。

exports.config = {
  allScriptsTimeout: 11000,

  specs: [
    'test/e2e/{,*/}*.js'
  ],

  capabilities: {
    // 'browserName': 'chrome'
    // 'browserName': 'firefox'
    // 'browserName': 'safari'
  },

  chromeOnly: false,

  baseUrl: 'http://localhost:9001/',

  framework: 'jasmine',

  jasmineNodeOpts: {
    defaultTimeoutInterval: 30000
  }
};

※注意!
chromeOnlyfalseに設定している場合、WebDriverを使うことになりますが、その場合はJDKのインストールが必須になります。事前に入れておいてください。
あと、立ち上げるブラウザの種類ですが、Gruntfileで指定する場合はbrowserでe2e.conf.jsで指定する場合はbrowserNameになります。(キーが違ってハマった・・・)
指定箇所を明示するためにあえてコメントで残してあります。

さて、準備万端、意気揚々とコマンド実行です。

$ grunt e2e

が、ダメ!動かない・・・
どうやらprotractorに同梱のwebdriver-managerを走らせる必要がありそうです。いろいろ調べたのですが、どうもgrunt-protractor-runnerは自身のnode_modulesディレクトリ配下のprotractorを使っているらしく、そちらのwebdriver-managerを実行してねって書いてありました。で、タスクは?うん、ないですね・・・えぇ〜!?
ちなみに変更履歴に以下のような記述がありました。

Temporarily remove automatically download/update webdriver-manager because it fails in some environment such as Windows (#41)

諦めんなよ!
しかたがないので、手実行、と言いたいところですが、これだと環境構築でめんどくさいことになりそうなので別の方法を探してみますが・・・見つからないです・・・
※途中grunt-protractor-webdriverというプラグインが見つかったのですが、これは違った・・・

幸い、protractorが個別にインストールされている場合はそちらを参照してくれるような作りになっているぽいので、何とかそちらで管理をしてみようと思います。
※グローバルインストールも考えたのですが、環境が個別にまとまっている感じが良かったので・・・

少々強引ではありますがgrunt-shellを使ってテスト前にwebdriver-managerのupdateを走らせることにします。
まずは必要なプラグインをインストール。

$ npm install protractor --save-dev
$ npm install grunt-shell --save-dev

続いてコマンドを登録します。

shell: {
  webdriver_update: {
    command: 'node_modules/.bin/webdriver-manager update'
  }
}

最後にタスクを修正します。

grunt.registerTask('e2e', function(){
  grunt.task.run([
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'shell:webdriver_update',
    'protractor'
  ]);
});

いざ実行。

$ grunt e2e

できた!けどイケてねぇ〜!
とりあえず作者の今後の活動に期待しときます・・・
※ここまで書いてnpmのpostinstallもアリかなぁとちょっと思いました・・・

実際のテストを書いてみる。

実際にサンプルに対してE2Eテストを書いてみました。

'use strict';

describe('sample e2e tests.', function() {

  var searchText,sortKey,sortRevers,itemsPanel;

  beforeEach(function() {
    browser.get('/index.html');
    searchText = $('#searchText');
    sortKey = $('#sortKey');
    sortRevers = $('#sortOrder');
    itemsPanel = $('.items-panel');
  });

  it('default search setting.', function(){
    expect(searchText.getText()).toBe('');
    expect(sortKey.getAttribute('value')).toBe('id');
    expect(sortRevers.isSelected()).toBeFalsy();

    itemsPanel.$$('.item-title').then(function(items){
      expect(items.length).toBe(2);
      expect(items[0].getText()).toBe('HTML5');
      expect(items[1].getText()).toBe('AngularJS');
    });
  });

  it('search "ang" result.', function(){

    searchText.sendKeys('ang');

    itemsPanel.$$('.item-title').then(function(items){
      expect(items.length).toBe(1);
      expect(items[0].getText()).toBe('AngularJS');
    });
  });

  it('search sort by title.', function(){

    $('#sortKey > option[value=title]').click();

    itemsPanel.$$('.item-title').then(function(items){
      expect(items.length).toBe(2);
      expect(items[0].getText()).toBe('AngularJS');
      expect(items[1].getText()).toBe('HTML5');
    });

  });

  it('search sort desc.', function(){

    sortRevers.click();

    itemsPanel.$$('.item-title').then(function(items){
      expect(items.length).toBe(2);
      expect(items[0].getText()).toBe('AngularJS');
      expect(items[1].getText()).toBe('HTML5');
    });
  });
});

今回はあえてAngularJSに特化しない形でテストケースを書いています。
具体的には、DOMの検証にはCSSセレクターのみを利用し、その要素に対してイベントを発行している、といった具合です。

基本的な書き方は恐らくJasmineに準拠するのですが、protractor用に幾つか便利なメソッドが提供されています。詳しい使い方は、公式のマニュアルが詳しいのでそちらを参照にすると良いです。

https://github.com/angular/protractor/blob/master/docs/api.md

以下、トピックを抜粋して紹介します。

ElementFinder.prototype.$

jQuery使えるのかな?と思いきや、実は全く違います。ElementFinderというオブジェクトをCSSセレクターで返すエイリアスの関数です。
ElementFinderは単一のDOMオブジェクトというイメージです。複数のオブジェクトを同一視することはなく、複数オブジェクトを取り扱う場合はElementArrayFinderというオブジェクトを使います。(ちょっと不便な気がしますね・・・今後改善されるのでしょうか?)
余程のことがなければjQueryライクに書けるとは思いますが、より厳密に書いてあげたほうが良さそうですね。

ElementFinder.prototype.$$

こちらはElementArrayFinderを返します。上述の通り、こちらは複数のオブジェクトというイメージですね。
複数のDOMが帰ってきますので、実際の値を取得するgetやらcountやらが用意されています。getで帰ってくるのはElementFinderになります。

ここで非常に注意したいのが、ElementFinderElementArrayFinderから値を取得するメソッドとして提供されているgetTextcountといったメソッドの戻り値はPromiseになっているということです。
Promiseの詳細については割愛しますが、ざっくり言うと、「非同期通信の結果を取得するためのエンドポイント」ということです。(Java でいうところのFutureみたいなものでしょうか)
Promiseを通じて取得される値は非同期で順序が保証されていませんので、今回のテストのようにthenなどを使って、コールバック経由でないと実際の値は取得できません。したがって、以下の様な書き方はできません。

if ($('#hogehoge').getText() == 'inputValue') {
  // logic
}

代わりに以下のように書きます。

$('#hogehoge').getText().then(function(text){
  if (text == 'inputValue') {
    // logic
  }
});

もうこうなってくるとわけわからなくなります。
コールバックの呼び出しより先に後続ロジックが動いてしまいますので、処理をシリアルに実行しようとするとthenで返ってくるPromiseを更にチェインして処理を書いて・・・なんて事になります。実際に、セレクトボックスの値を取得しようとループで回して選択値を取得して検証、なんてテストを書こうとしてえらく大変でした。なので、DOMの取得は極力CSSセレクタで一発で取れるよう記述しないと話になりませんね・・・

「値の比較ができないってことはexpectはちゃんと動くの?」と思ったのですが、こちらは正常に動作しているようです。中身までは確認していませんが、expectによる検証は内部的にPromiseを解釈して値検証をしていると思われます。expect自体はそれぞれが独立した検証であり、検証順序の厳密性は問われないからでしょう。なるほど、頭良いですね。

恐らく、WebDriverとの値のやり取りはプロセス間通信が発生するため、即時に値を取得してロジックに反映させることができないのではないかと推測しています。まぁこればかりは仕方ないですね・・・

webdriver.WebElement.prototype.click

DOM要素をクリックする処理です。「テストコードではElementFinderclickを呼んでいるけど大丈夫?」と思ったのですが、こちらは大丈夫なようです。

The ElementFinder can be treated as a WebElement for most purposes, in particular, you may perform actions (i.e. click, getText) on them as you would a WebElement.

webdriver.WebElement.prototype.sendKeys

こちらはキー入力を行うものです。
基本的な使い方は上述のclickと同じです。

まとめ

今回はあえてAngularJSに依存しないE2Eテストを書いてみましたが、AngularJSを使う場合はby.modelなどAnuglarJSの変数に直接アクセスできるようなコードも書けるみたいですので、よりテストはし易いかなぁと感じました。
しかしながら、今回のようにCSSセレクターを利用したDOMの取得と、クリックとキー入力というイベントの発火を行うだけであれば、非常に汎用的ですし、AngularJSを使っていないアプリに対してもテストが書けます。protractorはこの辺のサポート実装を行っていますのでテストのコードの記述量を抑える意味でも導入するメリットは大いにあります。

また、今回は試していませんが、distで本番用にコンパイルしたコードをベースにしたテストなどもできそうです。変なミスをなくす上では重要なアプローチですよね。
テスト自体が自動化できたということでJenkinsなどのCIツールと組み合わせたりすると、テストが捗りそうですので、機会があればその辺も調査していきたいと思います。
ただ、実際のアプリとなると、連携させるAPIの準備であったり、APIが返すデータをDBなどに初期投入するなど、まだまだやることは多そうです。今後の課題ですね。

とりあえず、開発するにあたって必要な環境構築はひと通りできたかなぁと思います。これでやっとAngularJSを使ったアプリ開発に入れますね。(長かった・・・)

今後は、実際のAnuglarJSを触りながら、気になったトピックなどを書いていきたいですね。(まぁあくまで備忘録なのであしからず)

GruntのbowerInstallタスクで差し込まれるJavaScriptファイルの順番

bowerInstallって便利ですよね

grunt-bower-installを使うと、bower.jsonで定義されたモジュールを自動的にHTMLファイルに反映させてくれます。すごく便利ですね。

で、ちょっと気になったのが、HTMLファイルに内容が反映される時に記述される順番です。ライブラリによっては依存関係があったりして、読み込み順序を間違えると正しく動かない可能性ってありますよね。そこで、ちょっと調べてみました。

https://github.com/stephenplusplus/grunt-wiredep

grunt-bower-installの依存先のwiredepの中(detect-dependencies.js)で優先度を決定しているっぽいです。(ソースを見た感じ)
優先度は、お互いがお互いの依存関係として登録されているかどうかを基準に判断されているようです。つまり依存されているライブラリは優先度が高くなり、上位に記述され、先に読み込まれます。なるほど、流石にインテリジェンスですね。
さらに、いくつかのライブラリ(jQueryなど)の優先度はすごく高いと決めて、それらを上位に持ってきているようですね。
それ以外については、bower.jsonの記述順序に読み込まれるようです。

ちょっと実験してみました。

"angular-sanitize": "1.2.15",
"angular-resource": "1.2.15",
"angular": "1.2.15",

だと

<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>

になります。

"angular-resource": "1.2.15",
"angular-sanitize": "1.2.15",
"angular": "1.2.15",

だと

<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

になります。

なるほど、普通に使う分には困ることはなさそうです。ちなみにgrunt bowerInstallを実行するたびにHTMLファイルは強制的に書き換えられてしまいますので、ちょっとだけHTMLで順序を書き換えてもすぐに上書きされてしまいます。bower.jsonを書き換えましょう。