ここ数年を振り返って
およそ5年間、このブログは更新できてなかったんですが、今の会社で技術ブログは定期的に書いていたので、それをラップアップする感じでここ数年をまとめて振り返ってみようと思います(つまり穴埋め)
ちなみに今の会社に入ったのは2020年の1月で、その前はDeNAやメルカリに在籍していました。 そこもブログの空白期間なので、キャリアを振り返るという意味で少しだけ触れておきます。
まずDeNAではソーシャルゲームのフロントエンド開発に携わっていました。 ソーシャルゲーム全盛の時代、いくつものソシャゲが出てきては消えていく中で、何年もApp Storeのランキング上位に居続けるゲームの運営の熱意(というかもう愛)を間近で感じることができた日々でした。 直前にわずか2ヶ月で閉じることになったソシャゲに関わっていた身としては、晴天の霹靂の連続だったことを覚えています。
メルカリでは当初はフロントエンドエンジニアとして、後にエンジニアリングマネージャとしてサービスの開発・運営に携わっていました。 急成長するサービスと急拡大する組織の中で、生まれ続ける新たな課題に対し、考えて行動して失敗してまた考えてをずっと繰り返し続けた日々は学びの連続でした。 メルカリを卒業してもうすぐ3年になりますが、今でも組織的な課題が見つかったときに、「メルカリではこうだった」とまず考えることが本当に多いことに自分自身びっくりしています。 そのくらい、メルカリで学んだことが自分の考え方のベースになってるんだと改めて思います。
さて、これでブログの更新が途切れてから2年半を埋めることできたもちろんこんな数行ではできるはずがありませんができたので、残り2年半で何をやってきたかを技術ブログを元に書き出してみます。
tech.smartshopping.co.jp 入社最初のエントリーです。新規で開発中のプロダクトがVue/Nuxt.jsだったこともあり、入社直後からVueのキャッチアップを始めた頃です。 javascriptで書かれていたので、TypeScriptで書きたいなと思って一部導入をしてみました。ただVue2系とTypeScript相性はあまり良くなかったですね。 Vue3になって改善しましたが、かなりのBreaking Changeがあったため、移行は断念してReactに載せ替えたりしてます。
tech.smartshopping.co.jp ProductionでのエラーのトラッキングのためにSentryを導入した記事です。 試してみた系の記事は良く見かけますが、ここまで詳しくかかれた導入記事が日本語で少ないためか、結構PVが良かったです。 つい最近までこのエンジニアブログ内で一番PV集めてました。
tech.smartshopping.co.jp Cypressを使ったE2Eテストを導入した際の記事です。 コスト軽減のため、dashboard(課金)は使わずに、自前でMSを用意してk8s上でjob実行していました。 ただそれが要因化はわかりませんが、かなりのメンテコストを要したので、個人的にはdashboardで運用することを推奨します。
tech.smartshopping.co.jp Chrome拡張機能を作成したときの記事です。 記事の内容はなんの変哲もないChrome拡張機能の作成チュートリアルになってます。 業務では、E2Eのテストケースをweb上の操作から自動で作成するようなものを作りましたが、拡張機能のメンテコストの方が高くなってしまったため、今は使用していません。
tech.smartshopping.co.jp CypressによるE2Eテストを1年間メンテし続けた際の知見をまとめてみました。 E2Eをやってよかったことは、E2Eに対する知見が身についたことももちろんそうですが、 そもそもテストとはどうあるべきかなどについて色々調べることで知識が広がったことは良かったと思います。
tech.smartshopping.co.jp ReactとGraphQLを使用したプロダクトにおいて、単体テストをなるべくコスト低く効果的な書き方ができないかと色々調べて、実装してみた内容です。 サービスワーカーでレスポンスをハックするmswはとても筋の良いツールだと思います。
tech.smartshopping.co.jp 最近ではフロントエンドだけではなく、SET的な業務も行っており、開発環境の改善のためにgolangを書いたり、インフラ関連を触ったりもしています。
振り返ってみると、かなり色々なチャレンジができた5年間だったと思います。 とはいえまだまだエンジニアリングな人生は続くと思うので、チャレンジしていきます。
仮想マシン上で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
$ 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側で確認する
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
成功。スクリーンショットも取れてました。
さいごに
他のブラウザでも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」を検索すると数多くのプラグインが表示されるが、本体は↓
DLユーザ数を見れば一目瞭然
使いどころ
リグレッションテストに力を発揮する
※リグレッションテストとは:システムに修正を加えた時、今まで動いていたところに影響が出ないことを保証するためのテスト
なので、何かしらのプラットフォームなど、内部的な機能変更は頻繁に行われるが、大きなUIの変更はあまり無いサイトでは、テストさえ作ってしまえば大きくテスト工数を削減できる
Jenkinsと連携させて、毎日夜中にテストケースを自動実行させ、継続的改善につなげたりも
では、UIの変更が多いサイトでは使用しないほうが良いのか?
→機能が変わらなければ、PageObjectパターンを使用して記述すると、UI変更に対するテストコードの修正範囲を絞り込むことができるので、効率化できる(かもしれない)
課題
Seleniumによるテストは、継続的なメンテナンスが必須
メンテナンスをサボった挙句、テストがぶっ壊れて放置という問題も多い
ただし、うまく運用できれば、短時間で質の高い受入テストが可能なことは確か
短時間で質の高い受入テストが担保されることで、開発速度(サイクル)も結果的に早くなる
(影響範囲が大きくても、ガンガン修正できる)
関連リンク
公式ページ
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
rubyを実行するgruntプラグイン作るまで(3) - テストの記述
rubyを実行するgruntプラグイン作るまで(2) - 非同期でのコマンド実行 - 1010realのブログ ver2の続きです。
以前作ったgrunt-cclogdeleteをもう少し汎用的にして、外部コマンドを非同期実行するgrunt-run-asyncを作成しました。 外部コマンドなら何でも良いので、rubyに限らず、色々指定できます。
上記を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.を目指す。
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
で試したら、別のエラーがでました。
Promiseが定義されてないとのこと。jshintでes2015を許容しない設定になっているようです。
.jshintrcに以下のオプションを追加したら、問題なくjshintを通りました。
"esnext": true
最終的に、grunt test を実行し、jshint -> 用意したtest が全て通ればOKですね。
次回は、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択?
- require(child-process)からexecもしくはspawnを使う
- 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プラグインでの実行ファイルは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を呼び出して実行するところをかきます。