とある角度から

お腹いっぱいたべられる幸せ

宣伝 - Webフロントエンドハイパフォーマンスチューニング

縁あって、こちらの書籍のレビューを担当させて頂きました。

gihyo.jp

webサイトのパフォーマンスと言っても、一概にこれといった指標はなく、リソースのローディングから、ツリー構築、描画、スクリプト実行速度など、様々な要素が影響してきます。

この本では、webサイトのパフォーマンスに纏わる様々な要素について、その仕組みから、観測手段、改善ノウハウまでをセットにして解説しています。

ブラウザに関する知識を深めたい方や、実践的な改善手段を知りたい方にはぜひオススメです。

5/26(金)発売予定ですので、書店で見かけたら手にとって頂ければ幸いです。
という宣伝でした笑

仮想マシン上でSelenium IDEで作ったテストケース(ruby)を動かす

やりたいこと

Selenium IDEでテストケースをポンポン貯めてって、Jenkinsで勝手にテストして、テストが通らなかった場合はチャットに流す仕組み。

ひとまずVM上でテストケースを動かすだけで、割とやることがあったのでメモメモ

環境

ホストOS

OS X 10.11.6(El Capitan)

ゲストOS

VirtualBox 5.1.14
Vagrant 1.9.1
Centos-7.3
Ruby 2.3.0(x86_64-linux

準備1:centos上でfirefoxを動かす

仮想ディスプレイXvfbとfirefoxをインストー
$ sudo yum -y install xorg-x11-server-Xvfb firefox
Xvfbの設定のため、bash_profileを編集

$ sudo vi ~/.bash_profileして以下の様に編集

# User specific environment and startup programs
export DISPLAY=:1

設定し終わったら、 $ source ~/.bash_profileで設定再読込

centos7を日本語環境化

必要なパッケージをインストー

$ sudo yum install ibus-kkc vlgothic-*

言語設定を「ja_JP.UTF-8」に変更

$ cat /etc/locale.conf
LANG="en_US.UTF-8"
$ sudo localectl set-locale LANG=ja_JP.UTF-8
$ source /etc/locale.conf
$ cat /etc/locale.conf
LANG=ja_JP.UTF-8
動作確認のため、画面キャプチャを取れるようにする

ImageMagickをインストー

$ sudo yum -y install ImageMagick

スクリーンショットを撮るためのxwdコマンドをインストー

$ sudo yum -y install xwd netpbm-progs
仮想ディスプレイでfirefoxを起動してスクショを確認

xvfbを起動

$ Xvfb :1 -screen 0 1024x768x24 > /dev/null &
[1] 5450

firefoxを起動。ディスプレイは仮想ディスプレイを指定

$ firefox -display:1 -width 1024 -height 800 > /dev/null &
[2] 5633
$ Xlib:  extension "RANDR" missing on display ":1".
GLib-GIO-Message: Using the 'memory' GSettings backend.  Your settings will not be saved or shared with other applications.

バックグラウンドで実行されているプログラムを確認

$ jobs
[1]-  実行中               Xvfb :1 -screen 0 1024x768x24 > /dev/null &
[2]+  実行中               firefox -display:1 -width 1024 -height 800 > /dev/null &

画面キャプチャを出力

$ xwd -display :1 -root | xwdtopnm | pnmtopng > test.png
xwdtopnm: writing PPM file
$ ls -la
-rw-r--r--.  1 vagrant vagrant 2359312  4月 28 08:29 test.png

これを共有フォルダ(ゲストOSとホストOS間の)とかに移動して、ホストOS側で確認する
f:id:n_1010real:20170510181815p:plain
Good!

準備2:seleniumからfirefoxを操作するために必要な設定

selenium-webdriver(Ruby)をインストー
$ gem install selenium-webdriver -v 3.4.0
geckodriverを取得、パス配下へ移動して、再読込
wget https://github.com/mozilla/geckodriver/releases/download/v0.16.1/geckodriver-v0.16.1-linux64.tar.gz
tar -xvzf geckodriver-v0.16.1-linux64.tar.gz
sudo mv geckodriver /usr/local/bin/
source ~/.bashrc

テストケースを作成し、ゲストOSで動くように書き換える

適当なテストケースをseleniumIDEで作成し、「Ruby / Test::Unit / WebDriver」でエクスポート

エクスポートしたテストケースがこちら → gist0c078b912c7732c0a14cad07ce41d685

ゲストOSで動くように、テストケースを書き換え
  • ${receiver}を@receiverに置き換え
  • captureEntirePageScreenshotをdriver.save_screenshotに置き換え(プライベートAPIらしいので非推奨)

修正したテストケースがこちら → giste31d0aea526a5b6f2e47da08fdd5e402

ゲストOSで、テストケースを実行

上記を共有フォルダへ置き、vagrant環境で参照できるようにして実行する
(ファイル名は「case1」gistの名前とは一致してません)

$ ruby case1

f:id:n_1010real:20170510185937p:plain 成功。スクリーンショットも取れてました。 f:id:n_1010real:20170510190238p:plain

さいごに

他のブラウザでもwebDriverを登録して、スクリプトを少し修正すればいけるはず。
ただ、テストケースから直接webDriverを呼び出しているので、ブラウザ毎(&ドライバ毎)にスクリプトの修正が必要になる。
呼び出すDriverの切り分けはSelenium Serverにお任せしたいので、それも試さなければ。。

参考

ありがとうございました!
Vagrant の CentOS で Selenium テストを回したい - Qiita
CentOS7の日本語化(日本語環境で利用する)
RubyでSeleniumを使ってスクレイピング - Qiita
Selenium IDEからRubyのテストケースを書き出す | | Scimpr Blog
入門、Selenium - Seleniumの仕組み | CodeGrid

Seleniumまとめ

久しぶりにSeleniumを使ったら、色々進化していたので、
自分の中の情報アップデートのためにアウトプット

Seleniumとは

ブラウザ上での操作をスクリプトで記述し、実行できる
テストコードはHTML もしくは以下の開発言語で記述可能
Java」「C#」「Ruby」「PHP」「Perl」「Python」「JavaScript(Node.js)」等

Seleniumの歴史

Selenium Core

初期のSelenium
事前にwebサーバ上にSelenium Coreエンジンとテストスクリプト配備
ローカルPCからのアクセス時に、上記を埋め込んだページを返すことで、テストスクリプトを実行
Javascriptなので、セキュリティ的な制約に引っかかる
現在は非推奨

Selenium RC (selenium1)

webサーバ上にテストスクリプト配備しなくても良いように、Selenium Serverという中継サーバを介してブラウザを操作する
Core同様、Javascriptなので、セキュリティ的な制約に引っかかる
現在は非推奨

WebDriver

ブラウザの拡張機能やOSの機能を使用してブラウザを操作する仕組み
ブラウザ毎にDriverが用意されている(Chrome,IE,Edge,FF,PhantomJS,Opera,Safari…)
JavaScriptではないため、CoreやRCの様に、セキュリティ的な制約を受けない
W3Cで標準化が進められており、2017/4/20現在のステータスは勧告候補(CR)
https://w3c.github.io/webdriver/webdriver-spec.html

Selenium WebDriver(selenium2)

selenium1 + WebDriver
Selenium RCにおけるJavascriptによるテストケース実行をWebDriverに置き換えたもの
今はこれ

Selenium IDE

ブラウザの操作を記録して実行できるFirefoxのアドオン
記録したテストケースから修正を行い別のテストケースを作ることも可能
テストケースを他言語にエクスポートして実行することも可能(nodejsは未対応。。)
WebDriverPlaybackを使用しクロスブラウザで実行することが可能
この機能を拡張するプラグインもかなりの数出ている(有名なものではSelblock等)

Firefoxのアドオンで「Selenium IDE」を検索すると数多くのプラグインが表示されるが、本体は↓
f:id:n_1010real:20170425192131p:plain DLユーザ数を見れば一目瞭然

使いどころ

リグレッションテストに力を発揮する
※リグレッションテストとは:システムに修正を加えた時、今まで動いていたところに影響が出ないことを保証するためのテスト
なので、何かしらのプラットフォームなど、内部的な機能変更は頻繁に行われるが、大きなUIの変更はあまり無いサイトでは、テストさえ作ってしまえば大きくテスト工数を削減できる
Jenkinsと連携させて、毎日夜中にテストケースを自動実行させ、継続的改善につなげたりも

では、UIの変更が多いサイトでは使用しないほうが良いのか?

→機能が変わらなければ、PageObjectパターンを使用して記述すると、UI変更に対するテストコードの修正範囲を絞り込むことができるので、効率化できる(かもしれない)

課題

Seleniumによるテストは、継続的なメンテナンスが必須
メンテナンスをサボった挙句、テストがぶっ壊れて放置という問題も多い
ただし、うまく運用できれば、短時間で質の高い受入テストが可能なことは確か
短時間で質の高い受入テストが担保されることで、開発速度(サイクル)も結果的に早くなる
(影響範囲が大きくても、ガンガン修正できる)

関連リンク

公式ページ

http://www.seleniumhq.org/

Seleniumリポジトリ

https://github.com/SeleniumHQ/selenium
マニュアル、APIドキュメントはこちらを参考に

日本Seleniumユーザーコミュニティ

http://www.selenium.jp/
テスト用ページが用意されている

参考リンク

色々参考にさせてもらいました!あざす!

Selenium WebDriverとSelenium IDEなどについて

https://simple-it-life.com/2016/07/24/selenium-type/

[2016年時点] Selenium IDE のまとめ (インストールから応用まで)

http://qiita.com/gluelan2013/items/daa8680e8e86c0937743

5分でわかるSelenium IDEの使い方

http://qiita.com/naoqoo2/items/90d382cd9370d3526509

Selenium IDEのインストール

http://qiita.com/takara@github/items/0d00260c84592f786968

Seleniumでテスト失敗時のスクリーンショットを取得する方法

http://softwaretest.jp/labo/tech/labo-298/

SeleniumのPageObjectパターンを学ぶ

http://ninoseki.hatenablog.com/entry/2013/01/09/212424

失敗からはじめるSelenium

http://blog.cybozu.io/entry/5499

rubyを実行するgruntプラグイン作るまで(3) - テストの記述

rubyを実行するgruntプラグイン作るまで(2) - 非同期でのコマンド実行 - 1010realのブログ ver2の続きです。

以前作ったgrunt-cclogdeleteをもう少し汎用的にして、外部コマンドを非同期実行するgrunt-run-asyncを作成しました。 外部コマンドなら何でも良いので、rubyに限らず、色々指定できます。

github.com

上記をnpmに登録する前に行った、gruntプラグインのテストの記述について、やったことを書いていきます。

1.テストを書く

grunt-initテンプレートで作成された、テスト用フォルダの構成は以下の通り。

  • test : テスト用ファイル置き場
    • fixtures : タスク実行対象ファイル
    • expected : タスク実行後の(期待する)結果
    • xxxxx_test.js : テスト用js
    • tmp : grunt testを実行すると作られる。タスク実行後の結果ファイルがここに出力される

grunt testを実行すると、test/fixtures内のファイルを対象にタスクを実行し、結果をtmp配下に出力します。
その後、出力結果がtest/expected内のファイルと正しいかどうかでテスト通過判定しているので、その辺のつじつまを合わせてあげれば良い。
(「grunt testを実行してつくられるtmpフォルダの中身 == test/expectedの中身」となればテスト成功)

2.テストを通るまで機能をデバッグ

grunt testを繰り返し、Done, without errors.を目指す。 f:id:n_1010real:20170418134452p:plain

3.jshintを通す

デフォルトのgruntタスクを叩くと、jshint後に記述したテスト(grunt test)を実行する模様。
せっかくなら両方通したいので、jshintが通るように、jsを整理します。

ここで、nodeのバージョンを最新(v6.10.2)にあげたら、jshintでエラーがでたので、grunt-contrib-jshintモジュールを最新にあげました。

npm uninstall grunt-contrib-jshint --save-dev
npm install grunt-contrib-jshint --save-dev

で試したら、別のエラーがでました。 f:id:n_1010real:20170418135225p:plain

Promiseが定義されてないとのこと。jshintでes2015を許容しない設定になっているようです。
.jshintrcに以下のオプションを追加したら、問題なくjshintを通りました。

"esnext": true

最終的に、grunt test を実行し、jshint -> 用意したtest が全て通ればOKですね。 f:id:n_1010real:20170418135658p:plain

次回は、npmへの登録部分を書きます。 他のサイトに書いてあることをそのままやっただけなので、書くのはやめました。

rubyを実行するgruntプラグイン作るまで(2) - 非同期でのコマンド実行

rubyを実行するgruntプラグイン作るまで(1) - gruntpluginのテンプレートを実行の続きです。

実行するrbファイルを作成

cocos2d-jsを使う機会があって、ビルド時にcc.log();を全て削除したかったので、そんなrbを作りました。
grunt-cclogdelete/cclogdelete.rb at master · 1010real/grunt-cclogdelete · GitHub

以下のような感じで、引数にファイル指定すると、ファイルの中身からcc.log( … );を削除したものを標準出力に出力します。

ruby cclogdelete.rb [対象ファイル]

gruntでコマンドを実行するには

次に、gruntからruby(というかコマンド)の実行をするにはどうするか小一時間ググる。と以下の2択?

  1. require(child-process)からexecもしくはspawnを使う
  2. grunt.util.spawnを使う

後々のgulpへの移行を見据えて1を選択。
ちなみに今回はruby実行時の標準出力をそのままファイルに出力したいので、そんな書き方は以下の様になります。

gruntでコマンドの終了を待つ(非同期で実行する)には

先ほどのコードをそのままtasks/cclogdelete.jsに記述して実行しても、うまく動きません。
なんか、rubyコマンドを呼び出した後、gruntが特に何も処理することがなくなったと認識して勝手に終了してしまう。(参考:Grunt で非同期タスクを調べてみたが・・・。 - Qiita

調べてみると、非同期でのタスクの場合には var done = this.async(); を呼出して、非同期ですってことをgruntに通知&戻ってきた関数(done)を全てのタスクが終わったら呼び出せということらしい。

てことで作ったのが以下。
処理するファイル数分のspawn作って、コマンドの終了した数が処理ファイル数になったらdone()してます。

一応そんな感じでオレオレプラグインが作成できました↓
GitHub - 1010real/grunt-cclogdelete: Delete cclog() with ruby script.

(記事分けたけど、実はもうあんまり書くことなかったことに気付いて、ソース貼り付けて稼ぎました汗)

上記をもうちょっと汎用的に、コマンドを非同期実行するgruntプラグインを作成中で、
そこではテストも書いていて、Readme.mdとnpmへの登録もする予定なので、次回はその辺を書きたいと思います。

rubyを実行するgruntプラグイン作るまで(1) - gruntpluginのテンプレートを実行

rubyのプログラムをgruntタスクの合間に挟みたかったので、gruntプラグインを自作することにしました。
物はこちら(※テストも書いてないし、npmに登録もしていないオレオレプラグイン汗)
github.com

gruntプラグイン用のテンプレートを持ってくる

まずはgruntpluginを作るときのテンプレートを作成してくれるgrunt-initモジュールをインストール

npm install -g grunt-init

使い方わからないのでヘルプを見る

grunt-init --help
grunt-init: Generate project scaffolding from a template. (v0.3.2)

Usage
 grunt-init [options] [template]

...

Available templates

(No templates found)

Templates that exist in the /Users/xxxxxx/.grunt-init directory may be
run with "grunt-init TEMPLATE". Templates that exist in another location may be
run with "grunt-init /path/to/TEMPLATE". A template is a directory that must
contain, at the very minimum, a template.js file.

For more information, see http://gruntjs.com/project-scaffolding

ふむふむ「有効なtemplatesはない」テンプレートは~/.grunt-initディレクトリの中に云々
詳しくは公式を見よと書いてある。
なので見てみると、いくつかテンプレートがあるみたいです。

今回はgruntplugin用のテンプレートを~/.grunt-initディレクトリにcloneしてきます。

mkdir ~/.grunt-init
git clone https://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin

で、適当なフォルダの中で、以下のコマンドを実行すると色々聞かれます。
(今回はすでにgruntを動かしているプロジェクト内のnode_moduleフォルダ内に、grunt-cclogdeleteという名前で空フォルダを作ってそこで実行しました)

grunt-init gruntplugin
Running "init:gruntplugin" (init) task
...

[?] Project name (grunt-cclogdelete) 
[?] Description (The best Grunt plugin ever.) 
...(ここ幾つか質問される)

Writing .gitignore...OK
Writing .jshintrc...OK
...

Done, without errors.

質問に全て答えると色々ファイルを作ってくれました。
githubリポジトリ指定まで聞いてくるので、とりあえず適当に答えてあとから修正しましたw
(ちなみにフォルダ内に何かファイルがあると怒られるので空にしておきましょう)

とりあえずこの状態で一旦githubリポジトリを作ってあげます。

git init
git add .
git commit -m 'first commit'
git remote add origin git@github.com:1010real/grunt-cclogdelete.git
git push -u origin master

grunt-cclogdeleteの初期コミット内容はこちら

テンプレートの中身は?

gruntプラグインでの実行ファイルはtasksフォルダ内に置かれます。
なのでtasks/cclogdelete.jsを眺めてみると、オプションを受け取ってデフォルト値とマージした後に、ファイルリストを受け取って読み込み→destに出力する部分が用意されてるぽい。感動。

とりあえずそのまま動かしてみる。プロジェクトのGrunffile.jsを編集
(gruntpluginテンプレートに含まれるGruntfile.jsでは無いです。分かりにくくてすみません。)

module.exports = function(grunt) {
  grunt.initConfig({

...

// 以下追記
    'cclogdelete': {
      build: {
        options: {
          param: 'test_param'
        },
        files: [{
          expand: true,
          cwd: 'input',
          src: '**/*.js',
          dest: 'output/',
          ext: '.js'
        }],
      }
    },

  ...

  }
});

// 追記
grunt.loadNpmTasks('grunt-cclogdelete');
...

実行してみる

grunt cclogdelete:build
Running "cclogdelete:build" (cclogdelete) task
File "output/src/lib/lodash.js" created.

...

Done, without errors.

動いてる動いてる。
ちなみにオプションがちゃんと渡ってるかどうかを確かめるため、tasks/cclogdelete.jsを編集

...

  grunt.registerMultiTask('cclogdelete', 'delete cclog() with ruby script.', function() {
    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      punctuation: '.',
      separator: ', '
    });

    grunt.log.writeln(JSON.stringify(options)); // これ一行追記

    ...

実行すると以下のような出力が追加されている。
grunt.initConfigで追加したparam:test_paramが渡ってます。おけおけ

{"punctuation":".","separator":", ","param":"test_param}

長くなってきたので、記事を分けます。。。

とりあえず、grunt-init gruntpluginで作られるテンプレートでは、grunt.initConfigで指定されたファイルを読み込んで(expand対応)、それをそのままdestに出力するところまで含まれるようです。
内容をカンマ区切りで結合して、最後にピリオドを追加した結果をdestに出力するところまで含まれるようです。テスト書いてて気付きました(2016/03/12追記)

次の記事で、rubyを呼び出して実行するところをかきます。

backbone.jsでtodoアプリを少しずつ作る(2)

まえおき

前回作ったbackbone.jsのtodoサンプルをより洗練した形に作り変えていきます。

「backbonejsを使ってアプリを作る」から「作りたいアプリのために、backbonejsを使いこなす」ようになるのが最終目標。。。

AppViewのシングルトン化

今回の設計では、アプリ全体を管理するAppViewは常に1つであるべきです。
2回以上newしないように、気をつけてコーディングしてもいいのですが、プログラム側でインスタンスの生成は1度しか行われないようにするのがベストです。

なので、AppViewのconstructorをオーバーライドしてみます。

var AppView = Backbone.View.extend({
         ・・・
  constructor: function() {
    if (!AppView.instance) {
      AppView.instance = this;
      Backbone.View.apply(AppView.instance, arguments);
    }
    return AppView.instance;
  },
         ・・・
});

これで2回目以降のインスタンス生成の際は、最初に作成したインスタンスを参照するようになります。

onはlistenToで代用

FoodViewのinitializeメソッドで、modelに対する変更・削除イベントを購読し、自身のメソッドをコールバックとして割り当てています↓

var FoodView = Backbone.View.extend({
         ・・・
  initialize: function() {
         ・・・
    this.model.on('change', this.render, this); // ここ
    this.model.on('destroy', this.remove, this); // ここ
         ・・・

この場合、Model(発行者)に対して、FoodView(購読者)のメソッドが割り当てられているせいで、ViewをDOMから削除しても、Model側にViewへの参照が残ってしまいます。
結果、削除したViewはガーベジコレクションの対象にならず、この状態で何度もビューを再作成してたりするとメモリリークを起こしてしまいます。

これに対し、listenToを使うとViewがDOMから削除(remove)されたタイミングで、ModelからViewへの参照を切ってくれます。
※listenToで登録した際に、発行側のオブジェクトを保持しておいて、View削除の際に、ちゃんとoff(onの逆メソッド)してくれているようです。

なので、以下のように書き換えます。

var FoodView = Backbone.View.extend({
         ・・・
  initialize: function() {
    //修正前
    //this.model.on('change', this.render, this);
    //this.model.on('destroy', this.remove, this);

    //修正後
    this.listenTo(this.model, 'change', this.render);
    this.listenTo(this.model, 'destroy', this.remove);
         ・・・

※AppViewのinitializeメソッドは、後程、他の修正と一緒に変更します。

グローバルっぽい変数fcへの依存をなくす

NewViewのaddFoodメソッドの中で、fc.createうんちゃら観たいな記述がありますね↓

var NewView = Backbone.View.extend({
         ・・・
  addFood: function() {
    fc.create({name:$(this.el).find('input#newfood-name').val(), ... (省略 // ここ
         ・・・

この書き方だと、fcが定義されていなかった場合にはエラーを起こしますし、fcという名前が変更になったら、ここも修正が必要になります。
つまり、NewViewはfcに依存したモジュールになってしまっています。


同じことが、FoodListViewにも言えます↓

var FoodListView = Backbone.View.extend({
         ・・・
  addAll: function() {
    fc.each(this.add, this); // ここ
         ・・・


そもそも、なんともグローバルチックな宣言をされているfc...がそもそもいけてない↓

var fc = new FoodCollection;

FoodCollectionは、アプリ全体で扱われるデータなので、アプリ全体を管理するAppViewで一度だけ定義して、変更があってもそこを修正するのみにしたい。

ということで、以下のような感じで修正します。

1. fcけしちゃうっ

//var fc = new FoodCollection;

2. AppViewの初期化時に、FoodCollectionインスタンスを生成し、各Viewに渡す。

var AppView = Backbone.View.extend({
         ・・・
  collections:{}, // 追加
  initialize: function() {
    this.collections.foods = new FoodCollection; // 追加
    this.views.foodlist = new FoodListView({collection:this.collections.foods}); // 変更
    this.views.new = new NewView({collection:this.collections.foods}); // 変更
    this.collections.foods.fetch({reset:true}); // 変更
    // 以下、削除
    //fc.on('add', this.views.foodlist.add, this.views.foodlist);
    //fc.on('reset', this.views.foodlist.addAll, this.views.foodlist);
    //fc.fetch({reset:true});
         ・・・

3.NewView,FoodListViewで、渡されたcollectionを参照するように修正

var NewView = Backbone.View.extend({
         ・・・
  addFood: function() {
         ・・・
    // 修正前
    //fc.create({name:$(this.el).find('input#newfood-name').val(), calory:$(this.el).find('input#newfood-calory').val()});
      
    // 修正後
    this.collection.create({name:$(this.el).find('input#newfood-name').val(), calory:$(this.el).find('input#newfood-calory').val()});
         ・・・
var FoodListView = Backbone.View.extend({
         ・・・
  initialize: function() {
         ・・・
    // 追加(AppViewでのonをlistenToに変更してこちらに移動)
    this.listenTo(this.collection, 'add', this.add);
    this.listenTo(this.collection, 'reset', this.addAll);
  },
         ・・・
  addAll: function() {
    // 修正前
    //fc.each(this.add, this);
    // 修正後
    this.collection.each(this.add, this);
         ・・・

fcが消えた!すっきり

Modelの変更とDOMの操作は切り離す

NewViewのaddFoodメソッドですが、Foodを追加した後にthis.render()を呼び出しています。

var NewView = Backbone.View.extend({
         ・・・
  addFood: function() {
      
    this.collection.create({name:$(this.el).find('input#newfood-name').val(), calory:$(this.el).find('input#newfood-calory').val()});
    this.render(); // ここ
         ・・・

例えば、ここでさらに、editFoodとか、copyFoodとかいうメソッドを追加したらどうでしょう。
各メソッドの最後にthis.render()を呼び出すでしょうか。。。否、断じて否!

そもそも、Modelの変更とDOMの操作は完全に切り離されていなきゃMVCとは言えません。
という訳で以下のように修正します。

var NewView = Backbone.View.extend({
         ・・・
  initialize: function() {
    this.render();
    this.listenTo(this.collection, 'add', this.render); // 追加
  },
         ・・・
  addFood: function() {
    this.collection.create({name:$(this.el).find('input#newfood-name').val(), calory:$(this.el).find('input#newfood-calory').val()});
    //this.render(); 削除
         ・・・

Collectionに対する変更を監視する形にしてあげれば、editFoodでもcopyFoodでも、最終目的はCollectionの操作までという感じではっきりします。

自身のビューに対する操作でも、きっちりModel,Collectionの修正を監視して操作してあげると、ますます、MVCな感じになります。

完成したサンプル

やってみて

backbonejsは自由度が高い分、設計段階でとても悩みますね。
でもだからこそ、jQuery万歳な人から少しは抜け出せてきた気がする。。。
まあマイペースでがんばるお