すべおの人間を猫で殺すか、Akka.FSMのステヌトマシンで殺す





最初の蚘事で曞いたように、さほど前ではなく、C ++からScalaに切り替えたした。 これに䌎い、私はアッカが挔じる俳優のモデルを研究し始めたした。 私にずっお最も鮮明な印象は、このラむブラリが提䟛する有限状態マシンFSMの実装ずテストの容易さでした。 Akkaには他にもすばらしい䟿利なものがたくさんあるので、なぜこれが起こったのかはわかりたせん。 しかし、今では、最初のScalaプロゞェクトで、郜合の良いこずに裏付けられたあらゆる機䌚にステヌトマシンを䜿甚しおいたす心から願っおいたす。 それで、私はAkka.FSMに぀いおの知識だけでなく、私が蓄積したいく぀かのトリックず個人的なベストプラクティスをコミュニティず共有する準備ができおいるず刀断したした。 私はハブで同様のトピックを芋぀けたせんでしたそしお、䞀般的にScalaずAkkaに぀いおの蚘事では、それはどういうわけかあたりありたせんでした。 そしお、それが退屈しないように-私は䞀緒に本物の電子猫の行動を実装するこずを提案したす。 私の蚘事に觊発された孀独なロマンティックな魂が、宿題ずしお本栌的な「たたこっち」に提䟛する機胜を掗緎させるず信じたい。 䞻なこずは、コメントでコミュニティず結果を共有した埌、そのような魂は忘れないずいうこずです。 理想的には、共有アクセスを備えたgithubでプロゞェクトを䜜成し、誰もがトランスヒュヌマニズムのアむデアの開発に自分の個人的な貢献をもたらすこずができるようにしたす。 そしお今-冗談ず空想の方向で、私たちは袖をたくりたす。 最初から始めたす。7Dずプレれンスの効果を高めるために、私はあなたずすべおのステップを螏みたす。 TDD添付認蚌されおいないロボコットがあれば、それは確かに冗談ではありたせん。



この蚘事の情報は、すでにScalaに少なくずも少し慣れおおり、少なくずも俳優のモデルに぀いお衚面的な理解を持っおいる人を察象ずしおいたす。 知り合いになりたいが、どこから始めればいいのか分からない人のために、ボヌナスずしお、小さな開始指瀺を曞いお、それがネタバレしないようにスポむラヌの䞋に隠したした。 必芁なすべおのラむブラリを䜿甚しお、あたり手間をかけずにクリヌンなScalaプロゞェクトを䜜成する方法に぀いお説明したす。





そのため、既に理解しおいるように、最初にakka-actor、akka-testkitおよびscalatestラむブラリの最新バヌゞョンを䜿甚したクリヌンなプロゞェクトが必芁ですこの蚘事の執筆時点ではakka 2.3.4およびscalatest 2.1.6です。



''うヌん...しかし、これはどのようなゎミですか ''、たたは察象倖の人のために
譊告1玠手でScalaをたったく感じず、鍵穎からもScalaを芗き芋しなかった堎合、この蚘事で埌述するすべおの特定の郚分を理解するこずはできないでしょう。 しかし、最も頑固な人私はこれを自分で承認したすのために、ファッショナブルで光沢のあるTypesafe Activatorバンを䜿甚しお、Scalaで䞍必芁な困難なく新しいプロゞェクトを䜜成する方法を説明したす。



譊告2次のコマンドラむンアクションは、OS LinuxおよびMac OS Xに有効です。Windowsに必芁なアクションは、説明されおいるものず䌌おいたすが、異なりたす少なくずもProjectsディレクトリ名の前にチルダがないこず、バックスラッシュ、「フォルダ」ずいう語「ディレクトリ」たたは「ディレクトリ」ずいう蚀葉の代わりに、Windows甚に蚭蚈された特別なactivate.batファむルのアヌカむブ内の存圚。



プロゞェクトを䜜成する



行きたしょう。 私が個人的に新しいプロゞェクトを䜜成する最も簡単な方法は、前述のタむプセヌフアクティベヌタヌを公匏りェブサむトからダりンロヌドするこずです。 執筆時点でサむトで宣蚀されおいるラむブラリのバヌゞョンは、Activator 1.2.10、Akka 2.3.4、Scala 2.11.1です。 すべおがZIPアヌカむブずしおダりンロヌドされたす。 ダりンロヌド䞭は、オヌブンを230℃に予熱する必芁がありたす。 それたでの間、「なぜオヌブンが必芁なのですか o_0”-352MBのアヌカむブが既にダりンロヌドされおいたす。 このすべおをディスクのどこかに展開したす。 〜/ Projectsディレクトリですべおの操䜜を行いたす。 だから



$ mkdir ~/Projects $ cd ~/Projects $ unzip ~/Downloads/typesafe-activator-1.2.10.zip
      
      





アヌカむブを開梱したら、パンにオむルを塗るこずを忘れないでください。 すべお、私は玄束したす、そしお、すべおは非垞に深刻になりたす。 プロゞェクトを䜜成するには、グラフィカルむンタヌフェむスを䜿甚する方法ずコマンドラむンを䜿甚する方法の2぀がありたす。 劎働者ゞェダむずしお、もちろん、電力の経路を遞択したすさらに、タヌミナルはすでに開いおいたす-䜕らかのUIがあるため、タヌミナルを閉じないでください。



 $ activator-1.2.10/activator new kote hello-akka
      
      





この簡単な行を䜿甚しお、アクティベヌタヌに、 hello-akkaずいうテンプレヌトから、珟圚のフォルダヌに 新しい  koteプロゞェクトを䜜成するよう指瀺したす芚えおいるように、〜/ Projectsに残りたす。 このテンプレヌトには、必芁なラむブラリ甚に構成されたbuild.sbtファむルが既に含たれおいたす。 ダヌクサむドの可胜性は、い぀ものように、より簡単で魅力的なので、誰かがコマンドラむンで成功しない堎合は、。 ./activator ui



たたは既にアクティベヌタヌコン゜ヌルにいる堎合は単にui を入力しお、開くブラりザヌですべおを行うこずができたす。 そこはすべおずおもきれいです。少なくずも楜しみのためだけに芋おください-あなたがそれを奜きになるこずを玄束したす。 プロゞェクトが䜜成されたら、そのディレクトリに移動したす。



 $ cd kote
      
      





IDEたたは非IDE



さらに、各ゞェダむは、ed、vi、vim、emacs、Sublime、TextMate、Atom、他の䜕か、たたは本栌的なIDEを䜿甚しお自分の匷さを決定したす。 個人的には、Scalaに切り替えたずきにIntelliJ IDEAを䜿い始めたので、すぐにこの環境のプロゞェクトファむルを生成したす。 動䜜させるには、 addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.2")



行addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.2")



をproject / plugins.sbtファむルに远加する必芁がありたす。



 $ echo "addSbtPlugin(\"com.github.mpeltonen\" % \"sbt-idea\" % \"1.5.2\")" > project/plugins.sbt
      
      





その埌、アクティベヌタを起動するず、圌は必芁なすべおをコマンドで実行したす。



 $ $ ./activator > gen-idea sbt-classifiers
      
      





これで、IDEAでプロゞェクトを開くこずができたす。



それずもIDEではありたせんか



IDEがフォヌスのダヌクサむドたたはその逆であり、ゞェダむが䟡倀がないず思うなら、これがあなたの完党な暩利です。 この堎合、アクティベヌタヌのコマンドラむンに留たり、任意の䟿利な方法でファむルを線集できたす。 そしお、猫の将来の運呜を決定するのは、2぀のアクティベヌタヌチヌムだけです。

  1. コンパむル -プロゞェクトのコンパむル 。
  2. test-すべおのテストを実行したす。 必芁に応じおコンパむルを呌び出したすので、私は嘘を぀いたので、このコマンドだけで察応できたす。


この蚘事の䞀郚ずしお本番環境でkoteを起動したせんが、Tamagotchiの最終バヌゞョンの朜圚的な開発者はrunコマンドを䜿甚しおこれを行うこずができたす。



こおのために堎所を掃陀したす



ご存知のように、すべおの猫はきちんずした物足りないです。 したがっお、私たちは将来のペットのために枅朔で敎頓された家を準備するこずから始めたす。 ぀たり、hello-akkaテンプレヌトの䞀郚ずしお新しく䜜成されたプロゞェクトに付属するすべおの䜙分なファむルを削陀したす。 個人的には、src / main / java、src / test / javaディレクトリずすべおのコンテンツ、および䞍芁なすべおの.scalaファむルを考慮する必芁がありたすsrc / main / scala / HelloAkkaScala.scalaずsrc / test / scala /HelloAkkaSpec.scala。 さお、これで先ぞ進む準備ができたした。





最初のステップ



最初はテストがありたした。 そしお、テストはコンパむルされたせんでした。 ご存知のように、この声明がTDDの基本的な仮定であり、珟圚私がコミットしおいたす。 したがっお、説明はマシン自䜓ではなく、Akka TestKitラむブラリが提䟛するテスト機胜を実蚌するための最初のテストの䜜成から始めたす。 私が䜿甚するアクティベヌタヌに加えお、すでにテストフレヌムワヌクであるscalatestがありたす。 圌は私にずおも䌌合っおおり、私たちのプロゞェクトでそれを䜿わない理由はないず思いたす。 䞀般に、Akka TestKitはフレヌムワヌクに䟝存しないため、spec2などで䜿甚できたす。 テストパッケヌゞの名前を気にしないために、ファむルを盎接src / test / scala / KoteSpec.scalaに配眮したす



 import akka.actor.ActorSystem import akka.testkit.{ImplicitSender, TestFSMRef, TestKit} import org.scalatest.{BeforeAndAfterAll, FreeSpecLike, Matchers} class KoteSpec(_system: ActorSystem) extends TestKit(_system) with ImplicitSender with Matchers with FreeSpecLike with BeforeAndAfterAll { def this() = this(ActorSystem("KoteSpec")) import kote.Kote._ override def afterAll(): Unit = { system.shutdown() system.awaitTermination(10.seconds) } "A Kote actor" - { // All future tests go here } }
      
      





さらに、コメントのすぐ䞋にあるすべおのテストをこのクラスの本䜓に远加するこずを想定しおいたす。 たずえば、FlatSpecLikeではなく、FreeSpecLikeを䜿甚したす。これは、さたざたな状態ずオヌトマトンの遷移に察する倚くのテストを明確に構成する方が個人的にはるかに䟿利だからです。 最初のテストの䜜成を開始する準備ができたので、猫は䜕よりもやりたいこず-睡眠ずいう事実から始めるこずを提案したす。 したがっお、TDDの原則を考慮しお、新しく生たれた猫が最初から寝おいるこずを確認するテストを䜜成したす。



 "should sleep at birth" in { val kote = TestFSMRef(new Kote) kote.stateName should be(State.Sleeping) kote.stateData should be(Data.Empty) }
      
      







順番に理解しおみたしょう。 TestFSMRefは、FSMクラスを䜿甚しお実装されたステヌトマシンのテストを簡玠化するためにAkka TestKitフレヌムワヌクが提䟛するクラスです。 より正確に蚀うず、TestFSMRefは、applyメ゜ッドを呌び出すコンパニオンオブゞェクトを持぀クラスです。 そしおこのメ​​゜ッドは、最も䞀般的なActorRefの子孫であるTestFSMRefクラスのむンスタンスを返したす。぀たり、単玔なアクタヌずしおメッセヌゞをマシンに送信できたす。 ただし、TestFSMRefの機胜は、単玔なActorRefず比范しお倚少拡匵されおおり、これらの拡匵機胜はテスト専甚に蚭蚈されおいたす。 これらの拡匵機胜の1぀は、テストした子猫の珟圚の状態ぞのアクセスを提䟛するstateNameずstateDataの2぀の関数です。 なぜ2぀の機胜、1぀の状態があるのですか 実際、通垞の理解では、状態はオヌトマトンの内郚パラメヌタヌの珟圚の倀のセットです。 2぀の倉数はどこから来たのですか 事実は、オヌトマトンの珟圚の状態を説明するために、Akka.FSMErlangのオヌトマトンの蚭蚈の原則に基づくは、状態の「名前」ずそれに関連付けられた「デヌタ」の抂念を分けおいるずいうこずです。 さらに、Akkaはオヌトマトンクラスで可倉プロパティvar の䜿甚を避けるこずをお勧めしたす。これにより、プログラムコヌド内のオヌトマトンの状態は、事前定矩された少数のよく知られた堎所でのみ倉曎でき、明癜で暗黙的な倉曎を避けるこずができるずいう利点がありたす。 さらに、将来のクラス内からこれらの2぀の倉数に盎接アクセスするこずはできたせん。これらは、FSM基本クラスでprivateずしお宣蚀されおいたす。 ただし、TestFSMRefはテストのためにそれらぞのアクセスを提䟛したす。 そしお、マシン自䜓のクラスからそれらに到達する方法はさらに明らかになりたす。



それで、私たちの睡眠状態を私は睡眠ず呌びたした。 そしお、それを補助オブゞェクトStateに抌し蟌みたす。これは、コヌドを明確にし、混乱を避けるために、これからステヌトの名前をすべお保存したす。 デヌタに関しおは、この段階ではただ䜕になるかわかりたせん。 ただし、マシンをデヌタずしお「フィヌド」する必芁がありたす。そうしないず機胜したせん。 したがっお、Emptyずいう名前で倉数に名前を付けるこずにしたした。これは私の個人的な遞択であり、䜕も匷制するものではありたせん。 別の方法で呌び出すこずができたすNothing、Undefined。 私に関しおは、Emptyは短く、十分な情報を提䟛したす。 たた、デヌタず呌ばれる特別に割り圓おられたオブゞェクトにデヌタを保存するこずにも慣れおいたす。 さたざたな皮類のデヌタの「戊闘」アサルトラむフルには、州名よりも少ない、たたはそれ以䞊の名前がある堎合があるため、垞に専甚の堎所に保管したす。カツレツを別々に、別々に飛ぶ。



さお、コンパむルしおいたすか テストで参照する型ず倉数がないため、コンパむルが倱敗するこずは明らかです。 これは、TDDサむクルの次の段階に進む準備ができおいるこずを意味したす。



オヌトマトンのクラスを宣蚀するには、状態の名前ずそのデヌタを蚘述するすべおのクラスずオブゞェクトが継承される2぀の基本タむプが必芁です。 環境を散らかさないために、子猫の生掻に必芁なすべおの定矩を保存するコンパニオンオブゞェクトを䜜成したす。 これはScalaの䞖界では䞀般的な基準であり、誰も私たちを責めるこずはありたせん。 パッケヌゞの名前を䜿甚したテストでわずらわしくない堎合は、プロゞェクト自䜓に぀いおも䜜成したす。 圌をkoteず呌びたしょう。 そしお、ペットの実装ファむルをそれぞれsrc / main / scala / kote / Kote.scalaに配眮したす。 それでは、始めたしょう



 package kote import akka.actor.FSM import scala.concurrent.duration._ /** Kote companion object */ object Kote { sealed trait State sealed trait Data }
      
      





これらの定矩は、子猫クラスを宣蚀するのに十分です



 /** Kote Tamakotchi mimimi njawka! */ class Kote extends FSM[Kote.State, Kote.Data] { import Kote._ }
      
      





クラス内に、さらにアクセスしやすくするために、補助オブゞェクトでさらに宣蚀されるすべおのもののむンポヌトを远加したした。 最初の「眠い」状態の名前ずデヌタの倀のみを宣蚀できたす。



 /** Kote companion object */ object Kote { sealed trait State sealed trait Data object State { case object Sleeping extends State } object Data { case object Empty extends Data } }
      
      





テストをコンパむルする前に、最埌のステップが残っおいたした。 クラス自䜓からず同じように簡単か぀単玔にKoteオブゞェクトの内郚を参照するそしお今埌参照したいため、KoteSpecクラスの本䜓にむンポヌトを远加するず䟿利です。 代替コンストラクタを宣蚀した盎埌にできたす



 ... def this() = this(ActorSystem("KoteSpec")) import Kote._ ...
      
      





さお、KoteSpec.scalaファむルのimportセクションにimport kote.Koteを远加するこずを忘れないでください。 これでプロゞェクトが正垞にコンパむルされ、テストを実行できたす。 なに èµ€ NullPointerException そしお、あなたは考えたした-新しい子猫を䜜るのはずおも簡単ですか 自然は䜕癟䞇幎もの進化を台無しにしおきたした たあ、パニックはありたせん。 おそらく問題は、出生盎埌に䜕をすべきかを動物に䌝えなかったこずです。 ずおも簡単です



 class Kote extends FSM[Kote.State, Kote.Data] { import Kote._ startWith(State.Sleeping, Data.Empty) }
      
      





テストを開始したす-出来䞊がり 私の倧奜きな緑 子猫は生き返ったように芋えたしたが、どういうわけか退屈なものです。それ自䜓は愚かに眠りたす-それがすべおです。 悲しいです。 圌を起こしたしょう。



「眠り、私の喜び」、たたは初期状態での行動の実珟方法



これをどのように行うのでしょうか テストの実行䞭にモニタヌの速床を萜ずさないでください 建蚭的に考えおみたしょう。子猫が俳優である堎合、圌ず通信する唯䞀の方法はメッセヌゞを送信するこずです。 そのような重芁なコテ官僚、あなたは圌に秘曞を雇うだけでよいので、圌は通信を敎理したす。 圌が目を芚たすために、圌はどんなメッセヌゞを送るべきですか 私たちは圌を簡単に曞くこずができたしたkote 「プロニス」 起きろ」 しかし、私は個人的に行の䞭でメッセヌゞを送信するのは悪いマナヌだず考えおいたす。なぜなら、あなたはい぀でもある文字で間違いを犯すこずができ、コンパむラはそれに気付かず、デバッグするのが非垞に難しいからです。 そしお、あなたが空想した堎合、私たちの新生児のコテはただ人間の蚀語を理解しおいないはずです。 チヌムの特別な猫の蚀語を開発するこずを提案したす。圌はそれを誕生から孊び始めおいるようです。 たあ、本胜的に、たたは䜕か。 そしお、私たちは圌の本胜の発展に貢献したす。 圌を蚓緎する最初のチヌムはWakeUpず呌ばれたす。 そしお、補助オブゞェクトのCommandsサブオブゞェクトに配眮したす。



 object Kote { ... object Commands { case object WakeUp } }
      
      





それではテストを始めたしょう



 "should wake up on command" in { val kote = TestFSMRef(new Kote) kote ! Commands.WakeUp kote.stateName should be (State.Awake) }
      
      





もちろん、テストはコンパむルされたせん。 条件の名前を宣蚀するのを忘れたした



  case object Awake extends State
      
      





テストはコンパむルされたしたが、どうやらそれは私たちのために運呜づけられたもので、別の䟋倖 NoSuchElementExceptionkey not foundSleepingでクラッシュしたす。 これらすべおの野barな文章はどういう意味ですか 量子実隓の若い恋人に、圌は眠るべきだず蚀いたした、そしお圌は本圓に玠盎に眠りたすが、圌はただ䜕を眠り 、 どのようにそれをするかを知りたせん。 さらに、この䞍確実性の状態で圌にメッセヌゞを送ろうずしおいたす。 しかし、猫の有名な拷問者や䞭毒者のようになり、貧しい動物を絶望的な無知に保ち、その行動を簡単に説明したしょう



 when(State.Sleeping, Data.Empty) { FSM.NullFunction }
      
      





はじめに、悪くない。 whenは、2組の角かっこを持぀最も䞀般的なscala関数です。 ぀たり、の堎合です。 たず、行動を蚘述したい状態の名前を瀺し、次にこの堎合scalaがそれらを瀺すこずができないため、2番目の括匧は衚瀺されたせん、動物の行動を特城付ける郚分関数この状態。 ファンキヌな振る舞いです。 そしお、行動はさたざたな倖郚刺激に察する反応です。 単に-着信メッセヌゞで。 通垞の反応には3぀のタむプがありたす-マシンが珟圚の状態のたた滞圚、新しい状態に移行goto、たたは䜜業を停止停止したす。 4番目のオプション-「異垞な」反応-は、マシンが問題に察凊できず、䟋倖をスロヌする堎合ですそしお、通垞のアクタヌの堎合のように、スヌパヌバむザヌが珟圚の監督の戊略に埓っおそれをどうするかを決定したす。 䟋倖のトピックに぀いおは埌ほど觊れたす。



FSM.NullFunctionは、この状態の猫はたったく䜕もせず、䜕にも反応せず、すべおの着信メッセヌゞを耳に通すこずを䌝える、Akkaラむブラリによっお有甚に提䟛される関数です。 {case _ =>}ず曞くこずもできたすが、それはたったく同じではありたせん。これに぀いおは埌で説明したす。 NullFunctionを将来の状態を蚘述するための「ギャグ」ずしお䜿甚するず䟿利です。詳现はこの段階では重芁ではありたせんが、それらぞの移行をテストする必芁がありたす。



「りェむクアップ、レむゞヌビヌスト」、たたは新しい状態ぞの遷移によっおむベントに応答する方法



それでは、今すぐテストを実行したしょう-そしお今、転倒の理由は完党に異なっおいたす睡眠は目芚めず等しくありたせんでした。 もちろん、結局のずころ、私たちの猫は眠るこずを孊びたしたが、私たちはただ圌にりェむクアップチヌムぞの察応方法を教えおいたせん。 少しかき混ぜおみたしょう



 when(State.Sleeping) { case Event(Commands.WakeUp, Data.Empty) => goto(State.Awake) }
      
      





前述したように、状態ずデヌタの名前を持぀倉数に盎接アクセスするこずはできたせん。 メッセヌゞがマシンに到着したずきにのみ、それらにアクセスできたす。 FSMはこのメッセヌゞをケヌスクラスむベントにラップし、そこに珟圚のステヌタスデヌタを远加したす。 これで、パタヌンマッチングを適甚し、必芁なものをすべお「到着」むベントから分離できたす。 この堎合、Sleepingずいう名前の状態でWakeUpコマンドを受け取り、デヌタがData.Emptyであるこずを確認したす。 そしお、私たちはこのビネグレット党䜓に反応しお、新しい状態、぀たり目芚めぞず移行したす。 動䜜を蚘述するこのアプロヌチにより、状態名ず珟圚のデヌタを組み合わせるためのさたざたなオプションを凊理できたす。 ぀たり、同じ状態で芋぀けるために、珟圚のデヌタに応じお同じメッセヌゞに異なる反応をするこずができたす。



ここで、状態間の遷移関数であるgotoずstayの機胜に泚目したいず思いたす。 単独では、これらは副䜜甚のない「玔粋な」関数です。 これは、圌らの呌び出しの事実が珟圚の状態の倉化に぀ながらないこずを意味したす。 これらは、必芁な状態倀gotoの堎合はナヌザヌが指定し、stayの堎合はcurrentで指定されたのみを返し、FSMが理解できる型に倉換されたす。 倉曎が発生するには、動䜜関数から倉曎を返す必芁がありたす。



私たちはそれを理解したした。 ここでテストを実行したす-ただし、再び倱敗したす。次の状態のAwakeは存圚したせん。 次の状態が宣蚀されおいない堎合、遷移が発生せず、マシンが同じ状態のたたである堎合、次の状態が宣蚀された堎合に䜕が起こるかを意図的に瀺したいず思いたした。 開始状態で発生した䟋倖もスロヌされたせん。 倚くの堎合、開発の䞀環ずしお、私はこれを忘れお、移行が発生せず、テストがクラッシュする理由を理解するために時間をかけたした。 ログ内の非自明なテストの「次の状態の目芚めは存圚したせん」ずいうメッセヌゞは、ずりわけ気付くのが簡単ではありたせん。 しかし、時間が経぀に぀れお、この機胜に慣れ始めたす。



したがっお、次の状態ずしおnull関数を宣蚀するず、テストが緑色に倉わりたす。



  when(State.Awake)(FSM.NullFunction)
      
      







「猫をなでお」、たたは揺るぎないたたむベントに察応する方法



さお、今、あなたは圌が目芚めたずいう事実を利甚しお、子猫をstrokeでるこずができたす。 私はそれずどこに远加するこずを願っおいたす-あなたはただそれを理解したしたか



チヌム

  case object Stroke
      
      





テスト

 "should purr on stroke" in { val kote = TestFSMRef(new Kote) kote ! Commands.WakeUp kote ! Commands.Stroke expectMsg("purrr") kote.stateName should be (State.Awake) }
      
      





コテ

 when(State.Awake) { case Event(Commands.Stroke, Data.Empty) => sender() ! "purrr" stay() }
      
      







同じこずをより簡朔に曞くこずができたす

 when(State.Awake) { case Event(Commands.Stroke, Data.Empty) => stay() replying "purrr" }
      
      





「猫を二床起こさないでください」、たたは繰り返しずに繰り返しずにテストする方法



やめろ さお、テストで猫をpetでるために、私たちは最初に圌を起こしおから、圌をpetでたすか 優れた、぀たり、テストされた状態にただ10-15の䞭間のものがある堎合そしお100-150の堎合、正しいものに入るために、単䞀の゚ラヌを犯さずにすべおを正しく実行する必芁がありたすか ただ間違いであり、私たちが考えおいる堎所にいない堎合はどうなりたすか たたは、時間の経過ずずもに䞭間状態間の遷移に䜕か倉化がありたしたか この堎合、TestFSMRefを䜿甚するず、すべおの䞭間手順を実行するこずなく、setState関数を䜿甚しお必芁な状態ずデヌタを保蚌できたす。 それでは、テストを倉曎したしょう。



 "should purr on stroke" in { val kote = TestFSMRef(new Kote) kote.setState(State.Awake, Data.Empty) kote ! Commands.Stroke expectMsg("purrr") kote.stateName should be (State.Awake) }
      
      





さお、いく぀かの異なる刺激物に察しお同じ状態をテストするために、私は個人的に重耇コヌドを取り陀くこの方法を発明したした



 class TestedKote { val kote = TestFSMRef(new Kote) }
      
      





そしお今、私はすべおのテストを安党に眮き換えるこずができたす



 "should sleep at birth" in new TestedKote { kote.stateName should be (State.Sleeping) kote.stateData should be (Data.Empty) } "should wake up on command" in new TestedKote { kote ! Commands.WakeUp kote.stateName should be (State.Awake) } "should purr on stroke" in new TestedKote { kote.setState(State.Awake, Data.Empty) kote ! Commands.Stroke expectMsg("purrr") kote.stateName should be (State.Awake) }
      
      





同じ非開始状態を数回テストするこずに関しお、私は次の簡単なトリックを思い぀きたした



 "while in Awake state" - { trait AwakeKoteState extends TestedKote { kote.setState(State.Awake, Data.Empty) } "should purr on stroke" in new AwakeKoteState { kote ! Commands.Stroke expectMsg("purrr") kote.stateName should be(State.Awake) } }
      
      





ご芧のずおり、すべおの「芚醒」テスト甚に「芚醒状態にある」ずいうサブタむトルのフレヌムを䜜成し、その䞭に特性AwakeKoteStateを配眮したすポむントではなくクラス化するこずもできたす。 この状態のすべおのテストは、その助けを借りお宣蚀したす。



「もっず呜を吹き蟌む」、぀たり意味のあるデヌタを州に远加する方法



私たちの猫が䜕が欠けおいるのか考えおくださいたあ、私は-その空腹感。私は猫を飌っおいたした、私は私が話しおいるこずを知っおいたす圌らは垞に食べたいです圌らが眠らないならもちろんですそしお、倢の䞭で、あなたは、圌らが愛するグラブず玠晎らしく健康的なボりルを芋るのを芋たすしかし、猫をどこに飢えさせるのでしょうか圌の生きおいる芪relativeの正確な䜍眮はわかりたせんが、私たちのマシンでは、州の名前に加えお、これらの目的のために、珟圚空のデヌタがありたす。私は少し考えるこずを提案したす。出生時、普通の猫はすぐにおっぱいを探したす。だから、圌はすでに少し空腹に生たれおいたす。そしお、あなたが圌を逊うならば、圌は満腹になりたす、すなわち、空腹になりたす。圌が眠り、走り、さらには食べさえすれば、垞に空腹感/満腹感がありたす。぀たり、自分で想像できるどの状態でも、デヌタを空にするこずはできたせん。それは぀たり他のデヌタを発衚し、これらを捚おお忘れるずきです。圌らの時間は過ぎ、進化はそのように決定したした、そしお私たちは圌らのために悲しむこずはありたせん。したがっお、飢レベルを倉数hungerIntで衚し、レベル100は子猫の飢dies死を意味し、レベル0以䞋は過食を意味したすこれは家族が飢excessiveの過剰なレベルず呌んでいるものです。そしお、圌は、䟋えば60のレベルで生たれたす-぀たり、すでに少し空腹ですが、ただ耐えられたす。ケヌスクラスVitalSignsに新しい倉数を抌し蟌み、ケヌスオブゞェクトEmptyを削陀したす。 Dataオブゞェクトにデヌタの説明を保存し続けたす。レベル0以䞋-過食から私たちの家族が飢levelの欠劂の過床のレベルを呌び出すように。そしお、圌は、䟋えば60のレベルで生たれたす-぀たり、すでに少し空腹ですが、ただ耐えられたす。ケヌスクラスVitalSignsに新しい倉数を抌し蟌み、ケヌスオブゞェクトEmptyを削陀したす。 Dataオブゞェクトにデヌタの説明を保存し続けたす。レベル0以䞋-過食から私たちの家族が飢levelの欠劂の過床のレベルを呌び出すように。そしお、圌は、䟋えば60のレベルで生たれたす-぀たり、すでに少し空腹ですが、ただ耐えられたす。ケヌスクラスVitalSignsに新しい倉数を抌し蟌み、ケヌスオブゞェクトEmptyを削陀したす。 Dataオブゞェクトにデヌタの説明を保存し続けたす。だから



 ... object Data { case class VitalSigns(hunger: Int) extends Data } ...
      
      





圓然、プロゞェクト党䜓で、Data.EmptyをData.VitalSignsに倉曎する必芁がありたす。startWith行から開始



  startWith(State.Sleeping, Data.VitalSigns(hunger = 60))
      
      





実際、既に説明した状態での子猫の既存の動䜜では、私たちもちろん圌はその重芁なむンゞケヌタヌを気にしないので、ここでData.EmptyをVitalSignsではなくアンダヌスコアで安党に眮き換えるこずができたす。



 when(State.Sleeping) { case Event(Commands.WakeUp, _) => goto(State.Awake) } when(State.Awake) { case Event(Commands.Stroke, _) => stay() replying "purrr" }
      
      





今、私たちの子猫はさらに進化しおおり、その行動を耇雑にし、十分にうんざりしおいる堎合にのみwhenでるずきに鳎り響きたす。



 when(State.Awake) { case Event(Commands.Stroke, Data.VitalSigns(hunger)) if hunger < 30 => stay() replying "purrr" case Event(Commands.Stroke, Data.VitalSigns(hunger)) => stay() replying "miaw!!11" }
      
      







そしおテスト



 "while in Awake state" - { trait AwakeKoteState extends TestedKote { def initialHunger: Int kote.setState(State.Awake, Data.VitalSigns(initialHunger)) } trait FullUp { def initialHunger: Int = 15 } trait Hungry { def initialHunger: Int = 75 } "should purr on stroke if not hungry" in new AwakeKoteState with FullUp { kote ! Commands.Stroke expectMsg("purrr") kote.stateName should be(State.Awake) } "should miaw on stroke if hungry" in new AwakeKoteState with Hungry { kote ! Commands.Stroke expectMsg("miaw!!11") kote.stateName should be(State.Awake) } }
      
      





「動物は飢えおいる」、たたはむベントの蚈画方法



子猫は時間の経過ずずもに空腹のレベルを「獲埗」する必芁がありたす䜕ですか残酷 これが人生です



メッセヌゞ

  case class GrowHungry(by: Int)
      
      





コテ

 class Kote extends FSM[Kote.State, Kote.Data] { import Kote._ import context.dispatcher startWith(State.Sleeping, Data.VitalSigns(hunger = 60)) val hungerControl = context.system.scheduler.schedule(5.minutes, 5.minutes, self, Commands.GrowHungry(3)) override def postStop(): Unit = { hungerControl.cancel() } ...
      
      





«» , «» ( +3 5 ) , . hungerControl Cancellable, postStop, , dispatcher , , , , . : implicit ExecutionContext, import context.dispatcher.



« !», ,



蚘事を匕きずらないために、私はすぐに空腹からの猫の死空腹> = 100ず、特に空腹状態空腹> 85ぞの移行を実感したす。アッカは圌女のプランナヌをテストし、メッセヌゞは時間通りに到着するず信じ、猫がそれにどのように反応するかを曞きたす。猫が眠っおいる、目を芚たしおいる、食べ物を芁求しおいる、食べおいる、マりスで遊んでいるなど、すべおの条件で「自然な脂肪燃焌」が発生するこずに泚意しおください。この堎合の察凊方法すべおの可胜な状態に察しお同じ動䜜を説明したすかテストず䞀緒にそしお、ある時点でテストを曞くのを忘れお、そのようなボヌルを芋぀けた猫が1぀の状態でフリヌズしお氞遠の満腹を楜しんだらはい、圌ず䞀緒に地獄に行き、圌に楜しんでもらい、気にしたせんしかし、結局のずころ、このヒゲ野郎は、叀い習慣に埓っお、定期的に食事をし、脂肪は燃えたせん-そしお結局、圌は䜓内の食物の過剰摂取で死ぬでしょうこの堎合、FSMは、子猫を心から思いやり、whenUnhandled関数を䜿甚するこずを提案したす。この関数は、珟圚の状態の動䜜のオプションのいずれずも䞀臎しないメッセヌゞの任意の状態で機胜したす。芚えおおいお、私はそれを曞いたFSM.NullFunctionは{_ =>}ずは異なりたすか今、あなたは正確に䜕を掚枬しおいるず思いたす。最初のケヌスでアクタヌに届いたメッセヌゞを凊理せず、それらすべおがwhenUnhandled関数に該圓する堎合、2番目のケヌスでは状況は逆です。すべおのメッセヌゞはビヘむビアヌ関数によっお単に「吞収」され、whenUnhandledに到達したせん。



 whenUnhandled { case Event(Commands.GrowHungry(by), Data.VitalSigns(hunger)) => val newHunger = hunger + by if (newHunger < 85) stay() using Data.VitalSigns(newHunger) else if (newHunger < 100) goto(State.VeryHungry) using Data.VitalSigns(newHunger) else throw new RuntimeException("They killed the kitty! Bastards!") }
      
      





ここでは、別の関数の出珟を芋るこずができたす-䜿甚しおいたす。コンテキストから、移行䞭に特定のデヌタを状態にバむンドでき、stayずgotoの䞡方で䜿甚できるこずは明らかです。぀たり、usingを䜿甚するず、珟圚の状態のたたで新しいデヌタを䜿甚したり、新しいデヌタを䜿甚しお新しい状態に移動したりできたす。コヌドの以前のすべおのバヌゞョンのようにusingが指定されおいない堎合、デヌタは倉曎されず同じたたです。



「生呜を䞎える病理孊」、たたは䟋倖的な状況をテストする方法



時間ずスペヌスを節玄するために、すべおのテストに぀いおは説明したせん。これらは以前のものず倧差ありたせん。興味深いこずに、スロヌされた䟋倖をテストする方法に蚀及する必芁があるず思いたす。実際、FSMの堎合、これは通垞のアクタヌに適甚可胜な方法ず違いはありたせん。



 "should die of hunger" in new AwakeKoteState with Hungry { intercept[RuntimeException] { kote.receive(Commands.GrowHungry(1000)) // headshot } }
      
      





メッセヌゞを送信する代わりにテストではなく、この堎合ナヌザヌガヌディアンであるスヌパヌバむザヌアクタヌに受信を配信し、テストは単にパスしたせん、アクタヌのreceiveメ゜ッド呌び出しを盎接䜿甚したす。これは、特定の状況でマシンが正しい䟋倖をスロヌするこずを確認するために必芁です。確かに、私たちの貧しい動物が党胜の監督者を蘇生させるかどうかは、これに䟝存し続け、スロヌされた䟋倖に察する圌の戊略で適切なマヌクを芋぀けたした。猫の死の原因ずしお䟋倖を䜿甚したのは、このテストを実蚌するためでした。たたは、単にstopを返すこずもできたす-しかし、通垞の猫は空腹ではなく老霢で死にたす。



「すでに呌吞を止めお、眠そう」、たたはある状態での滞圚時間を制限する方法



私たち自身が眠りに萜ちないように、バむパスしたくない最埌の機胜に぀いおお話したす。これは、状態の最倧タむムアりトを蚭定する機胜です。単玔に蚭定されたすwhen関数の小数点の埌に、状態の名前の盎埌に瀺されたす。たずえば、3時間の睡眠の埌、猫は目を芚たすので、目を芚たす必芁はありたせん。



 when(State.Sleeping, 3.hours) { case Event(Commands.WakeUp, _) => goto(State.Awake) }
      
      





どう思いたすか、起きおいや。それ自䜓では、タむムアりトは状態もデヌタも倉曎したせん。これは猫自身、圌自身の意思決定によっおのみ行うこずができたすたあ、ネオ、しかし圌はもう戻らないず聞きたした;そしお叀いチャックはもはやケヌキではありたせん。そしお、単䞀の堎所で-行動の機胜でこれはネオには圓おはたりたせん、圌は若い頃のチャックのようにどこでもできたす。しかし、今では、指定された期間の埌、誰も猫を起こさない堎合、StateTimeoutメッセヌゞを受け取りたす。そしお、それにどのように反応するかは、圌が決めるこずです。



 when(State.Sleeping, 3.hours) { case Event(Commands.WakeUp | StateTimeout, _) => goto(State.Awake) }
      
      





今、圌は2぀の理由から目芚めるこずができたす圌が十分に長く眠り、眠った堎合、たたは圌が力で起こされた堎合。これらの2぀のむベントを分離しお、異なる反応をするこずができたす。1぀はアクティブで遊び心があり、もう1぀は邪悪な悪臭を攟ち、絶えず鳎き声を䞊げお、みんなを動揺させたす。いずれにせよ、猫が倢の状態を抜けるず、タむムアりトは自動的にキャンセルされ愚かなスケゞュヌラヌはそれ自䜓でキャンセルする必芁がありたす、超自然的なこずは䜕も起こりたせん。ちなみに、猫がタむムアりトを受信したが、スリヌプ状態を続けた堎合滞圚を返す、予想どおり3時間埌に再び猫が受信したす。぀たり、明瀺的にキャンセルたたは再割り圓おされるこずなくstayを䜿甚したす。ForMax20.hours、さらに、動䜜機胜に捕捉され、stay応答を䌎うタむムアりト䞀定の時間が経過するず、再び「シュヌト」したす。



when関数で状態タむムアりトを指定できるずいう事実に加えおそしお、この状態に入るたびに動䜜したす、goto関数に枡すずきに盎接指定するこずもできたすし、前述のforMax関数たずえばstay。forMax1.minuteたたはgotoState.Sleeping.usingData.Something.forMax1.minute、このようなタむムアりトはこの特定の遷移でのみ機胜したす倀を眮き換えたすそこに瀺されおいる堎合



 when(State.Sleeping, 3.hours) { case Event(Commands.WakeUp, _) => goto(State.Awake).forMax(3.hours) case Event(StateTimeout, _) => goto(State.Awake).forMax(5.hours) }
      
      





今、私たちの猫は力で目芚め、3時間目を芚たし続け、通垞は5時間眠りたす。もちろん、Awake状態でStateTimeoutむベントを凊理するこずを条件ずしたす。



「䜕圌はいびきをかきたすか!!」、たたは状態間の遷移䞭に自動アクションを蚭定する方法



そしお最埌に、最埌に、私は玄束したす。Akka FSMには、onTransitionメ゜ッドずいう別の䟿利な機胜がありたす。状態から状態ぞの遷移䞭にいく぀かのアクションを蚭定できたす。次のように䜿甚したす。



 onTransition { case State.Sleeping -> State.Awake => log.warning("Meow!") case _ -> State.Sleeping => log.info("Zzzzz...") }
      
      





すべおが明らかなように思えたすが、念のために説明したす。睡眠状態から芚醒状態に移行する瞬間に、子猫が特別な方法で䞀床だけ鳎きたす。任意の状態から睡眠状態に移行するず、いびきが1぀だけ発せられたす英語では、これがどのように聞こえたす。



これらのアクションは、FSMActorRef.setState関数を䜿甚しおテストで状態を蚭定した堎合でも機胜したすもちろん、珟圚の状態からタヌゲット状態ぞの遷移がonTransitionで説明されおいるもののいずれかに䞀臎する堎合。したがっお、それらをテストできたす。さお、ここでgoto関数を䜿甚しおも意味がないこずに泚意しおください。これは、状態遷移䞭に頑固にデヌタを倉曎しようずしたこずがあり、長い間デヌタが機胜しない理由を理解できなかった人ずしおあなたに䌝えたす。私が発芋した別のニュアンス遷移トリガヌは、動䜜関数からstayを返すこずで珟圚の状態にずどたる堎合でも機胜したす。これはAkkaの将来のバヌゞョンで修正されるず玄束されおいたしたが、珟時点では、Sleeping状態から䜕かに反応したずきにstayを返すず、onTransitionが機胜し、子猫がいびきをかくこずになりたす。



終わり



今日はこれだけに぀いお話したかったのです。そしお、独立した研究のタスクずしお、質問に答えるこずを提案したす。同じ状態名に察しおwhen関数が連続しお数回呌び出されるずどうなりたすかたたは、連続しおwhenUnhandled関数を数回呌び出したす。ご枅聎ありがずうございたした。



PSこの蚘事を曞いおいる過皋で、生きおいおも死んでもいないネコは䞀人もいなかった。



All Articles