メモ:DBItestを通す
DBIを使ったパッケージを実装したい人のためのお役立ち情報(超ニッチ)その2です。その1はこれ。
DBItestとは
DBIに準拠していることをテストするために、DBItestというパッケージが用意されています。
DBItestをインストールすれば、おめでとうございます、これであなたもDBIとのつらい戦いのスタートラインに立ったことになります。
DBItestの使い方
使い方はこのvignetteが詳しいです。
まずはDBItest用のtestthatのテストを追加しましょう。例ではDBItest
になってますが、なんでも大丈夫です。
devtools::use_testthat() devtools::use_test("DBItest")
これを実行すると、tests/testthat/test-DBItest.Rというファイルができているはずです。開いて、中身をまるごと以下に置き換えましょう。<ドライバのコンストラクタ>()
というのはSQLite()
とかそういうやつです。
DBItest::make_context(<ドライバのコンストラクタ>(), NULL) DBItest::test_all()
え、たったの2行でいいの??と思うかもしれませんが、たったの2行でいいんですけど、DBItest::test_all()
で流れるフルスペックのテストを通るようなDBパッケージは稀です。たぶんGitHubにログインした状態じゃないと見れませんが、以下から名だたるパッケージのtest-DBItest.R
を眺めると、苦労の多さが偲ばれることでしょう。
ということで、いきなり全部のテストを流すのではなく、まずは簡単なところから始めましょう。DBItest::test_all()
の中身を見れば以下のようになっているのがわかります。
DBItest::test_all #> function (skip = NULL, ctx = get_default_context()) #> { #> test_getting_started(skip = skip, ctx = ctx) #> test_driver(skip = skip, ctx = ctx) #> test_connection(skip = skip, ctx = ctx) #> test_result(skip = skip, ctx = ctx) #> test_sql(skip = skip, ctx = ctx) #> test_meta(skip = skip, ctx = ctx) #> test_transaction(skip = skip, ctx = ctx) #> test_compliance(skip = skip, ctx = ctx) #> } #> <environment: namespace:DBItest>
ひとまずtest_driver()
だけからはじめるといいでしょう。具体的には上のtest-DBItest.R
をこう書き換えます。
DBItest::make_context(<ドライバのコンストラクタ>(), NULL) DBItest::test_getting_started() DBItest::test_driver()
これで、test_driver()
が通ったら次はtest_connect()
、その次はtest_result()
...というようにしてテストを足していきましょう。
tweak
DBItestには、テストケースをいじらないといけない場合としてよくあるものをDBItest::make_context()
のtweak
引数に指定できます。この引数に指定するオブジェクトはDBItest::tweak()
で作成します。
例えば、Redashrでまず引っかかったのは、こんなエラーです。RedashrパッケージのドライバのコンストラクタはRedash()
なんですが、なぜかedash()
という関数を使おうとして「そんなものはない」というエラーになっています。
Failed ------------------------------------------------------------------------- 1. Failure: DBItest: Driver: constructor --------------------------------------- "edashr" %in% getNamespaceExports(pkg_env) isn't true. 2. Failure: DBItest: Driver: constructor --------------------------------------- exists("edashr", mode = "function", pkg_env) isn't true. 3. Error: DBItest: Driver: constructor ----------------------------------------- object 'edashr' of mode 'function' was not found
これは、「DBI対応のパッケージは「R + <DB名>」という名前なので、コンストラクタはパッケージ名の先頭一文字を取ったものだ!」という暗黙の前提があるためです(そんなのどこに書いてあるんだ...)。
このエラーに対処するためには、tweak()
のconstructor_name
を指定します。具体的にはこうなります。
tweaks <- DBItest::tweaks( constructor_name = "Redash" ) DBItest::make_context( Redash(), NULL, tweaks = tweaks )
他にもいろんなtweakがあるので詳しくはヘルプを参照してみてください。ちなみにRedashrで今指定しているtweakはconstructor_relax_args
とomit_blob_tests
です。参考までに。
construct_args
さて、DBをテストするには、テスト用のDBに接続したりします。当たり前ですけど。しかし、常にデフォルト引数で接続できるとは限りません。そういときは、DBItest::make_context()
のconstruct_args
引数に必要な引数を指定できます。
DBItest::make_context( SuperGreatDB(), connect_args = list(host = "127.0.1.1", password = "foo"), tweaks = tweaks )
また、環境による違いもあります。例えばRedashrでは、手元とCIでIPアドレスやポートが違います。こういう違いを吸収するものとして、configパッケージが便利です。
Redashrではこういうconfig.yml
をtests/testthat/
下(プロジェクトルートではない点に注意)に置いて、
default: connect_args: api_key: L3BcIMTltQoTB83mGtHrcUa8mmeJdMaTuGFA3Bkv base_url: http://10.0.75.1/ data_source_name: test circleci: connect_args: api_key: L3BcIMTltQoTB83mGtHrcUa8mmeJdMaTuGFA3Bkv base_url: http://127.0.0.1:5000/ data_source_name: test
CI上だけで定義される環境変数で設定を分岐しています。(まあこれはbase_url
しか違わないので、configパッケージを使うほどでもないのかもしれませんけど)
if (identical(Sys.getenv("CIRCLECI"), "true")) Sys.setenv(R_CONFIG_ACTIVE = "circleci") DBItest::make_context( Redash(), connect_args = config::get("connect_args"), tweaks = tweaks )
skip
そうはいっても、がんばっても通らないテストというものがあります。こういうものは、各テストのskip
引数に指定しておくとスキップできます。
たとえば、Redashrではdata_type_driver
というテストをスキップ指定しています。
DBItest::test_driver( skip = c( # Redash cannot determine the type of backend before connecting "data_type_driver" ) )
困ったときは仕様を確認
なぜこのテストが落ちるのか、とか困ったときには仕様を確認しましょう。
そうすれば、デフォルトの実装の方が間違っていることにも気付いたりできます(つらい)。
感想
DBItestは、ドキュメントがちゃんとしているようで色んな所に暗黙知みたいなのがあるのが難しいところですね...。ここに書いた内容があってるのかもあんまり自信がないので、変なとこがあればツッコミください。