勉強会開催について考えていることを雑に書く
雑です。
前提
- 多くても100名くらいの
- 発表系イベントで
- コミュニティで企画するのではなくてきとーに興味ありそうな人に声をかける
開催のモチベーション
- 私が表立って開催している勉強会は「知らないことは何か、ということを知る」ことを目的として開催しているつもり
- つまり想定参加者層なんてないようなものだ(と思いたい)
発表者募集
- 発表者を探すのは大変
- これだけで力つきる可能性もある
- ので、発表者が集まらなければ諦めるのも一つの手かなと
- 話したい人が話したいように話せば良いと思う
- 何かを得られるかどうかは聞く側の捉え方次第
懇親会 vs 省電力運営
- キャンセルはでる(一桁だろうが二桁だろうが)
- 省電力開催するなら懇親会は面倒臭い
- とはいえ、懇親会の会話で得られることもある
どうしたものやらと思いつつ今の考えは
- ビアバッシュ可能な会場を選択する
- 参加者は当日募る
- 飲み物は調整しやすいし赤字になってもダメージすくないので前日勘で頼む
- 食べ物は当日頼む
- ピザは楽
- しかし糖質制限の方やチーズがだめな方に対応しずらい
- ここはちょっとまだ模索中…
- 省電力運営であることを通知する
- あまりに注文が多いなら以降「懇親会を開催しない」というとか?
- 参加者募集を前日もしくは当日まで募集しない?
- 仕事や病気など、どうしても当日キャンセルせざるを得ない理由はある
- とはいえ、当日参加可能になったと気付ける人は多くない
- なら、当日だけ募集にすれば参加できそうかつ本気で参加したい人が参加可能になるのでは?
みたいなことを考えつつ、まぁ次回あたりから順次試してみようかなという感じ
募集ページの概要
- 読まれないことが多い
- もしくは、読んだとしても忘れる
- ので、「こういうイベントだよ」というのはイベント名で伝えたほうがよさそう
- だが世の中厳しい
- その他のことは前日に別媒体で情報を流したほうがよさそうな気がする
まとめ
なんか色々発生するけど万能な解決策はない。 毎回考えるしかない気がする。
#TestingFrameworkMeeting を開催しました
発表資料やtogetterは以下にあります。
https://github.com/tddbc/TestingFrameworkMeeting/blob/master/links.md
久々にこの濃度のイベントに参加した気がしますね…。 今やってることに直接参考になる話とかも聞けたりしたので個人的には大満足でした。
というわけで熱にあてられたので記憶している範囲で振り返ってみます。
t-wadaさんの発表
「simplify, amplifyで何かお願いします」とゆるめにお願いしてしまって、あとあとこれで良かったのかと悩んでもいたけど、まーさすがt-wadaさんというか、最初の発表としていい感じに話を広げてくださった。
議論などの流れでアドリブ入ったり脱線したりしたのはなかなか楽しい展開だった。
Evolve slowly
私はてきとーに作ってはGitHubにポイ捨てするタチなのでそんなこと考えてなかったけど、確かにテスティングフレームワークは緩やかな発展のほうが良いというのには納得。 ただ、それで発展が阻害されてしまっては意味ないと思うので、適度な競合ライブラリはあっても良いのではないかとは思っている。
Runnerの挙動が全体的にバッチっぽい
- テストケースを収集して
- テスト実行して
- レポートを出力する
RUnnerとしてはこの流れが主流だけど、もっとincremental(?)にやれるのではという話を(たしかJUnit5の話の流れで)きいて、確かにこの部分はどの言語でもわりと胸中しているなと感じた。 そしてなかなか手を出しづらい場所でもあるよね…。
余談
シンプルな方向に作る人たちってどういう時にCoreに機能を追加するのだろう、と前々から思っていたけど、話きいたり質問して"Coreという存在はなく、小さな各ライブラリに依存性がある"という見方をするのが良い落とし所なのかなと個人的に結論付けた。
あとpower-assertが非英語圏で流行ってるというのに妙に納得した。
Goとテスト
tenntennさんの講演。 Goのテストに加えてGo文化の話も聞けて良かった。
標準パッケージとして色々揃っている
これは後方互換を重視する言語であれば強みだろう。 なにしろよほどのことがない限り公式がメンテするのだから。
ifとassertion
- Goのtestingライブラリにはassertionがない
- if文を書いてテストに失敗した時はErrorfなどを返す
というのに対して
- assertionがぱっと見どこにあるのかわかりづらい
という返しがあったのは、私にはなかった着眼点だった。 個人的にはifもassertionもそんなに変わりない気がしているが、違和感ある人はあるのだろう。
あえて機能を少なくすることで何が起こるか実験しいるのでは
みたいな意見があった。 推測の域を出ないけど興味深い考察だとは思うので、今後を見守りたいものである。
Lint
Kuniwakさんの講演。 私は個人プロジェクトではあまり使っていないが、仕事ではお世話になっているので最近の動向なども含めて色々きけて満足。
"変数名のtypoを単体テストで発見するのは遅すぎる"
静的型付け脳な私としては「それバックグラウンドでインクリメンタルコンパイルしておけば」と思わなくもないが、動的型付けだと確かにこの問題はあるのかもしれない。
省力化ツール
この話をきいて、かつてmzpさんが「機械的に調べられるものは機械が勝手に修正をコミットしてくれるまで行けばいいよね」的なことを仰っていたことを思い出した。
読み手のみの省力化だけでなく書き手の省力化も、というのが今後業界的な流れになってもおかしくないのではないだろうか。
Lintとテストレフームワークを共通化
Runnerを共通化するという発想は私にはなかった考えなので新鮮だった。 どのくらい新鮮だったかというと、翌日から F# と Scala の自作テスティングフレームワークにその機能を組み込めるか試して見る程度には。
ぱっと考えてみた限り原理的にはいけそうなだが、どこまでビルドチェーンを複雑にせずに組み込むかはちょっと考える必要がありそうだとは思ってる。
ASTにに含まれない情報はどうする?
- 具象構文木をlintの対象にする
- トークン列も組み合わせる
というパターンが存在するみたい(前者はやや実験的傾向がまだ強うそうに思うのだがどうなのだろう)。
余談
「PythonでVimScriptのLint作ってます」「GoでLuaのLint作ってます」の会話が個人的にツボだった。
QuickCheck(という名のテスト合成 + Property Based Testing)
私の発表。 これまた脱線しまくるという状況になった。
power assertに反逆したい
流行にはあえて反逆したくなることがあるので、そういうことを考えていますよという話をちょろっとしていた。 時間があったらもっと話したかったけど本題ではなかったので泣く泣くスキップ。。。
テストの合成
ここで色々と脱線してた。
- テストの合成とはなんぞや
- 初出ではなく、おそらく昔からある考えだとは思うけど出典見つけられず…
- テストが疎結合にならないのではないか?
- 疎結合とテスト実行速度のバランス次第だと思っている
- 話し損ねたこと
- unitを引数に受け取りテストケースを戻り値とする関数 -> 副作用を内包する
- 変数に束縛されたテストケース -> 副作用を持たない or 一度しか実行しない
- このあたりは言語によるかもしれない…?要検証
エッジケースの話
- だいたい人間でもわかりそうな部分のエッジケースしか発見できなさそう
これは一理あるけど、でも経験的に予想外のテスト失敗に遭遇したことがある身としては
性質自体を間違ってしまうリスクをどうやって減らす?
これは難しい問題だと思っている。 個人的には、
- よくある事例から性質を引っ張ってくる
- ミューテーションテスト
- 最初はユニットテストで担保し、ある程度でいけそうだと判断したら入れ替える
- エラー周りから攻める
- エラー系は入力集合を作りやすいことが多いし性質も"エラーになる"で終わらせられるから
あたりだと思っているが、根本的打開策になっていないのが厳しいところではある。
これはこれでスキルいるね
性質を導き出せるかは個人のスキルによるので、慣れていないとチーム全員で使うのは厳しいのではないかという指摘(というか参加者の感想)。
これは指摘通り。 まぁ、最初はリファクタリングの際に導入するとかで慣れていくしかないのではないでしょーか。
Serverspec
mizzyさんの講演。 OSSに対する姿勢などもきけて良い学びがあった。
AssurerとServerspecの思想の違い
実装動機が異なるとここまで異なるプロダクトになるのだなーと。
なぜRSpec?
- 強いこだわりがあるわけではない
- 非本質的なことには振り回されたくない
非本質的なことに振り回されたくないはかなり同意。 とはいえ、絶対に振り回されないは厳しいと思うのでどこまで最小限にできるかどうかなのかもな、と。
応用的な観点
テストをインフラの方面にもってきた、かつ敷居を低くしたというのを実感できる発表だった。
雑感
飛び入り発表
飛び入りでESDocやoktestの話も聞けて、大変参考になった。 この辺りは思うところがあったのでちょっとライブラリに手を加えたりしてみている。
懇親会での話
特に記憶に残っている話を箇条書きで。
- 枯れた(安定した)ライブラリが最終更新で判断されるのはなんか違う気がする
- GitHubのstarや最新コミットが目につきやすいので無意識・意識的に誘導されてしまう?
- どの程度使われているかに注目できれば良いのかも?人気言語まとめとかもどこかの団体が勝手にやってるわけだし
- 信頼問題
- Python2からPython3に移行できない理由とか
- 人は自分が使っている言語を擁護するように動く傾向がある
- DBセットアップのテスト
- Serverspec的なものをDBにも応用できないだろうか
- DB界隈とソフトウェア界隈の空気の違い?的な話
- 懇親会費が余ったのでJUnitに寄付することにした
- いつv1.0.0をリリースするか
- あれもこれもとズルズルいってしまう
- 対象環境が多いとどこまで対応してからリリースすべきか悩ましい
- 最低限という区切りを設ける/締切駆動開発などで時限式にするとか
furiosaやawsspecの話もきけて満足。
次回やるとしたら
やるかわからないけどこの辺り題材にゆるっと平日夜にやれないかな(議論したい、聞きたいというそこの貴方、発表者探しを手伝っていただけませんか? or 発表者になりませんか?)
謝辞
企画に関わってくださった皆様、発表者の方々、他のイベントと被りまくっていたのに本イベントに足を運んでくださった皆様に感謝します。 皆様のおかげで、とても意義のあるイベントになりました。
そしてmixiさんとKuniwakさん、朝早くから夜遅くまで会場準備撤収ありがとうございました。 とても気持ちよくイベントを行えました。
*1:というか心の葛藤?
dog用のautodocライブラリを作った
Rubyにはautodocというライブラリがあります。
これのdog用のライブラリを作りました。
pocketberserker/dog-autodoc · GitHub
dog-autodoc用のsbtプラグインは実装中です…最低限部分できたので一旦リリースしました。
pocketberserker/sbt-dog-autodoc · GitHub
モチベーション
Scalaにはplay-autodocというものが存在する。
でもこれ、Play Framework用なんですよね。ということで
- Playに依存しないものにしたい
- どうせだからクライアントは切り替えたい -> httpzを使おう
というのがモチベーションです。
苦労した点
- sbtから出力ディレクトリを設定しつつ
- テストを一回だけ実行し
- 実行後に出力する
この流れをどうやってみたすかかなり悩んだ。 というか、当初の想定より随分と変わっている(テストの実装ミスしてて当初想定していた残骸がゴミコミットとして残ってしまった…orz)。
最終的には、
- Autodoc用のTestFrameworkを登録しておく
- AutodocMarker型の値が結果となっているケースのみ特別な処理をしてからイベントを登録する
- Autodocは実行したテスト結果とテストケース名から文字列をジェネレートできる
Event#fullyQualifiedName
にクラス名を入れるので、あとは出力結果(文字列)をどうにかしてEventに持たせれば保存に必要な情報が揃う- ところで、出力が可能なのはテストに成功したときのみ
- テスト成功時には通常
Event#throwable.isDefined
はfalse
を返すはずである。なぜなら、テスト成功なのに例外が投げられている状態は矛盾しているからだ。 - 実際JUnit Reporterはこの組み合わせを無視している https://github.com/sbt/sbt/blob/v0.13.9/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala#L84
- つまり、この使われないであろう
Event#throwable
に出力を押し込んでおくことで、ファイル書き出しをsbt内だけで完結させることができる
- https://github.com/sbt/sbt/blob/v0.13.9/testing/src/main/scala/sbt/TestReportListener.scala#L9 を使ってイベントを取得してテスト終了時に保存
という形に。ひどい実装だ…。
こういう形になった最大の要因は型パラメータが邪魔をしてリフレクションでの取得 or 型の変換が難しかったからですね。以下そのときのxuwei-kさんのアドバイス。
@pocketberserker Scalaのリフレクション、(少なくとも2.10だと)スレッドセーフじゃないけど、2.10サポート諦める(もしくは頑張ってロックとって頑張る)んですか?
(さらに、2.11でもまだ直ってないという噂もある)
— Kenji Yoshida (@xuwei_k) September 3, 2015
@pocketberserker TestCaseをtype aliasやめて
case class TestCase[A](value: Kleisli[TestResult, Endo[Param], A])(implicit A: ClassTag[A])
とか?
— Kenji Yoshida (@xuwei_k) September 3, 2015
こういうときはType erasureが辛く感じます…。 結局、autodocのためにdog自体をいじるのはなんとなくいやだったので採用しませんでした。
まとめ
dogの作りが従来のユニットテスティングフレームワークと異なるからこういうときつらい…でもがんばる。
今後は安定かとかconsole用のWriterを復活させるかどうかあたりですかね。 あ、あとrename…ひどい命名ばかりしているので良い名前母種中です。
dogというScala用テストフレームワーク(?)を作ってみた
https://github.com/pocketberserker/dog
このフレームワークは Persimmon の移植というか Persimmon に存在していた型をよりMonadとかそういう方面に倒してみようというものです。
テストという存在の型について考えてみる - pocketberserkerの爆走
上記に途中経過は書きましたがそれから数日と経たずにいくつか変更が入ってます。
特徴
type TestCase[A] = Kleisli[TestResult, Endo[Param], A]
type AssertionResult[A] = NotPassedCause \/ A
- scalapropsフレンドリー
- イミュータブル、ミュータブルを意識しましょうてきな
モジュールとかその他リポジトリについて
- dog-core
- 基本となる型と関数をまとめたもの
- TestCase単体でタイムアウト設定して実行できるように scalaz.concurrent に依存している
- 上記の通り TestCase は Kleisli なので合成できる
- AssertionResult を合成することでいわゆる Soft Assertion を実現している
- dog
- sbtのtest-interfaceに依存
- scalapropsの実装をfork(実際にはコピーしたけど)して改修
- scalapropsとは異なり非同期実行しても問題ないつくりになっているがさてはてどうしたものやら
- gen
- scalaprops-genに依存
- scalaprops.Gen を使って入力値だけいい感じに生成してもらう
- 入力と期待値の集合は作れるけど性質までかけそうにない場合とかに…ぶっちゃけパラメタライズテストのかわり
- 固定値で回すくらいなら数撃ってあてようの方針
- props
- scalaprops-coreに依存
- PropertyやPropertiesとTestCaseの変換を目的としている
- sbt-dog 9割方sbt-scalapropsのコピー。scalaprops様様である(MITライセンスで助かった…)
- sbt-scalapropsに比べてぴーきーな挙動になりやすい
- dog-examplesサンプル集。雰囲気がつかめるかも?
ここまでで実装期間6日です。 正直ScalazとScalapropsがなければここまで楽に実装はできなかったと思うので xuwei-kさん ++
作った理由を改めて
- テストの合成とか型とかについてちゃんと検証したかった(某交流会の資料作成絡み)
- PersimmonのVS拡張や某ライブラリの移植に難航しているので息抜きしたかった
- 深夜のテンション
名前はリポジトリ作るときに"ドッグフーディングしたい"というのと"Co猫"という謎ワードがふってきて悩んだ結果であり、特に猫に張り合っているわけではないです。
今後の計画
Property#forAll
などをラップして性質を満たす時にGen#sample
をテスト結果として束縛できるようにしたい- Scala.js対応
- とはいえこれはリフレクションを使って実装している現状うまくいかない
- なので、scalaprops共々forkして別リポジトリで実装するつもり
- やりたいことあればてきとーに
こういうものがほしい、とかあればissueに登録していtだけるとうれしいです。
ScalaMatsuriにこれで応募してみるという手もあるが、さてはて。
テストという存在の型について考えてみる
いわゆるユニットテストフレームワークに現れる各種型について考えてみる。
成果は考えない。
このエントリは考えがまとまっていない段階で書かれたものであるため、間違っている可能性があります。
成果物(の途中経過)
途中結果を素っ飛ばした作成中のものは下記に公開している。
https://github.com/pocketberserker/dog/tree/d0ad112e00c72500a2ac5df764b7109249e684de
ドッグフーディングしたいのでこの名前になっただけである*1。
この記事では Scala with Scalaz と 一部 FSharp(というか Persimmon )を使って考察する。
テストケースは Kleisli
テストケースは入力値、実行事前条件、期待値、実行事後条件から構成されるはずだ。 このうち入力値、実行事前条件、実行事後条件は型の内部に隠せるものなので、たぶんよくあるテストケースはこんな感じになる。
trait TestCase { def run(): TestResult = { ??? } }
しかし、テスト結果を合成したい場合にはこれは圧倒的に情報が足りないので、テスト結果に値を持てるようにしたほうがよさそうである。
trait TestCase[A] { def run(): TestResult[A] = { ??? } } // 型コンストラクタはおいおい trait TestResult[A]
さて、これでTestCase#run
は 引数なしで実行できTestResult[A]
を返す操作が行える存在になった。
ここでちょっと考えると、引数なしというのは Unit
を引数にとる、と考えられなくもない。
そうすると都合の良いことに、Kleisli
の構造に一致する。
import scalaz._ type TestCase[A] = Kleisli[TestResult, Unit, A] // ちょっと不恰好だが、以下のように実行できる // testcase.run(())
テスト結果はMonadにできなくもない
テスト結果は多くの場合、成功か失敗かで表されるはずだ。
case class Success[A](value: A) extends TestResult[A] case class Failure[A](reason: String) extends TestResult[A]
しかし、これは色々と大雑把な構造になっている。
- エラーが考慮されていない
- 失敗時の情報が蓄積できない
- "失敗"なのか"テストをスキップ(無視)した"のか判断がつかない
問題1を解決するためにエラーを持てるようにする。
case class Done[A](result: String \/ A) extends TestResult[A] case class Error[A](e: Throwable) extends TestResult[A]
テスト実行完了か、エラーが発生したことが確認できるようになった。 この時点では明らかにMonadである。
問題2について解決を試みる。
case class Done[A](results: ValidationNel[String, A]) extends TestResult[A] case class Error[A](e: Throwable) extends TestResult[A]
成功時に保持すべき値は最終結果ひとつで良いのに対し、失敗情報は多数保持すべきなのでValidationNel
がちょうどよさそうだ。
ただし、実装次第ではMonad則を満たさなくなりそうである。
問題3が残っている。
これはValidationNel
だけでは解決できそうにないので、別の型を導入する。
trait Cause case class Violated(reason: String) extends Cause case class Skipped(reason: String) extends Cause trait AssertionResult[A] case class Passed[A](value: A) extends AssertionResult[A] case class NotPassed[A](cause: Cause) extends AssertionResult[A] type AssertionNel[A] = NonEmptyList[AssertionResult[A]] case class Done[A](results: AssertionNel[A]) extends TestResult[A] case class Error[A](e: Throwable) extends TestResult[A]
Done#results
に入る値を特定のものに絞り(ありえない状態を除外する、といったほうが正しい)、それ以外の入力は受け付けないようにすればTestResult[A]
はMonad則を満たす。
しかしその実装はMonadと呼べるのかというのは、疑問が尽きない。
Persimmon の TestResult は Monad ではない
今回はエラーだった場合はそれ以降何もしないことにしたので、Monadになる可能性があった。 しかし、実行できるアサーションはすべて実行してしまえ、という方針にするとMonad則を満たせなくなる。
Persimmon はそういう方針なので、おそらくMonad則を満たさない。
AssertionNel ~> TestResult
は実装できるか
これに関しては考え方次第である。
- テストケース名や入力値をテスト結果に持たせたいなら、無理
- 変換時は空のデータを持たせ、Runnerで後から差し替えるならできそう
検証が足りていないのでもう少し考慮すべき項目があるかもしれない。
Persimmonに関していえば、変換できなくもない。 テスト名が空な場合にはリフレクションで変数(関数)名前を取得して入れ替える機能が備わっているためだ。
Assertion[A]
は必要か
以下のような型を定義できる。
type Assertion[A] = Kleisli[AssertionResult, Unit, A]
しかし必要性を感じない。
基本的にさっさと実行されてしまうため、機能過多に思える。
これが何かに使えるか考えるなら、AssertionResult[A]
の機能拡充を考えたほうがよさそうである。
TestResult[A] は Applicative であるべきか
現時点のTestResult[A]
は入力値を制御しているためMonad則を満たすが、Applicativeにとどめておくべきかどうかは悩ましい問題だ。
Scalaのfor式は F# のコンピュテーション式に比べて表現に不安を感じるし、テスト失敗情報を蓄積させるならMonadにすべきではないのはその通りだ。 しかし、ScalaだとApplicativeで合成するのはそれはそれで面倒くさい作業にしかならない気もしている。
書きやすいDSLを実装する、という点ではScalaではマクロを使うことも考慮すべきなのだろうが…さてはて。
まとまらない
- FreeやFreeApを使って何かできないか考えていたらいつのまにかこういうものに行き着いた、なぜ
- Operational monad などの利用を考えると、やはりHaskellのライブラリを移植したほうが早いのだろうか…
- とはいえ、Scalazの各種型で色々定義できそうということがわかっただけでも面白かったので良しとする
*1:猫の双対でco猫とかにしてみたかったけどどう見ても名前負け思想だったのでやめた