既存のmethodを壊さずに内部のmethodを差し替えて実行したい

はじめに

例えば、以下のような状況で M.F0() を呼びたい。しかしその内部で呼ばれる m.f()は呼んでほしくない場合。

type M struct {
}

func (m *M) F0() error {
    // do something
    return m.f()
}

func (m *M) F1() error {
    // do something
    return m.f()
}

func (m *M) f() error {
    // この処理を呼びたくない
    panic("don't call!!1")
}

通常のアプリケーションコードとして結合された状態で呼ばれる分には何ら問題無いコードも、テスト中では呼び出したくない場合がある。 例えば、 M.f() が外部のリソースに依存するような処理だった場合など、テスト中では呼び出したくない。

もちろん現状で以下の呼び出しはpanicする。

m.F0() // panic
m.F1() // panic

この時、公開された関数である M.F0() , M.F1() の内部で呼ばれる M.f() の挙動を差し替えたい。

単純なembedではダメ

以下はどう考えてもダメ。

type mockM struct {
    *M
}

func (m *M) f() error {
    // こちらが呼ばれてくれると嬉しいけれど
}

embedにより書き換えたつもりになっているのかもしれないけれど。mockM.F0() の呼び出しで、実質呼ばれるのは内部の M.F0()。 その為、結局 M.f() の方が呼ばれてしまう。

内部の部分を切り分けてembedするのが無難そう

内部の部分を切り分けてembedするのが無難そう。型定義を以下の様に変える。

type mClient interface {
    f() error
}

type M struct {
    mClient
}

内部で利用するclientのinterfaceを定義。 デフォルトではactualMClientの方を利用し、テストの時などはmockedMClientの方を利用する。

type actualMClient struct {
}

func (c *actualMailerClient) f() error {
    // この処理を呼びたくない
}

type mockedMClient struct {
}

func (c *mockedMClient) f() error {
    // こちらが呼ばれてくれると嬉しい
}

以下のような関数があるとうれしいかも?

func NewM() *M{
    client := actualMClient{}
    m := M{mClient: &client}
    return &m
}

python3.5でpyobjcのコードを動かそうとしたら Symbol not found: _PyObject_REPR

はじめに

python3.5でpyobjcのコードを動かそうとした際に、以下のようなエラーが出てしまっていた。

Symbol not found: _PyObject_REPR

結論からいうとパッケージが古かったので更新した。(3.0.4 -> 3.1.1)

stack overflowで該当する質問

現在の状態

個人的にmacportsを使っている。

$ port installed | grep pyobjc
  py35-pyobjc @3.0.4_0 (active)
  py35-pyobjc-cocoa @3.0.4_0 (active)

portfileの書き換え

pypiを見たら最新は3.1.1だったのでそれを使うように書き換える。pipで単にインストールしても良いけれど。拡張ライブラリが含まれたようなものはその環境固有の方法で管理したほうが良い気がしている。

ここに書いてある作業に従ってportfileを書き換えれば良い

$ cd `port dir py35-pyobjc-cocoa`
$ sudo cp -p Portfile Portfile.orig
# versionの書き換え
$ sudo port edit py35-pyobjc-cocoa
$ sudo port -d checksum py35-pyobjc-cocoa
# distのchecksumの方に書き換える
$ sudo port edit py35-pyobjc-cocoa
$ sudo port -d install py35-pyobjc-cocoa

$ cd `port dir py35-pyobjc`
$ sudo cp -p Portfile Portfile.orig
# versionの書き換え
$ sudo port edit py35-pyobjc
$ sudo port -d checksum py35-pyobjc
$ sudo port -d livecheck py35-pyobjc
# distのchecksumの方に書き換える
$ sudo port edit py35-pyobjc
$ sudo port -d install py35-pyobjc

portfileを書き換えたらインストールする。

versionの書き換え

3.0.4 -> 3.1.1

checksumの書き換え

checksumを実行してわざと失敗させて新しいchecksumの値を取るのが便利。以下のような出力からDistfile ... の部分を取り出す。

Portfile checksum: pyobjc-core-3.1.1.tar.gz rmd160 12ee6d8132c59420c768dafdb2ee73f17ea97cc1
Distfile checksum: pyobjc-core-3.1.1.tar.gz rmd160 20a806aed2a7396ca86ca680060947ff6b48eccb
DEBUG: Calculated (sha256) is 07c4e9c4fa5bec881cd86bf1b7dbcdb306b16567b490d06fa538c0d1b2677d2e
Error: Checksum (sha256) mismatch for pyobjc-core-3.1.1.tar.gz
Portfile checksum: pyobjc-core-3.1.1.tar.gz sha256 a4708886ff7844ff7537c60d14e903014a90b3e3cdbad5e717acfcbe150768b7
Distfile checksum: pyobjc-core-3.1.1.tar.gz sha256 07c4e9c4fa5bec881cd86bf1b7dbcdb306b16567b490d06fa538c0d1b2677d2e

結果

$ port installed | grep pyobjc
  py35-pyobjc @3.0.4_0
  py35-pyobjc @3.1.1_0 (active)
  py35-pyobjc-cocoa @3.0.4_0
  py35-pyobjc-cocoa @3.1.1_0 (active)

hello worldが動くようになった。