プログラミングをしていると、テストをどうするかというので困る時がある。例えばあるコンポーネントのテストをしたいが、それはHTTPサーバを必要とする場合とか。実際に必要となるものによっては簡易版を用意してやるとか手はあるが、ユニットテストレベルでそこまでやる気にならないこともある。そもそも、用意した簡易版の何かしらは正しく動くのかとかも気になる。
Sagittariusではいろいろ黒魔術的な何かを使えばライブラリに束縛された値を上書きすることが可能ではある。っが、これをやるとグローバルに変更されるという問題もある。テストの実行単位が常に単一であるとか、単一スレッドでしか走らないとかにしてしまえばこれだけでも問題はないのだが、数が増えるとそれがボトルネックになるのが見えているのでできれば避けたい。
っと、この辺りまでが最近まで悶々としていた問題。もとになったのは、SchemeでもMockitoみたいなのほしいなぁというもの。そこから可搬性とか考えずに、とりあえず束縛だけ一時的に変更できればなんとかできなくね?というところに至る。っで、悶々としている間になんとかなりそうだなぁと思ったので実装してみた。実装の詳細は面白くもないだろうので割愛。
(sagittarius sandbox)というライブラリに実装。使い方は以下:
(import (rnrs) (sagittarius sandbox))
(define s "b")
(define (test) (string-ref s 0))
(with-sandbox
(lambda ()
(define-in-sandbox '(rnrs) (string-ref s i) #\a)
(test)))
;; => #\a
;; (test)
(playground ((string-ref '(rnrs) (lambda (s i) #\a)))
(test))
;; => #\a
コメントアウトされてるtestのコメント外すと予定と違う値が返ってくるが、既知のバグということで。理由としてはサンドボックス内で作られる束縛は指定されたライブラリのみ作用するけど、上記のtestで使われてるstring-refは(rnrs)が依存しているライブラリの一つで定義されてるので、一回サンドボックス外で実行するとGLOCに置き換わって定義されてるライブラリを直で見に行くから。VM内でGLOCに置き換える部分をもう一段ラップして元の識別子を残すようにすれば解決できるんだけど、性能への影響が怖いのとそこまで需要があるかなぁという気持ちが混じりあってとりあえず保留。性能に影響がでないうまい方法を思いついたらやることにする。サンドボックスができれば次はモックだということでちゃちゃっと作った。以下のように使える。
(import (rnrs) (srfi :1) (sagittarius mock))
(define (test) (make-list 3 #f))
(let ((status (mock-up ((srfi :1))
(test) (mock-status-of 'make-list))))
(mock-status-arguments-list status))
;; => ((3 #f))
(mock-up ((srfi :1))
(mock-it '(srfi :1) (make-list . args) '(a a))
(make-list 3 #f))
;; => (a a)
まだAPIが荒いのでもう少し練らないととは思いつつ、テストに使えそうな感じではある。実装に綺麗い目な黒魔術(絶対にドキュメント化されないという意味で)を多用しているのは、まぁご愛敬ということで。
No comments:
Post a Comment