shimada-kの日記

ソフトウェア・エンジニアのブログです

pytestのfixtureについて

この記事は「LIFULL Advent Calendar 2018」5日目の記事として書かれました。

f:id:shimada-k:20181205201906p:plain

qiita.com

今年もアドベントカレンダーの季節になりました。年々ブログを書くという行為から遠ざかっている気がします。勤務先の会社がLIFULLグループではなくなる予定なので「LIFULL Advent Calendar」の参加も今年が最後になります。来年は組織のアドベントカレンダーではなく特定の技術のものに参加することになるでしょう。

仕事でpytestを使ってテストコードを書いているのでpytestの便利な機能について紹介したいと思います。

はじめに

Python標準のunittestや他のテストフレームワークとの大きな違いはfixtureが強力というところだとと思います。unittestではsetUpClassやtearDownClassを使用することになります。

pytestではもともとfuncargという仕組みがあり、テストコード共通で使う変数を定義することができました。Pytest-2.3からより汎用的な仕組みとして fixtureが導入されました。

fixtureは高機能でテストケースで使用する変数を定義すること以外にもマニュアルに目を通すと他にも柔軟な使い方ができそうなのでいくつか取り上げたいと思います。

pytest-fixture

https://docs.pytest.org/en/latest/fixture.htmldocs.pytest.org

pytestのfixtureは依存性の注入です。セッション、モジュール、クラス、ファンクションとスコープを設定できます。 本家のドキュメントを見ているといくつか有用そうな使用について紹介します。ソースは全て引用です。

ファクトリパターン

@pytest.fixture
def make_customer_record():

    def _make_customer_record(name):
        return {
            "name": name,
            "orders": []
        }

    return _make_customer_record


def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")

fixtureを複数定義するほどではないが、共通部分が多いという場合に使用できそうです。

複数パラメータ

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module",
                params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
    yield smtp_connection
    print("finalizing %s" % smtp_connection)
    smtp_connection.close()

parametrizeライブラリと似た様な感じですが、parametrizeはfixtureとは別の仕組みとして提供されるので、conftest.pyに書きたいならfixtureのparamsを、テストコード側で定義したいならparametrizeといった使い分けかなと思います。

fixture内でのオブジェクト返却

# content of test_appsetup.py

import pytest

class App(object):
    def __init__(self, smtp_connection):
        self.smtp_connection = smtp_connection

@pytest.fixture(scope="module")
def app(smtp_connection):
    return App(smtp_connection)

def test_smtp_connection_exists(app):
    assert app.smtp_connection

フィックスチャとして利用したいものが単純な変数やデータではなくオブジェクトになる場合はこの様な使用になると思います。ファクトリパターンのもっと構造化されたものという感じでしょうか。

テストケース内のパラメータ読み取り

# content of test_anothersmtp.py

smtpserver = "mail.python.org"  # will be read by smtp fixture

def test_showhelo(smtp_connection):
    assert 0, smtp_connection.helo()
# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection(request):
    server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    smtp_connection = smtplib.SMTP(server, 587, timeout=5)
    yield smtp_connection
    print("finalizing %s (%s)" % (smtp_connection, server))
    smtp_connection.close()

テストケース内でフィックスチャに渡すデータを定義するケースはぱっと思いつきませんが、fixture内でオブジェクトを返却するケースで、クラスを統一したいというケースかなと思います。「fixture内でのオブジェクト返却」と「ファクトリパターン」の組み合わせになるかと思います。

まとめ

Pytestはfixtureが充実しているのでsetup/teardownをモジュールごとに書く必要がなくなっています。そのことでテストコードの可読性が高いです。

またディレクトリごとにconftest.pyを配置することでシンプルながらも、オーバーライドも可能となっており柔軟性も高いです。

fixture以外にも実は知っていればもっと効率的にテストコードを書けるということがあると思うので、一度マニュアルに目を通すと発見があると思います。

DIYのすゝめ

11月頃からクリスマス用に子供のおもちゃを作ろうと決意し準備を進めておりました。ものとしてはAdafruit Touch Hatを使用したおもちゃを製作しました(この関連で別postで組み込み系の記事を公開しました)。

Adafruit Touch Hatは12個の静電容量式センサがついているので銅箔ステッカーを板にはりつけて触ると音がでるおもちゃです。

当初Adafruit Touch Hatということでガチのドラムマシーンを作ろうかと思っていましたが諸事情により途中で設計からやりなおすことにしました。一通りやってみてハードルををいくつか感じました。

まずハードウェアは作り直しが効きません。のこぎりで切ったらもとに戻せませんし、釘がずれて板が割れても元に戻せません。ソフトウェアのように動かしてダメだったらデバッグして製品としての精度を高めるということが通用しない、ということを痛感しました。

また、不確定要素が多かったなと思います。部材の購入はネット通販でまかなっていたのですが、これがなんともわかりにくい。製品の写真と10cm x 15cm x 10cmという非常にざっくりとした表記しかありません。「どこからどこ」がどの長さなのかちゃんと明記しているサイトはほとんどありません。なので届くまで実際の取り付け感覚は得られないです。ネット通販の場合。

あとは根本的なところなんですが、本来の用途と違う使い方は鬼門なんですね。まあ当然と言えば当然なんですが。高品質なものほど特定の用途に特化しているので別の用途で転用しようとすると途端に融通が効きません。そういう意味で見た目で良さそうと思っても実際部材として使ってみると全然ダメだったりします。

今回身にしみて以下の3点心に刻みました。

  • 部材などの買い出しは通勤沿線で行うこと
  • 買い出しをする店はできる限り安価な価格帯の店にすること
  • 設計はとにかくシンプルに、複雑さは一切排除すること

部材などの買い出しは通勤沿線で行うこと

web通販は先述のようなデメリットがありますので実際手にとって納得できたものを買った方がよいです。実際組み上げる過程で本当にいろんな問題に遭遇します。言い方を変えると組み上げるまで何が起こるかわからないです。なので必然的に何度も店に足を運ぶことになります。その時に沿線から遠い店に行くとなるとその移動時間分仕事やプライベートで都合をつけないといけなかったり、交通費が別途かかったりします。その労力たるやバカになりません。

買い出しをする店はできる限り安価な価格帯の店にすること

購入した部材が残念ながら無駄になってしまった時に5000円で買った部材と200円で購入した部材だとどうなるでしょうか。前者はきっと諦めきれないでしょう。またもう一度店に行くのも非常に億劫です。しかし安ければ安いほど残念な結果が出ても飲み込みやすいです。ベストは100円ショップです。100円ショップで買える範囲内で設計することをおすすめします。

設計はとにかくシンプルに、複雑さは一切排除すること

これはソフトウェアも同じですが複雑な設計はあらゆる問題の温床になります。ことDIY初心者は極端なほどシンプルさにはこだわった方がいいと思います。余計な加工や実装上のギミックなどは(考えるのは楽しいですが)極力我慢しましょう。シンプルな設計にするとそこから応用を広げていくことも可能です。ハードはユーザの使い方次第で工夫の余地がありまる。そこはソフトウェアと違い画一的ではありません。言うは易しするは難し。迷ったらシンプルに。迷ったらシンプルに、です。

おわりに

当初考えていたものとは全然別のかたちになりましたが今回色々勉強になりました。プロダクトとしてビジョンがあって、子供向けのおもちゃとして成立すること、設計から全体を作るのは初めてだったこと。変数がいくつもあるなかで何らかの形にする必要がありました。

結果的にうちの子は楽しそうに遊んでくれたのでよかったです。作り手冥利に尽きますね。

Raspberry Piでドラムボードを試す

この記事は「LIFULL Advent Calendar 2017」12日目の記事として書かれました。

qiita.com

年の瀬ですね。年末です。もう今年もアドベントカレンダーの季節です。

ついこの間元旦だったような気さえしますよ。本当に時間の経過は早いものです。

はじめに

仕事でPythonを使うことになったので何かいいおもちゃはないかと思ってこれを期にRaspberry Piを買っていじってみることにしました。

Raspberry Piは言わずと知れた組み込み系のハードウェアですね。OSをMicro SDに焼いてそこから起動しますね。はい、ご存知の通りです。

Raspberry Piはそれ単体だとただの小さいコンピュータなので、何かデバイスと組み合わせてごにょごにょして遊ぶんですが。音が出るボードを2品ほど試してみたのでそのレポートになります。

使用したのはDrum HATとAdafruit Touch Hatの2つの静電容量式のセンサーボードです。

Drum HAT

www.switch-science.com

白い枠の内側部分がセンサーになっており白色LEDもコードで制御することができます。センサーの数は8つです。 「Drum HAT」だけあってドラムの構成楽器のモチーフが基盤にプリントしてあります。「海賊ロボ忍者さる」はよくわかりません。

f:id:shimada-k:20171212004643p:plain

ライブラリがわかりやすくマニュアルが充実しているので普通のエンジニアなら難なく動かせると思います。ただ残念なのが、むき出しで使うことが前提になっているようでセンサー部分の拡張性が全く考慮されておりません。

Adafruit Touch Hat

www.adafruit.com

こちらはタッチ部分が2穴になっていて、公式サイトのようにワニ型クリップで挟むことができるようになっています。センサーは12個です。

f:id:shimada-k:20171212004700p:plain

Drum HATの欠点だった拡張性が確保されていて基本的には触るモノ(もちろん導電性があるもの)をボードに接続することが前提になっています。

ただこちら、実は届いてそのままは使えませんでした。GPIOソケットと本体が別れた状態になっていて、はんだ付けが必要になります。

f:id:shimada-k:20171212005312p:plain

半田付けなんて中学か高校の技術の授業でやったっきりでした。ほんとうに久しぶりでした。そのためにはんだごてを購入しました。はんだごてって意外に安いものなんだということを知りました(800円程度)。

ライブラリについて

Drum HATの方はハードウェア部分は隠蔽化されていて使いやすいライブラリになっている感触です。プログラムを書く側はセンサー部分が反応した時のコールバックインターフェースの実装に集中できます。

Drum HATのサンプルプログラムを改良しクラス化して他のモジュールを組み込みやすいようにしてみました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import glob
import os
import time
import drumhat
import pygame

import signal

from pprint import pprint

import sys

class TestDrum:
    #DUM_FOLDER = ""
    #files = None
    #samples = None

    def __init__(self, source):
        DRUM_FOLDER = source
    
        BANK = os.path.join(os.path.dirname(__file__), DRUM_FOLDER)
        pygame.mixer.init(44100, -16, 1, 512)
        pygame.mixer.set_num_channels(16)

        self.files = glob.glob(os.path.join(BANK, "*.wav"))
        self.files.sort()
        self.samples = [pygame.mixer.Sound(f) for f in self.files]

    def start_record(self):
        for i in [0, 1, 2, 3, 4, 5, 6, 7]:
            drumhat.led_on(i + 1)
            pygame.time.wait(100);
            drumhat.led_off(i + 1)

td = TestDrum("drums")
td.start_record()

def handle_hit(event):
    td.samples[event.channel].play(loops = 0)
    print("You hit pad {}, playing: {}".format(event.pad, td.files[event.channel]))
    td.hit_time = time.time()

def handle_release():
    pass

drumhat.on_hit(drumhat.PADS, handle_hit)
drumhat.on_release(drumhat.PADS, handle_release)

# シグナルハンドラ
def handler(signal, frame):
    # ここで終了処理を行う
    sys.exit(0)

signal.signal(signal.SIGINT, handler)

while(True):
    time.sleep(1)

一方Adafruitの方はDrum HATよりも一つ下のハードウェア層をいじらないといけないようなイメージです。サンプルを見ると自前で論理演算をやってどのセンサーが反応したかをメインループの中で判定していたりします。

github.com

まとめ

拡張性があるのは断然Adafruitの方です。基盤を直接触れさせたくないですしセンサー部分を基盤から切り離せるのは大きいです。Drum HATの方はアルミホイルを細長く切って繋いでみたり導電スポンジを切って接合部分に繋いでみたりしましたが残念ながら安定しませんでした。

pimoroniはこのDrum HAT以外にもPiano HATやRainbow HATやUnicorn HATなど色々なHATを販売しています。いろいろな組み込みプログラミングが安価で試せるのは素晴らしいですね。

Adafruitはボード自体はしっかりしてますが、ソフトウェアを頑張って作る必要があります。その分拡張性があるボートと12個のセンサーがありますので、仮にドラムの打楽器として8つ使ったとしても残り4つは制御用など別用途で使用できます。センサー部に導線を固定できる機構もありますのでネジとボルトで固定できます。

どちらも使い方次第で夢が広がるボードです。実際にモノを動作させるプログラムを書くのは新鮮で楽しいものです。

IBM Blockchainでソリッドステートを作る

この記事は「ネクスト(Lifull) Advent Calendar 2016」21日目の記事として書かれました。

qiita.com

前回の投稿からかなり時間が空いてしまいました。今年も年の瀬ですね。

なぜ年の「瀬」というかというと、「瀬」には「流れの速い」という意味があるそうです。またツケ払いが多かった江戸時代には年末にツケの清算がなされたことから、めまぐるしい時期ということで年末のことを「年の瀬」ということになったらしいです。

はじめに

私はFinTech系の事業に携わっておりますので、ぜひブロックチェーンについて議論したいと思いました。

昨年にはマウントゴックスの事件などありキナ臭い印象が広がっているかもしれません。しかしマウントゴックスの破綻とブロックチェーンの技術自体には何の因果関係もありません。

2016年には三菱UFJが仮想通貨に参入したり、2017年度の税制改正ではビットコインの購入に際しては消費税を課税しないなど、ブロックチェーンを使用した仮想通貨は徐々に社会的な認知度をあげていると言えます。

闇雲にブロックチェーンについてレポートを書いてみるものいまいちモチベーションが上がりませんので、今回は「攻殻機動隊 S.A.C. SSS」のソリッドステートを取り上げてみることにしてみました。

フライト記録は改竄しておけよ

最低限の説明は必要でしょう。「攻殻機動隊 S.A.C. SSS」は2011年に公開された劇場版・攻殻機動隊です。「攻殻機動隊 S.A.C. 2nd GIG」で草薙素子が9課を去ってから2年後が舞台となっており、少子高齢化社会を背景にして新たな組織となった9課の苦悩と、超ウィザード級ハッカー傀儡廻(くぐつまわし)」との電子戦が描かれています。

sss.ph9.jp

ソリッドステートとは全自動老人介護システム内の誘拐インフラのことです。虐待を受けている子供を身寄りのない老人の子供としてデータを書き換えるシステムを指しています。

f:id:shimada-k:20161220232356p:plain

少子高齢化時代の対策として、厚生労働省の職員が開発したシステムとされています。

少子化が叫ばれるなか、年間5万人もの6歳未満の子供が無意味に命を落としていることを。

児童相談所や警察など関係機関が事態を把握していたにもかかわらず防ぐことができなかったものだ。

どこかで聞いたような話です。2011年の作品ながら、現在を言い当てているようです。

ソリッドステートの本質は記憶を改竄することだと思います。なにせ子供の親を入れ替えないといけないのですから。親と子供の記憶は最低限書き換えが必要ですし、親類やその他周辺関係者の記憶も書き換える必要があるでしょう。

警告を無視したな

さて、本題に入りましょう。ブロックチェーンを使った仕組みを作るということは、チェーンコードを書くということです。今回はIBM Blockchainを使います。

IBM BlockchainとはIBM Bluemix上で動かせるアプリケーションフレームワークです。GUIで操作できAPIを叩いてデプロイできます。IBM BluemixとはIBMが提供するPaaSです。30日間の無料トライアル期間があります。

IBM Bluemix http://www.ibm.com/developerworks/jp/bluemix/

IBM Blockchainのサービスでは4つのピアを動かすことができます。

f:id:shimada-k:20161220234608p:plain

IBM Blockchainで自作のチェーンコードを動かす場合、APIメニューを使用することになります。

f:id:shimada-k:20161220234618p:plain

APIメニューではリクエストを送信して検証するためのUIを提供しています。curlを使って自前でリクエストすることもできますが、開発時はこちらを使ったほうが格段にはかどります。

委ねてみるわ ゴーストの囁きに

チェーンコード

IBM Blockchainでは Hyperledger Fabricというブロクチェーンを基盤に採用しています。本家のHyperledgerプロジェクトから派生したインキュベーションプロジェクト、という位置づけです。オープンソースで公開されています。

Hyperledger Project https://www.hyperledger.org/

HyperledgerではDeploy,Invoke,Queryの3種類のメソッドを使います。Deployはデプロイ時に使用するもの、Invokeはデータの新規追加、更新、削除。Queryは参照です。

今回はInvokeでデータを書き換え、他のピアでもデータが書き換わっていることをQueryで確認します。

IBM Blockchain上で動いている4つのピアは個々人の記憶の塊だと見立てます。

記憶のデータは以下のような構造にしました。戸籍データのようなものです。

type Family struct{
    Sex int `json:"sex"`
    Birthday string `json:"birthday"`
    SpouseId string `json:"spouse_id"`
    FatherId string `json:"father_id"`
    MotherId string `json:"mother_id"`
    ChildId string `json:"child_id"`
}

データの改竄フローは以下のように実装しました。

  1. 父の持つ子供IDを消す(hospital)
  2. 母の持つ子供IDを消す(hospital)
  3. 子供の両親IDを消す(plugged)
  4. 子供の父親IDを老人IDに変更する(adopted)

ちなみに「攻殻機動隊 S.A.C. SSS」では老人は理解した上でソリッドステートに加担していたので、作品中では明確にされていませんが老人は自分の子だとは認識していなかったと思われます。そのため老人のデータ(記憶)は変更しないことにします。

実装したチェーンコードはgistにあげておきました。

動かしてみる

下記のリスクエストでデプロイします。IBM BlockchainのAPIメニューから行います。ピアは0を指定しています。ユーザはuser_type1_0でログインしています。

 {
     "jsonrpc": "2.0",
     "method": "deploy",
     "params": {
         "type": 1,
         "chaincodeID": {
             "path": "https://github.com/shimada-k/learn-chaincode/finished"
         },
         "ctorMsg": {
             "function": "init",
             "args": [
                 "hello"
             ]
         },
         "secureContext": "user_type1_0"
     },
     "id": 0
 }

データを準備する

Invoke:addを使用してデータを追加します。あらかじめ老人、子供、子供の父母のデータを作成しておきました。

老人

老人は80歳という設定です。身寄りのない老人ということで配偶者と子供はありません。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 1,\"birthday\": \"1936-12-21\",\"spouse_id\": \"\",\"father_id\": \"8b77fb747eda782e202f85f3c7e5df850200977a053527fccbc3914d50ee98b5\",\"mother_id\": \"194f138b39f10252c32bd1cc95b9144da7688954c56eb014c3b7c4759ce33735\",\"child_id\": \"\"}"
  },
  "id": 0
}
子供の父

父親は38歳(1978年生まれ)です。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 1,\"birthday\": \"1978-02-20\",\"spouse_id\": \"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"father_id\": \"786580bfbb1477f9f00c2bb3566ad52f204067d52d2756a9b4ea408ffbe74152\",\"mother_id\": \"e81727115da92778c19055bd2839bf6c3105224062958e28d7d2317de8a7cdd6\",\"child_id\": \"65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2\"}"
  },
  "id": 0
}
子供の母

母親は36歳です。sex:1は男性、sex:2は女性としています。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 2,\"birthday\": \"1980-08-14\",\"spouse_id\": \"7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279\",\"father_id\": \"ff5b8a198f9f96f89ca2aa483c927d58ab7f42563df7c24b47992fdf3cddccea\",\"mother_id\": \"c2dff7c37bbc245dc93ce572476254bd287860e1cebd2b5f36181748fd1f5a54\",\"child_id\": \"65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2\"}"
  },
  "id": 0
}
子供

子供は6歳です。当然ながら配偶者と子供はいません。father_idとmother_idはすでに追加したIDを登録しています。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\": 1,\"birthday\": \"2010-11-03\",\"spouse_id\": \"\",\"father_id\": \"7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279\",\"mother_id\": \"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"child_id\": \"\"}"
  },
  "id": 0
}

データが揃ったのでいよいよ理不尽に損なわれてしまうゴーストの再利用を一部始終、追体験してみましょう。 Invokeでデータを書き換え、Queryで書き換わったことを確認します。

まずはデータの書き換えからです。リクエストするJSONを示します。

父の持つ子供IDを消す(hospital)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "hospital",
      "args": [
        "7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

母の持つ子供IDを消す(hospital)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "hospital",
      "args": [
        "57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

子供の両親IDを消す(plugged)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "plugged",
      "args": [
        "65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

子供の父親IDを老人IDに変更する(adopted)

{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "adopted",
      "args": [
        "65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2",
        "2d9dc1dcf4330422f5594f0325b01b9326df7554c58f34a29e51a43970d5bbbd"
      ]
    },
    "secureContext": "user_type1_0"
  },
  "id": 0
}

次にデータが書き換わったことをQueryで確認しましょう。

両親のデータを確認する

父親
{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"1978-02-20\",\"spouse_id\":\"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"father_id\":\"786580bfbb1477f9f00c2bb3566ad52f204067d52d2756a9b4ea408ffbe74152\",\"mother_id\":\"e81727115da92778c19055bd2839bf6c3105224062958e28d7d2317de8a7cdd6\",\"child_id\":\"\"}"
  },
  "id": 0
}

子供IDが消えていますね。

母親のデータ
{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":2,\"birthday\":\"1980-08-14\",\"spouse_id\":\"7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279\",\"father_id\":\"ff5b8a198f9f96f89ca2aa483c927d58ab7f42563df7c24b47992fdf3cddccea\",\"mother_id\":\"c2dff7c37bbc245dc93ce572476254bd287860e1cebd2b5f36181748fd1f5a54\",\"child_id\":\"\"}"
  },
  "id": 0
}

こちらも子供IDが無くなっています。

子供のデータを確認する

子供のデータを確認してみましょう。父親が老人のIDに書き換わっているはずです。

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"2010-11-03\",\"spouse_id\":\"\",\"father_id\":\"2d9dc1dcf4330422f5594f0325b01b9326df7554c58f34a29e51a43970d5bbbd\",\"mother_id\":\"\",\"child_id\":\"\"}"
  },
  "id": 0
}

いいですね。父親IDが老人のIDに書き換わり、母親IDは空です。

別のピアで確認してみる

ブロックチェーンですから、他のピア上でも同一のデータ処理が行われているはずです。

上述の処理はピア0で行っているのでピア1でも確認してみます。APIメニューの上部でピアを切り替え、registerでuser_type2_0でログインしてQueryしてみましょう。

父親のデータ

request

{
  "jsonrpc": "2.0",
  "method": "query",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "read",
      "args": [
        "7b451b73ce0614147b01edb102f318a7496ebb533dde28e1fd80c370770f6279"
      ]
    },
    "secureContext": "user_type2_0"
  },
  "id": 0
}

response

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"1978-02-20\",\"spouse_id\":\"57001488396447b13c743ec4b3e6f7c6f01a53022c7141a8188d6677bf599fa4\",\"father_id\":\"786580bfbb1477f9f00c2bb3566ad52f204067d52d2756a9b4ea408ffbe74152\",\"mother_id\":\"e81727115da92778c19055bd2839bf6c3105224062958e28d7d2317de8a7cdd6\",\"child_id\":\"\"}"
  },
  "id": 0
}

・子供のデータ

request

{
  "jsonrpc": "2.0",
  "method": "query",
  "params": {
    "type": 1,
    "chaincodeID": {
      "name": "a027a646d118ca8513ae6854ca455ab6d3c1bf0c6ce331dfc384a7a406605114e2be6c7c46abbb3aaec8986a93327b8dbb5b2c399b3a7ff02eea48dac7184e74"
    },
    "ctorMsg": {
      "function": "read",
      "args": [
        "65152f7fb723e1f385bab62bdc18673209e2324f356f895bf516365cc62834e2"
      ]
    },
    "secureContext": "user_type2_0"
  },
  "id": 0
}

response

{
  "jsonrpc": "2.0",
  "result": {
    "status": "OK",
    "message": "{\"sex\":1,\"birthday\":\"2010-11-03\",\"spouse_id\":\"\",\"father_id\":\"2d9dc1dcf4330422f5594f0325b01b9326df7554c58f34a29e51a43970d5bbbd\",\"mother_id\":\"\",\"child_id\":\"\"}"
  },
  "id": 0
}

ピア1でも同一のデータが得られました。これで他のピアでも同期が取れていることが分かりました。これで記憶の改竄がおこなれ、無事に誘拐が完了されました。

攻殻機動隊 S.A.C. SSS」においても、カ・ルマのアジトにいた子供達を見て、ボーマが「記憶の一部が消されていて、IDが別の物に書き換えられているらしい事も分かった。」と言っています。

おわりに

今回はIBM Blockchainを使用してブロックチェーンを利用した仕組みを実装してみました。ハマったところをいくつか書いておきます。

開発はOSX環境で行いましたが、home brewでインストールしたGoのバージョンが古くてビルドできなくて悩みました。IBMのサンプルプロジェクトではGo 1.6推奨と書かれていますがOSX Sierraでは1.6でもコンパイルできませんでした。最新版を入れた方が手戻りが少ないかもしれません。

IBMのサンプルプロジェクトではビルドの環境も作っていますが、チェーンコードをデプロイするだけであれば不要です。ただデプロイして動かなかった場合に切り分けするために、ビルド環境を作りデプロイ前にビルドすることを強くお勧めします。

またデプロイ時のレスポンスでchaincodeIDが帰ってくるのですが、これが毎回同じだとデプロイでチェーンコードが変化していないことになります。

原因は/finishedの中身を更新していなかったことです。/startの中にスケルトンコードが入っているのでその中をひたすらcommit & pushしていたのですが、それでは当然ながら/finishedの中が変化していません。英語のチュートリアルを流し読みしていると気づきませんでした。

IBM Bluemixが本来有料のサービスということもありますが、今後はもっとローカライズされた情報が充実していき、より多くの人にとって敷居の低いものになっていくといいですね。

冒頭でも書きましたが、「攻殻機動隊 S.A.C. SSS」は社会問題を先取りして捉えています。2016年には「保育園落ちた日本死ね!!!*1」というブログ記事が公開され国会で話題になりました。待機児童の問題は主に都市部が深刻ですが、負担にあえぐ現役世代の声が顕在化した事象といえます。

一方高齢化問題としては、2025年問題を控えて社会保障制度の見直しが喫緊の課題です。2017年度から医療保険介護保険の見直しが入ります。年金カット法案と称されつつも国民年金法改正案*2が採決されました。

2017年はどのような年になるでしょうか。とりあえず都内に保育園が増えてくれるとありがたいですね。少子化問題社会保障制度の見直しを含めた高齢化問題は引き続き最優先事項です。

それにしても、ネットは広大だわ。もう既に、私達の知らない次の社会が生まれ始めている・・・


本記事は犯罪としての誘拐を肯定又は助長するものではありません

GCDの実行ポリシーとキューの種類まとめ

今更ながらGCDを勉強しました。Grand Central Dispatchです。わからなければ自分で使ってみることです。そうしたらいざという時に役に立ちます。

調査

GCDには主に2種類のキューのタイプと2種類のディスパッチポリシーがあります。組み合わせで合計4種類の使い方が存在します。

  1. 非同期実行&並列キュー
  2. 非同期実行&直列キュー
  3. 同期実行&並列キュー
  4. 同期実行&直列キュー

webの資料を読んでもいまいちイメージがつかなかったので以下のようなコードで4パターン検証してみました

環境

#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>

#define CEIL_MAX    100000
#define NUM_THREAD  20
#define CONTEXT_PRINT   1 

// 1からceilまでの間で素数の個数を計算する関数
int prime(int ceil, int thread_order)
{
    int i, j, num_yakusu, prime = 0;

    for(i = 1; i <= ceil; i++){
        num_yakusu=0;

        for(j = 1; j <= i; j++){
            if(i % j == 0){
                num_yakusu++;
            }
        }
        
        // 1とそれ自身でしか割り切れなかったら素数
        if(num_yakusu == 2){
            prime++;
            if(CONTEXT_PRINT){
                printf("thread order:%d\n", thread_order);
            }
        }
    }
    return prime;
}

// entry point
int main(int argc, char *argv[]){
    int i;

    //dispatch_queue_t q = dispatch_queue_create("gcd.sample.osx", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t q = dispatch_queue_create("gcd.sample.osx", DISPATCH_QUEUE_SERIAL);
    //dispatch_group_t group = dispatch_group_create();

    srand((unsigned)time(NULL));
    printf("loop start\n");

    for(i = 0; i < NUM_THREAD; i++){
        dispatch_group_async(group, q,
        //dispatch_sync(q,
            ^{
                pthread_t p = pthread_self();
                struct timeval tp;
                int ceil = rand() % CEIL_MAX + 1;
                int num_prime = prime(ceil, i);
                int j = i;
                struct timeval tq;

                printf("i:%d, thread id:%p ceil:%d num_prime:%d\n", i, p, ceil, num_prime);    
            }
        );
        printf("thread done\n");
    }
    //dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    printf("loop end\n");
}

1からceilまでの数値で素数がいくつあるか調べる処理をGCDを使って並列処理するコードです。

GCDの実行ポリシーとキューの種類の4パターンでソースコードを切り替え、生成されるスレッドの個数とディスパッチの対象を調べました。

パターン スレッドの個数 ディスパッチの対象
1 5 インキューされているすべてのタスク
2 2 実行中のタスクの次にインキューされたタスク
3 1 実行中のタスクの次にインキューされたタスク
4 1 実行中のタスクの次にインキューされたタスク

考察

パターン1と2は非同期実行です。1の場合は非同期実行&並列キューです。タスク同士が完全に独立で並列に実行されます。スレッドはメインスレッドの他に4スレッド生成されて、タスクすべてがコンテキストスイッチされて実行されます。

2の場合は非同期実行&直列キューです。メインスレッドの他に1つスレッドが生成されてそのスレッド内でインキューされた順番に実行されていきます。パターン1と2のケースは両方とも親スレッドとは別個にスレッドが作成されています。これは非同期実行ポリシーの特徴と言えます。

パターン3と4は同期実行です。3の場合は同期実行&並列キューです。並列キューなので複数スレッドが立ち上がると見せかけて実際起動するのは1スレッドだけです。

dispatch_syncはそれぞれのブロックの実行が完了するまで終わらないのです。なので、並列キュー的には複数スレッドを立てたいけど、処理できるブロックが1個ずつだから結果として1個しかスレッドが 立たないということになります。

4の場合は同期実行&直列キューです。これは言わずもがな純度100%の直列&同期実行です。インキューされた順番に1つ1つタスクが完了するまで次のタスクは実行されません。パターン3と4のケースは子スレッドが作成されるわけではありませんので親スレッドはブロックされます。

まとめ

実行結果より、キューの種類よりも実行ポリシーのほうが支配的であると言えます。

また「同期」と「直列」という単語が2種類出てくるのでわかりにくくなっているのですが、「同期」というのはタスク間に対して使われているものではなく、メインスレッドとの関係性を表す単語です。

そして「直列」というのはタスクの順番のみを表す単語として使用されています。少なくともGCDにおいては。

親スレッドとは別なところでCPUを全て使って複数処理を同時に走らせる(いちばんイメージしやすいパターン)なら並列&非同期の組み合わせ一択。親スレッドは自由にしたいというだけの場合は直列&非同期でも可。

むしろ上記以外のケースだった場合、GCD使ってもメリット薄い気がしますね。。

sched_setaffinityを使って非同期&並列キュー環境で全て同じCPUに固定した場合どうなるのか試したかったんですが、OSXでは使えませんでした。残念。

久しぶりにC言語を使いました。楽しかったです。

UIScrollViewにおけるcontentOffsetの注意点

UINavigationControllerを使用している場合で、UIScrollViewをpagingで使用している時にコードからUILabelを置こうとした時に想定通りの座標で配置できなかったことがあったので解決手段をメモしておきます。

既知のトピックではありますが、ちゃんとまとまった情報に出会えなくてかなりハマったので書いておきます。

これは2014年iOSアドベントカレンダー5日目の記事として書かれました。

環境

コード

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;

@end

#define NUM_LABEL 5

@implementation ViewController

- (void)viewDidLayoutSubviews {
    NSLog(@"viewDidLayoutSubviews");
    [super viewDidLayoutSubviews];
    
    //スクロールの総範囲の設定
    NSInteger kScrollObjWidth = [[UIScreen mainScreen] applicationFrame].size.width;
    [self.scrollView setContentSize:CGSizeMake(NUM_LABEL * kScrollObjWidth, self.scrollView.frame.size.height)];
    // 謎に下にずれることがあるのでy座標を0にする
    NSLog(@"contetOffset (%f, %f)", self.scrollView.contentOffset.x, self.scrollView.contentOffset.y);
    self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, 0);
}

- (void)viewDidAppear:(BOOL)animated
{
    NSLog(@"viewDidAppear");
    [super viewDidAppear:animated];
    //[self showObjects];
}

- (void)viewDidLoad {
    NSLog(@"viewDidLoad");
    [super viewDidLoad];
    self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, 0);
    // Do any additional setup after loading the view, typically from a nib.
    [self showObjects];
}

- (void)showObjects{
    NSInteger kScrollObjWidth = [[UIScreen mainScreen] applicationFrame].size.width;
    NSInteger i;
    
    for(i = 0; i < NUM_LABEL; i++){
        UILabel *label = [[UILabel alloc] init];
        
        label.frame = CGRectMake(kScrollObjWidth * i, 0, kScrollObjWidth, 30);
        
        label.textAlignment = NSTextAlignmentCenter;
        label.text = [NSString stringWithFormat:@"%ld", (long)i];
        
        label.backgroundColor = [UIColor greenColor];
        
        [self.scrollView addSubview:label];
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

このコード。上記のようにshowObjectsをviewDidLoadで呼び出すと下記のようになります。その場合、呼び出す前にcontentOffsetの初期化をやっても同様です。

オレンジ色のものがUIScrollViewで、緑色のものがUILabelです。

f:id:shimada-k:20141203013948p:plain

また、viewDidLayoutSubviewsでcontentOffsetを初期化しない場合でも同じ結果になります。コードからではなくInterface Builderでオブジェクトを置いている場合でも同様です。

ちなみに上記の状態でもマウスか指でスクロールして何かしらの動きを与えると座標が0の状態(想定通りの状態)に戻ります。

発生理由と対策

発生理由はNavigationControllerVilew配下にScrollViewを置いた場合、ContentsOffset.yのデフォルト値が-64になっているからです。

f:id:shimada-k:20141203012446p:plain

-64というのはステータスバーの20ptとナビゲーションバーの44ptの合計値で、NavigationController配下にあった時UIScrollViewのコンテンツが隠れてしまわないようにという配慮です。これはUIScrollViewの仕様です*1

ちなみにナビゲーションバーが存在しない状態だとcontentsOffsetの初期値は(0.000000, -20.000000)とはならずに(0.000000, 0.000000)となります。

解決するには

  • UIScollViewが配置され(てい)るUIViewControllerのautomaticallyAdjustsScrollViewInsetsをNOにする

もしくは

  • UIScrollViewのcontentsOffsetをviewDidLayoutSubviewsで初期化した後にshowObjectsを呼ぶ

必要があります。例えばcontentsOffsetをviewDidLayoutSubviewsを初期化してshowObjectsをviewDidAppearで呼び出すようにするとscrollViewの左上からちゃんとUILabelが表示されます。

f:id:shimada-k:20141203014005p:plain

この件はUIScrollViewに対してConstrainsを設定していても発生します。またタップすると(0, 0)に戻るという点と、ナビゲーションバーが存在しない状況では発生しないという点が邪魔をして、解決に時間がかかりました。

東京メトロのアプリコンテストに応募しました

iOSアプリを作成しアプリコンテストに応募しました。作成から応募までのログを宣伝を兼ねて書いておきます。

東京メトロ オープンデータ活用コンテスト

東京メトロAPIを使用したアプリを作るという趣旨です。サマって書くと個人・団体問わず参加OK、一人何作品出してもOK。Web/iOS/Android/WindowsPhoneで作ってね、賞金出すよ。というもの。僕らはiOSアプリを作りました。選考要素にデザインも含まれていたので社内のクリエイティブの友達を誘いプライベートの時間を使って2人体制で開発しました。

作ったもの


MetroCluster紹介動画 - YouTube

foursqureのAPIを使って電車の運行状況に応じて到着駅付近のvenueの画像と地図を表示するアプリを作りました。socket.ioを使ったチャット機能もつけてアプリのユーザ同士で会話できる機能もつけました。

感想

かなり詰みました。着手したのが9月末からで、スケジュールを切ったところAppleの審査(このことは後で詳細を書く)があるので10月中にベータ版を作ることに。開発期間は実質1ヶ月という状況。10月は仕事以外でも予定が多い月だったので開発時間の捻出に苦労しました。

Apple審査

マイルストーンに従ってβ版を10月下旬に審査に出しました。まあベータ版なので速攻でリジェクトを食らいました。ベータ版を修正&機能追加した版を再度11/2に審査に提出しました。

コンテストの応募締め切りが11月17日だったので最悪Apple審査が締め切りに間に合わない可能性があると思いコンテストの運営MLに審査が間に合わない場合の運営のケアはあるかどうか問い合わせました。

結果はNG。iOSアプリの場合、応募時点でAppStoreに公開さていることが条件と言われました。まあダメ元で聞いてみたのであまり期待してませんでした。

Apple次第ということで待っていると11月11日にApple審査が通りました。Ready for Sale。これで応募に関しては気に病むことはないと安堵したのもつかの間。AppStoreから落としてきたアプリを起動するとクラッシュ。メイン画面まで辿り着かないという状況に。

原因箇所は位置情報の取得を待つところで、無限ループに突入していました。申請前に一度アプリをアンインストールしてテストしなかったのが原因でこのバグを捕捉できていませんでした。

Appleの審査は新規よりもアップデートの方が申請通過が早いらしいという情報は事前に得ていたのでもうその可能性に賭けるしかないと思い、速攻で修正し再度申請。この時点で申請は3回目。日付は11/12日。

ただそう甘く無いらしく応募2営業日前になってもiTunes Connectは音沙汰なし。これはもうしょうがないと思い、緊急レビューの禁じ手を使う覚悟を決めました。緊急レビューは依頼しても確実にやってくれるものではないので、一旦Appleの返答待ちになりました。

緊急レビュー依頼の結果は、NG。まあ個人都合だから断られてもしかたないと思いつつも、絶望。

もう手を尽くしたので、あとはコンテストの運営側を説得するしかないと思い開発者サイトを見ると、iOSアプリケーションの場合は締め切りまでにAppleにSubmitしたバイナリに関しては審査対象とする旨のアナウンスが!

僕が問い合わせたように他の応募者からも同様の問い合わせが来ていたようで、ダメ元で問い合わせたのが良かったのか、iOSアプリを作成している応募者は応募時と審査通過時にiTunes Connectのステータス履歴をPDFにしたものを送ればコンテストの運営側が確認するのでOKとのこと。

もう神に救われた気分でしたよ。ええ。

それからは直したい部分の改修と時間がなくて追加できなかった機能を追加してコードフリーズ。前回の失敗を活かし申請前に地下鉄に乗ってデバッグ。締切日が月曜日だったので前日までに申請と応募の素材と文章をそろえました。

そして本日無事Apple申請に通過しました。実際にDLしてみたところ前回のようにクラッシュするケースはありません。かなりバタつきましたが作成したアプリをApple申請に出したのは初めての経験だったので勉強になりました。

神から祝福を受けたアプリなので是非使ってみてください。

MetroCluster

MetroCluster

  • KATSUYA SHIMADA
  • ライフスタイル
  • 無料

2015-07-20 追記

上記アプリはAPIの公開期限が過ぎたためAppストアから削除しました