JARファイルからAARファイルとEclipseライブラリプロジェクトを作成する方法

Pocket

こんにちは。AdStir開発チームの森田と申します。
弊社SSP『AdStir』のSDKを開発しております。

はじめに

 AdStirのSDKでは他社のアドネットワークのSDKをバンドルして配布することがあります。弊社ではその際にメディアさんの利便性を向上するための工夫をしています。
 Android用のSDKは、ライブラリ(JARファイル)、リソースファイル、AndroidManifestがバラバラに配布されることがあります。 その場合、ユーザがJARファイルとリソースファイルをプロジェクトに組み込み、AndroidManifestをマージする必要があります。 AARファイルやEclipseライブラリプロジェクトを使用することでこれらの手間を省くことができます。 そのため一部のSDKをAARファイルやEclipseライブラリプロジェクトにパッケージ化して配布しています。
 今回はJARファイルからAARファイルとEclipseライブラリプロジェクトを作成する手順を紹介します。

JARファイルとは?

Java Archiveの略称で、Javaアプリケーション(Android)のライブラリの配布に使用することがあります。

AARファイルとは?

Android Archiveの略称で、Android Studioに使われるアーカイブです。 ライブラリに加えてリソースファイル・AndroidManifestを含めることができます。

Eclipseライブラリプロジェクトとは?

ライブラリとして使用できるEclipse用のプロジェクトです。 メインプロジェクトのライブラリに追加することで、ライブラリとリソースを追加できます。

AAR作成方法

Android Studioを使用してJARファイル・リソースファイル・AndroidManifestからAARファイルを作成します。

1. モジュールの作成

File -> New -> New Module -> Android Libraryでモジュールを作成します。 aar-01

aar-02

2. ファイルの追加

作成したモジュールにJARファイル・リソースファイル・AndroidManifestを追加します。 libsフォルダにjarファイルをresフォルダにリソースファイルを追加し、AndroidManifestを記載します。 aar-03

3. AARファイルの作成

作成したモジュールを選択して、Build -> Make Module ‘モジュール名’でAARを作成します。 aar-04

’モジュール名’ -> build -> outputs -> aar フォルダに AARファイルが生成されています。 aar-05

debug/releaseの両方のaarが必要な場合、appのbuild.gradleのdependenciesに下記を追加してMake Projectをします。

Eclipseライブラリプロジェクト作成方法

Eclipseを使用してJARファイル・リソースファイルからライブラリプロジェクトを作成します。

1. プロジェクトの作成

File -> New -> Android Application Projectでプロジェクトを作成します。 Mark this project as a libraryにチェックを入れ、Create Activityのチェックを外します。 eclipse-01 eclipse-02

2. ファイルの追加

作成したプロジェクトにJARファイルとリソースファイルを追加します。 libsフォルダにJARファイルをresフォルダにリソースファイルを追加します。フォルダがない場合は作成してください。 eclipse-03

3. ライブラリプロジェクトの作成

作成したプロジェクトを選択し、Export -> Java -> JAR file で以下の項目を選択を選択します。

  • res
  • libs
  • .classpath
  • .project
  • AndroidManifest
  • proguard-project.txt
  • project.properties

Export Jar source files and resourcesのみ選択し、Optionは全てチェック選択します。 エクスポート先を指定してFinishをクリックします。 eclipse-04

作成したjarを解凍してライブラリプロジェクト完成です。

まとめ

今回はJARファイルからAARファイル・Eclipseライブラリプロジェクトを作成する手順を紹介しました。1度作成すると簡単に取り扱えるようになるので、同じライブラリを色々なプロジェクトで使う場合ではオススメです。

AWS利用におけるIAMの設計

Pocket

こんにちは。配信/インフラチームの石田です。
現在DACから出向でユナイテッドでお世話になっております。
DACでも社内/サービス側両方のインフラ担当を担っております。

弊社では、AWSをDSP/SSPのサービスの基盤として利用しております。
また、AWSの利用者も、インフラチームや開発者、データ解析チームまで様々です。
「皆が同じルートアカンウトでログインし、全操作可能な状態」はセキュリティ的に問題があると考えております。

今回は、AWSのリソースアクセスコントロールを行い、セキュリティを向上する目的で導入したIAMの設計を紹介します。ルートアカウントでのAWSログインの廃止、CloudTrailでの操作ログ取得を実施するまでの過程を紹介します。

■IAMとは

IAMとは、Identity and Access Managementのことです。
ユーザーに対してAWSへのアクセスを安全に制御するための仕組みで、無料で導入できるのが魅力です。簡単に言うと、「AWSアカウントの中で、だれが何をできるかをコントロールするためのサービス」です。

■認証情報の種類

AWSにおける認証情報の種類としてはと下記の4つのクレデンシャルがあります。

  • AWSアカウントのパスワード
  • AWSアカウントのAPIキー
  • IAMユーザのパスワード
  • IAMユーザのAPIキー

■前提方針

前提条件としては、下記2つです。当然ですが、AWSルートカウントを利用しない方針でなければCloudTrailでの操作ログとして成立しません。

  • AWSルートアカウントはパスワードを変更し、利用しない。
    登録情報等の管理と請求関連操作にだけ使う。
  • AWSルートアカウントのAPIキーは利用しない。

■設計

今回弊社では、「管理者・開発者・運用者の役割を設け、開発者の自由度を考慮した設計案」を採用しました。基本的には、個人にポリシーを付与する形ではなく、グループにポリシーを付与する形式をとっております。また、ポリシーを個別に作成することも可能ですが、AWS側であらかじめ準備しているポリシーを利用します。

◉グループ
弊社では、AWS利用ユーザを3分類し、開発者の自由度を高く設計しております。

  • 管理者(Admin)
    →全サービス操作可能

  • 開発者(Developers)
    →IAM以外全サービスの操作可能

  • 運用者(Operators)
    →全サービスの参照のみ可能

◉ポリシー
今回は下記5つのポリシーを上記グループに付与する形で権限管理を行っております。

  • ”Administrator Access”
    →AWSアカウントに代わる強力な権限。全操作可能(既存)

  • ”Power User Access”
    →上記、Admin用からIAM操作権が省かれたポリシー(既存)

  • ”Read Only Access”
    →参照用ユーザ向けポリシー(既存)

  • ”ViewBilling”
    →請求情報参照用ポリシー(独自)

  • ”ChangePassword”
    →初回ログインPW変更可能ポリシー(独自)

◉グループとポリシーの関係
最終的なグループへの付与ポリシーは下記の通りです。

  • 管理者(Admin)
    ”Administrator Access”, ”ChangePassword”

  • 開発者(Developers)
    ”Power User Access”,”ChangePassword”

  • 運用者(Operators)
    ”Read Only Access”,”ChangePassword”,”ViewBilling”

■設定手順

–ユーザ作成編–

1)ルートアカウントでログインする。

2)IAMに移動し、ユーザを作成する。ユーザ名を入力し、「ユーザごとにアクセスキーを生成」にチェックを入れる。
iam-05

3)「認証情報をダウンロード」を選択し、アクセスキー、シークレットキーを保管する。

4)作成したユーザを選択し、認証情報タブを開き、「パスワードの管理」を選択する。
iam-06

5)初回ログイン時に必要になるユーザのパスワードを生成する。「自動作成パスワードの割り当て」を選択する。また、「次回のサインインで新しいパスワードを作成するようにユーザに求める」にチェックを入れる。
iam-07

6)「認証情報ダウンロード」から発行したパスワードを保管する。

–ポリシー作成編–

1)ルートアカウントでログインする。

2)アカウント>請求情報に対するIAMユーザアクセスの編集を選択する。
iam-01

3)請求情報に対するIAMユーザアクセスの「IAMアクセスのアクティブ化」にチェックする。
iam-02

4)IAMに移動し、ViewBillingポリシーの作成を行う。

5)ポリシーの作成>Policy Generatorを選択する。

6)アクセス許可の編集にて効果「許可」、AWSサービス「AWS Billing」、アクション「ViewAccount」「ViewBilling」にチェックを入れ, ステートメントを追加を選択する。
iam-03

7)ポリシードキュメントが作成されるので、「ポリシーの検証」を実行後、作成する。今回のポリシー名は「ViewBilling」とする。
iam-04

–グループ作成編–
1)ルートアカウントでログインする。

2)IAMに移動し、グループ>新しいグループの作成を選択する。

3)Admin には”Administrator Access”と”ChangePassword”を付与する。

4)Developersには”Power User Access”と”ChangePassword”を付与する。

5)Operatorsには”Read Only Access” と”ViewBilling”と”ChangePassword”を付与する。

–Cloud Trail編–
1)ルートアカウントでログインする。

2)Cloud Trailを選択する。
iam-07

3)証跡名、S3バケットを指定することでログ取得が可能になる。
iam-06

■まとめ

AWSのルートアカウントでのログインを行っている人もまだまだ、多いと思います。
例えば、退職者が発生する度にAWSのルートアカウントのパスワードを変更するといった運用を続けるのはどうなのでしょうか。 AWSといった責任共有モデルでは、個々のセキュリティレベルの意識によってセキュリティレベルも大きく変わってきます。 今回のIAMの導入は、ユーザ、グループ、ポリシーといったコアな機能のみで構成されており、既存のポリシーを生かしつつ、簡単に導入できます。

アドサーバの実装にGo言語を用いるメリット

Pocket

こんにちは。配信・インフラチームの川住です。

先日の記事にもありますが、最近、弊社DSP『Bypass』のRTB入札サーバはGo言語で実装されたものに完全にリプレイスされました。以前の入札サーバはLuaとC言語で実装されていましたが、規模の拡大に伴ってより大量のリクエストを高速に捌く必要が出てきたため、弊社SSP『AdStir』での開発・運用実績があり、Luaより処理が高速で、かつ比較的容易にHTTPサーバを実装できるGo言語へのリプレイスに至りました。

今回は、アドサーバの実装にGo言語を用いるメリットをいくつか紹介します。

リクエスト処理時のリソース消費がNginx+Luaの構成に比べて少ない

従来のLua実装では、ngx_luaモジュールを使用しており、Nginxの各プロセス内でLuaのプログラムを実行する形を取っていました。したがって、比較的大きなサイズのプロセスがforkされてしまうため、メモリ消費量が非常に多くなっていました。それに対して、Go言語を用いたサーバでは、プロセスをforkすることなくリクエストを並行して処理できるため、メモリ消費量がLua実装に比べて非常に少なく済んでいます。

強力なキャッシュモジュールの存在

RTBの入札サーバでは、大量のリクエストを高速に捌く必要があります。そのため、memcachedなどのKVSとの通信にかかるコスト (通信回数, データサイズ etc.) も考慮しなければなりません。Go言語には『go-cache』という強力なインメモリキャッシュのライブラリがあり、こちらを使用することで、KVSから取得したデータをプロセス内に保持でき、KVSとの通信コストを減らせます。Go言語のサーバ自体は1プロセスで動作しているため、キャッシュデータの共有も比較的容易に行えます。

以下にgo-cacheを用いたデータの取得と格納のコードを掲載します。

上記のように、Get関数とSet関数を用いて簡単にデータの取得や格納を行えます。データの格納時にはTTLも設定できます。しかし、go-cacheではデータの取得や格納を行う際にMutexを用いた排他制御を行っています。そのため、RTBの入札サーバのようにデータの読み書きが頻繁な環境で使用すると、go-cacheへのアクセス自体がボトルネックとなるため性能が低下してしまいます。このような環境では、go-cacheのインスタンスを複数生成しておき、キーによってシャーディングするなどして同一資源へのアクセスを分散させる必要があります。以下にシャーディング処理の一例を掲載します。

まとめ

Go言語を用いることで、高速に動作するアドサーバを比較的容易に実装できます。ただし、Go言語のサーバでは1プロセスで処理を行うため、共有資源の排他制御がボトルネックとなる可能性があり、その点を考慮しつつ実装する必要があります。

アドサーバ開発でのLuaの利用 連載(3)

Pocket

こんにちは。ユナイテッドの技術開発部で部長をしている伊良子です。

ユナイテッドでのアドサーバ開発では、Go, Lua, C言語(nginxモジュール)が主に使われる言語ですが、ここ1年ほどで急速にGoを利用するケースが増えています。新規でNginxのCモジュールを開発することはここ数年では皆無ですし、Luaで書かれているアドサーバをGoにリプレースするケースも出てきている状況です。最近ですと、数年前に私がLua+C言語で書いた弊社DSP『Bypass』のRTB入札サーバも、今ではGoに完全にリプレースされており、Luaが大好きだった私としては少々寂しいところです。 しかしながら、保守性の高さからLuaを選択するアドバンテージは高いですし、また速度面でも(弊社内でのベンチマークではありますが)シンプルな処理であるほどLuaが勝るケースが多いため、シンプルなAPIサーバなど、利用の用途はまだまだあると考えています。

今回は、Luaを利用する上で、便利なモジュールを幾つかご紹介したいと思います。

lsyslog

https://github.com/remakeelectric/lsyslog

Luaからsyslogを使うラッパーモジュールです。C言語でコンパイルされたモジュールなので高速に動作しますし、rsyslogの設定と連携することでカスタムされたログを安定的に出力することが可能です。設定も簡単です。

一例として、Luaのサンプルコードと、それに対応したrsyslogの設定を下記します。

rsyslog.confでは、テンプレート設定とルールを設定します。 この例の場合、時間ごとにファイルを切り替え、出力時間を先頭に付加した上で、プログラムから受け取った文字列を展開しています。

lua-cjson

http://www.kyne.com.au/~mark/software/lua-cjson.php

LuaからJSONを扱うモジュールです。C言語でコンパイルされているため非常に高速です。 アドサーバは直接データベースを参照するケースは稀で、memcached等のオンメモリデータを参照するケースが多いのですが、その際データをアドサーバに対して受け渡すのに手軽な方法がJSONです。弊社では他にもバイナリにするケースや、messagepackを用いるケースもありますが、手軽さと視認性の高さからJSONを使うことが最も多いです。JSONを採用する上で問題になるのがデコードにかかるコストなのですが、様々なモジュールや方法を試した結果、最も良好な速度だったのが、このlua-cjsonです。

サンプルコードは下記のような形になります。

まとめ

Luaは自分でC言語のモジュールを作ることが出来るので、Cのライブラリがあれば自分で高速なモジュールを作ることも可能です。弊社では、

  • hiredisライブラリを利用したRedis操作モジュール
  • libmemcachedを利用したmemcached操作モジュール
  • opensslライブラリを利用した、暗号/複合化モジュール

などで利用しています。 C言語と連携しないと複雑な処理を高速に実行することが難しいのがLua言語の特徴ですが、今回ご紹介したような鉄板モジュールが開発チーム内で定まっていれば、C言語を扱えないプログラマでも手軽にLuaでアドサーバを書くことが可能になります。

Hadoop / Spark Conference 2016 参加報告

Pocket

こんにちは。データサイエンスチームの西岡です。

先週月曜におこなわれたHadoop/Spark Conference Japan 2016に参加しましたので、 参加報告をさせていただきます。

聴講した発表は以下です。

  • 午前- 基調講演
  • 13:00- 次世代アーキテクチャから見たHadoop/Sparkの位置づけ (D会場)
  • 13:45- KuduによるHadoopのトランザクションアクセスと分析パフォーマンスのトレードオフ解消 (B会場)
  • 14:30- さくらインターネットが構築した、Apache Sparkによる原価計算システムの仕組みとその背景 (C会場)
  • 15:15- SparkによるGISデータを題材とした時系列データ処理 (D会場)
  • 16:00- Hive On Sparkを活用した高速データ分析 (D会場)

内容を簡単にまとめたスライドも公開しています。

全体を通して受けた印象を一言で言うと、「ますます進化・浸透していくHadoop」です。

開発開始当時(2004年頃)に一般的な構成だったCPU・メモリ・HDDを前提とした作りとなっていましたが、 今やストレージとして普及したSSDや、深層学習などの計算インテンシブなタスクに用いられるGPU・FPGAなど、 新しいハードウェアに対応できるようHadoopの開発を進めていくということでした。

しかしその一方、少数ノードで事足りる環境にはHadoopは不向きであるという指摘もあり、 Hadoopではカバーできない環境を補完するようなプロダクトの登場を予感させる発表もありました。

これからの分散データ処理がどうなっていくのか、1エンジニアとして注目していきたいと思います。

Redisにおけるデータの大量挿入手法

Pocket

こんにちは。Bypass開発チームの川住と申します。
今回はRedisにおけるデータの大量挿入手法について紹介します。

Redisとは?

まず、Redisについて少し紹介します。
Redisとは、メモリ上に Key-Value Store(KVS)を構築できるソフトウェアです(http://redis.io/)。
同系統のソフトウェアにはmemcached (http://memcached.org/) などがあります。
memcachedと比較すると、

  • シングルスレッドでクエリを処理する
  • データを永続化できる
  • ハッシュやリストなど、様々なデータ型を利用できる

といった特徴があります。

これらのソフトウェアを用いると、
メモリ上にデータを保持しており、かつ処理がシンプルであるため、
高速にデータの取得(格納)可能なKVSを構築できます。

データの大量挿入

本題に移ります。
Redisのデータの格納はとても高速ですが、シングルスレッドでクエリを処理するため、
データ数に比例して処理に時間がかかってしまいます。

そこで、大量のデータを挿入する時には、
「1件あたりのデータの挿入にかかる時間」をできるだけ短くする必要があります。

弊社では、Perlスクリプトでデータを加工し、Redisに大量のデータを格納しています。
この時、PerlのRedisモジュールを用いて1件ずつデータを格納してしまうと、

  • Redisとのデータの送受信で発生するRTT (Round-Trip Time)
  • Redisからのレスポンスをパースし、Perlのデータ型に加工する時間

が問題になります。そこで、

  • 複数のコマンドの一括送信
  • Redisからのレスポンスの破棄
    • socketやnc (netcat) でコマンドを送信し、レスポンスを/dev/null等に捨てる

を行い、処理時間を削減します。

処理時間の計測

下記の手法で10万件のデータの格納にかかった時間をそれぞれ計測しました。

  • PerlのRedisモジュールを使う(手法1)

  • socketを用いてデータを流し込む(手法2)

  • ncを用いてデータを流し込む(手法3)

計測結果

同端末で各手法5回ずつ試行し、その平均を処理時間としました。

手法 処理時間 (sec) ロス率
手法1 (module) 59.710145 0%
手法2 (socket) 2.978576 0%
手法3 (netcat) 2.626164 18%

複数のデータの格納コマンドを一括で送信し、
レスポンスのパースを行わないことによって処理時間を大幅に削減できます。
socketを用いる場合は送信(受信)バッファの管理を行わないと、
バッファ溢れによってデータロスが発生してしまいます。

まとめ

今回はRedisにおけるデータの大量insert手法を紹介しました。
目的に合わせて最適な手法を選択することでより効率的にデータを処理できるようになります。
(参考URL: http://redis.io/topics/mass-insert

基本統計量について社内勉強会で発表しました

Pocket

こんにちは。データサイエンティストチームの西岡です。

先日、統計入門ということで基本統計量について社内勉強会で発表しました。

「平均」は恐らく誰もがご存知の統計指標だと思いますが、 平均だけに頼っていてはデータの特徴が見えなくなることもあるよ、ということに伝えるために基本統計量について紹介しました。

基本統計量には大きく分けて

  • 代表値 (representative value)
  • 散布度 (dispersion)

の2種類があります。

代表値は平均値・中央値・最頻値など、文字通りデータを1つの値で示す指標であり、 散布度は分散・標準偏差など、データのバラつき具合を表す指標です。

代表値として平均値ではなく中央値や最頻値を使ったほうが適切な場合もありますし、散布度と合わせるとベターです。

平均値に騙されず、より正確にデータの特徴を掴んでいきましょう。

Pythonでテスト 連載(2) ユニットテストの書き方

Pocket

こんにちは。技術開発部データサイエンティストチームの西岡です。

前回の連載ではなぜユニットテストを書くのかについて述べました。
今回は実際にPythonのunittestモジュールを使用しながら、テストの書き方を説明しようと思います。

Pythonのユニットテストの話ではありますが、ユニットテストの思想自体は他言語でも通用するものでもありますし、Pythonの使用者以外の方も自身のよく使われる言語のユニットテストフレームワークと照らし合わせながら読んでいただければと思います。

なお、ここではPython3を使用しています。

テストケース

calc.pyはテスト対象のスクリプト、test_calc.pyはテストスクリプトです。

calc.py

tests/test_calc.py

ディレクトリ構造は以下のようであるとします。

test_calc.pyでは、calc.pyのadd_three関数にある入力を渡し、その出力が期待通りであるかをテストしています。 これをテストケースと呼びます。

unittestには

  • unittest.TestCaseクラスを継承したクラスにテストメソッドを記述
  • テストメソッドはtest_*で始める

というルールがありますが、これさえ守ればコマンドラインからテストを実行することができます。

この例ではもちろん5+3≠7なので、

上のようにテストが失敗します。 テストが通ったときは下のような表示になります。

テストフィクスチャ

ここで、Rectangleという長方形を表すクラスがあるとします。 Rectangleクラスには、

  • 長方形の幅・高さを取得するgetメソッド
  • 長方形の幅・高さを設定するsetメソッド
  • 長方形の面積を取得するareaメソッド

があり、インスタンスの使用が終わるとfinalizeメソッドで幅と高さをNoneにしなければならないものとします。

階層構造

rect.py

tests/test_rect.py

という感じになるかと思います。

しかし、これではrec = rect.Rectangle(5, 3)rec.finalize()を全テストメソッドで書かなければならず、非常に手間です。

unittestでは、このようなテストに伴って発生する初期化処理・終了処理をまとめるためにsetUp・tearDownメソッドを使用します。

tests/test_rect.py

これにより初期化・終了処理をまとめることができ、すっきりしたテストコードになります。

ただし、setUpとtearDownによってインスタンスの生成が1回にまとまったわけではないことに注意が必要です(以下の実行結果を参照)。

これはテストが以前におこなわれた何らかの実行に依存しないようにするためです。

テストの書き方

テスト対象の関数が複数のことをおこなう場合

テストの書き方は、テスト対象のコードにより大きく書き方が変わります。

以下の関数をどうテストすればよいかを考えてみましょう。

このcalc_average_bmi関数では、全員のBMIの平均を返します(BMIが何かについては、こちらのサイトを参照)。

この関数をテストする際に問題が起こりうる箇所は2つあります。

  • 返される平均の値が正しくないとき、BMI計算が間違っているのか平均計算が間違っているのか特定できない

  • 引数に空の配列が渡された時、平均計算においてZeroDivisionErrorになる

  • person.height == 0のときにBMI計算においてZeroDivisionErrorになる

下2つの「引数に空の配列が渡される」「身長が0である」といった例外はcatchすれば済みますが、そもそもcalc_average_bmi関数は

  • 平均計算
  • BMI計算

の2つのことをやっているため、関数自身はどんどん読みづらく、肥大化していきます。

そこで、BMI計算と平均計算を分離しましょう。

bmi.py

test_bmi.py

と分離すれば、bmi計算と平均計算は各々のメソッドで完結し、calc_average_bmiはほぼユニットテストを書く必要もないほど単純なコードになりました。

ここでは省きましたが、身長の単位がセンチメートルかメートルかもこのメソッドの中でテストすることができます。

メソッドに1つのことだけをさせるように変更すれば、テストもその1つのことだけを確認すればよくなるため、テストが非常に書きやすくなり、またテスト漏れも防ぐことができます。

プログラムをテスタブルにするため、テストが書きやすいようにプロダクションコードの設計から見直すことが重要です。

外部サービスのテスト

ここでいう外部サービスとは、データベースやWeb APIなどです。

git commit時のテストで毎回データベースを丸々スキャンするようなクエリを流すわけにもいきませんし、外部APIは(あまり無いかとは思いますが)落ちている可能性もあるので、外部サービスとの連携テストにはモックオブジェクトを使用するのが一般的です。

今回はモックを使用したテストについては述べませんが、いずれ取り上げてみたいと思います。

まとめ

テスタブルなコードを書くことでバグを減らし、より高速な開発をおこなっていきましょう。

Pythonでテスト 連載(1) なぜユニットテストを書くのか?

Pocket

こんにちは。技術開発部データサイエンティストチームの西岡と申します。

データサイエンスチームでは、CTR・CVR予測や、ユーザターゲティングの精度向上に日々取り組んでいます。

前回の伊良子の連載でも述べてある通り広告配信をおこなうアドサーバにはLuaやGoなどが使用されていますが、
データサイエンスチームでは分析がメインとなるためPythonを使用し始めました。 Pythonにはscikit-learnpandasなどの分析用ライブラリが充実しており、モデリング等を迅速におこなえるためです。

もちろん、分析のみならず製品への機能追加もPythonでおこなっています。 そこで、この連載ではPythonのユニットテストの書き方・テストフレームワークを紹介します。
まず第1回となる今回は、そもそもなぜユニットテストを書くのかについて説明します。

そもそも「テスト」とは?

「テスト」が何なのかということについて、wikipediaには

ソフトウェアテスト(software test)は、コンピュータのプログラムを実行し、 正しく動作するか、目標とした品質に到達しているか、 意図しない動作をしないかどうかを確認する作業のことである。

とあります。

要はデバッグのための工程です。 そもそも、テストという大仰な名前をつけずとも

プログラムを書く -> 動作確認する -> 正しく動かなければ修正 -> 再度動作確認する -> …

という流れはエンジニアであれば意識せずとも普段からやっていることです。

「ユニットテスト」とは?

ユニットテストが何なのかについてはこちらのサイトが参考になります。

単体テスト(ユニットテストと呼ばれることもあります)は、プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテストです。通常、関数やメソッドが単体テストの単位(ユニット)となります。

例をあげると、

という関数があったとして、

というように、ユニットテストでは外部のプログラムからテスト対象の関数やメソッドを呼び出し、期待した結果が得られるかを検証します。

なぜユニットテストを書くのか?

ユニットテストを書く利点について、いろいろあるかと思いますがここでは以下の3点をあげておきます。

  • プログラムの理解が容易になる

    ユニットテストが存在することで、テスト対象のコードの振る舞いが理解しやすくなります。

    get_nested_arrayは名前からは配列を返すのか?と予想してしまいますが、このテストがあることで辞書型を返すということがわかります。

    多人数が関わる開発では、自分が書いたプログラムを他の人が触る、あるいは他の人が書いたプログラムを自分が触ることは日常茶飯事のため、ユニットテストはプログラムの理解を助けることにつながります。

  • プログラムのリファクタも兼ねる

    複雑な関数やメソッドではテストすべき項目が多く、テストもどんどん複雑になっていきます。

    関数・メソッドに多くのことをさせすぎず役割を1つに絞っておくことでテストも簡潔で分かりやすいものになります。 はじめからユニットテストを書くことを念頭においておけば、設計を意識しながらコードを書くことにつながり、プログラムもすっきりしたものになります(参考: テスト駆動開発)。

  • プログラムの変更が容易になる

    プログラムに変更を加えると予期せぬ箇所にも影響が出て動作がおかしくなる、ということはままあります。 プログラムにユニットテストが付属していれば他の部分に影響が出てもテストが失敗するため、バグの早期発見につながります。

ユニットテストは「銀の弾丸」ではない

前項ではユニットテストはいいことずくめであるかのような書き方をしましたが、ユニットテストにも「限界」があります。

  • 工数がかかる

    製品のプログラムとは別にテスト用のコードを書かなければならないため、当然ながら工数がかかります。 プロジェクトの規模や人数を考えてどの程度まで網羅するべきかをその都度決めておく必要があるでしょう。

  • 全てをユニットテストでテストできるわけではない

    ユニットテストでのテストが向いているのは、関数やメソッドなどのプログラムを構成する最小単位です。

    例えばWebアプリケーションを開発していると、画面に期待した通りHTML要素が配置されるかもテストしなければなりません。 しかしユニットテストでは画面のテストをおこなうのは難しく、しかも画面はデザインの変更などにより頻繁に変わる部分であるため、テストコードを書くには向いていません。

ユニットテストを書くのは「手段」に過ぎない

前述した通りユニットテストは「銀の弾丸」ではありませんが、それでもメンテナンスコストの低下、開発速度の向上など、ユニットテストを書くことで多大な恩恵が受けられると思います。

しかし、ユニットテストを書く際には気をつけておくべきことがあります。
あくまでユニットテストを書くのは「手段」にすぎず「目的」ではないということです。

最大の目的はバグをなくすことです。

そのために必要なのは適切なテストケースを考えることです。たとえどれだけ変更がそのプログラムに加えられても、このプログラムはこう動いてほしいと明示化することです。

終わりに

本連載の第一回では、なぜユニットテストを書くべきかを中心に書きました。

次回の連載では、実際にPythonのユニットテストフレームワークを使ってどうテストを記述するのかを解説します。

アドサーバ開発でのLuaの利用 連載(2)

Pocket

こんにちは。技術開発部で部長をしている伊良子と申します。

前回は、Luaを採用した経緯/理由について書くことで、Lua及びngx_luaの紹介をさせて頂きました。連載第二回の今回はngx_luaをより高速に動作させるためのTIPSをご紹介したいと思います。

LuaJITを利用する

Luaの最新バージョンは5.3ですが、ngx_luaがサポートしているのは5.1までです。ngx_luaは、このLua5.1を使用するよりもJITコンパイラ版であるLuaJITの使用を推奨しています。LuaJITはLua5.1系に対応した本家Luaとは別のプロジェクトで、本家Luaと比べて高速に動作します。
弊社でもngx_luaを利用し始めた当初は本家Luaを使用していたのですが、LuaJITに切り替えた際には1.3倍以上のパフォーマンス向上となりました。実際のアプリケーションでの速度比較なため、速度低下の要因は様々あるにも関わらずLuaJITに変えただけで1.3倍高速になったのには驚きました。

ngx_luaでの利用ではありませんが、本家LuaとLuaJITでベンチマーク比較してみましょう。

フィボナッチ数列の40番目の数を求めるプログラムで比較してみます。このような処理だと上記したような実際のWebアプリケーションと違い、処理系のみの比較となります。

LuaJITが10倍の速度差で圧勝しました。

ngx.shared.DICTの利用

ngx.shared.DICTは、子プロセス間で共通に使える記憶領域です。

のような形で使えます。memcachedと似たような利用方法になりますが、特筆すべきはその速度です。memcachedとngx.shared.DICTを、ApacheBencheを使った比較です。

memcachedでSET/GETを30回づつ行った場合:400〜500req/sec
同様のSET/GETをngx.shared.DICTで行った場合: 1200〜1400/sec
何もしないでレスポンスだけ返す場合: 1300〜1400req/sec

ngx.shared.DICTを使った場合は、何もしない場合と変わらない速度で動作しました。直接メモリーを読み書きするため非常に高速です。弊社では、memcachedのキャッシュとして利用しています(キャッシュのキャッシュ)

ngx.varやngx.req.XXXを何度も呼ばない

ngx.varやngx.reqは、参照するたびに、ngx.var.全XXX、ngx.req.全XXXを展開する仕様であるらしく、参照のコストが高いです。

のように一度だけ参照して、以後はその変数(テーブル)を使いまわすようにします。

os.timeではなくngx.timeを使う

luaの標準ライブラリであるos.timeを使うと、使う度にシステムコールが発生してしまいますが、ngx.timeを使えばnginxがキャッシュした時間を返すため高速です。

今回はこの辺で。
ネタもなくなってきたので次回でLuaは最終回とさせて頂きます。