Page ObjectじゃなくてApp Actionsを使おうというCypressのブログ記事

を読んだ。僕はGebでPage Objectを作るのが結構好きなので、このブログ記事の主張の理由を知りたくて読んだ。そして納得した。

www.cypress.io

## Cypress

Cypressについては今朝書いた。

bufferings.hatenablog.com

CypressでもPage Objectを使えはするんだけど、Page ObjectよりもApp Actions(自分たちが名付けた手法)の方が良いよ!という主張。

## Page Object Pattern

Page Object Patternについては、この辺か

martinfowler.com

この辺かな

https://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern

## 言ってること

Page Objectの問題は

  • メンテナンスが大変
  • テスト対象も状態を持っているのに、別の状態を持ち込んでしまう
  • 条件分岐をテストに持ち込んでしまう(これはCypressの主張的にはアンチパターン
  • UI経由で操作をするので遅い

ということで、Cypressは Application Actions という手法を提案

  • アプリケーションの状態を変更するのにUIを経由せずに、JSの関数を作ってそれを使うようにする

そうすることで

  • メンテナンスするPage Objectレイヤーがなくなる
  • 条件分岐がなくなる
  • UIを経由しないので操作が速い

となるよと。

## 感想

  • CypressがターゲットにしてるのはSPAだなと再確認。メリットやデメリットの話がSPA前提になっている。
  • SPAの場合、Page Objectを使わない方が良いというのは分かる。Pageとリクエストが一対一に対応していない場合が多いだろうから。
  • SPAは状態をクライアント側で持つので、CypressがJSを直接操作できるという強みが活かせる
  • テスト対象の状態を用意するために、UIを操作して状態を更新するのではなく、直接JSを操作して状態を用意することができるので速い

Cypressならではの強みを活かした主張だなぁという感じ。で、Cypressを使う場合にはそれに従っておいたら良いのかなと思った。ただ、実際に業務で使ったことがあるわけじゃないので、今は頭の中だけの話。

一方でこういうところに気をつける必要がありそう

  • テストとテスト対象が強く結びついてしまう。テストのためだけに状態を更新できるようにしないといけないかもしれない。
  • end-to-endテストなのに、仕様じゃなくて実装のことを理解して作らないといけない。
  • UIからの操作ではたどり着かない状況を裏側で作りだして、そこに対してテストをしてしまう、といった意味のないテストをしてしまうかもしれない

これも頭の中だけの話。

## 結局Page Objectはどうなの?

で、結局Page Objectはどうなの?というところだけど

  • SPAじゃない場合
    • Page Objectを使って良いと思う。十分メリットがある。
    • CypressでもPage Objectは使えるけど、Cypressを使うならCypress流の方(Page Objectを使わない方)が良いかなとは思う。
  • SPAの場合は
    • Cypressを使う場合は、Page Objectは使わずに、内部状態を直接更新する関数を使う方が良さそうに思った
    • Seleniumを使う場合も、Page Objectじゃなくて良さそうかな。状態をUI経由で更新する関数を用意したら良いのかな。

面白かったー。

Cypressという癖のあるフロントエンドテストツールが面白そう

f:id:bufferings:20190713081853p:plain

## Cypress

最近、Cypressというフロントエンドテストツールを触ってみてる。ブラウザを操作して色々できる。end-to-endのテストがメインではあるかな。

www.cypress.io

コードはこんな感じ ( Writing Your First Test | Cypress Documentation より) 。読みやすい。

describe('My First Test', function() {
  it('Gets, types and asserts', function() {
    cy.visit('https://example.cypress.io')

    cy.contains('type').click()

    // Should be on a new URL which includes '/commands/actions'
    cy.url().should('include', '/commands/actions')

    // Get an input, type into it and verify that the value has been updated
    cy.get('.action-email')
      .type('fake@email.com')
      .should('have.value', 'fake@email.com')
  })
})

これだけで、waitやリトライやタイムアウトも組み込まれてる。

## 仕組み

CypressはSeleniumを使ってない。Seleniumと違ってリモートAPIで操作するのではなくて、直接JavaScriptでDOMを操作してる。面白い。

実際は、iframeで対象ページを読み込んで操作してる。なので same-origin policy を乗り越えるために裏側でごにょっとしてる。詳しくは Web Security | Cypress Documentation に書かれてる。面白い。

## 利点

利点としては

  • リモート操作じゃないから速い
  • UI経由で操作しなくても直接JavaScriptでモデルを触ったりできる

といったものや、他にも

  • wait・リトライ・タイムアウトが組み込まれてるのでテストを書くことに集中できる
  • All-In-Oneになってるからワンコマンドで環境準備ができる
  • DOMのスナップショット機能があるからテストのステップごとにどういう状況だったかを後で確認することができる
  • デバッグができる
  • APIのMockや、ObjectのSpyやStubを簡単に作ることができる

というところがある。便利。内部的には mocha, chai, sinon といったフレームワーク・ライブラリーを使ってる。

## 制約

その分制約も色々あるから注意しておきたい。

  • 1つのテストケースの中では1つのスーパードメインしか対応してない
  • 複数タブや複数ブラウザのテストはできない
  • 今の所Chromeしかサポートされてない

## 有償サービス:Cypress Dashboard

Cypress自体はOSSで無償で利用できる。んで、Cypress Dashboardというサービスが、有償の機能として提供されている。これ、便利そう。

Cypress自体にテストの録画や、失敗したときのスクリーンショットを撮る機能があるんだけど、このCypress Dashboardはテストの結果やその動画などの過去ログを管理してくれる。「このテストが失敗した」ってときに動画を見たりどの処理でエラーになったのかが分かるのは便利そう。

www.cypress.io

個人で使う分には無料のSeedプランで大丈夫かな。OSS用の無料プランもある。

https://www.cypress.io/pricing

## こだわり

公式ドキュメントを一通り読んでみたんだけど、end-to-endのテストに対するこだわりがしっかりあって、それを実現するために色々な制約を受け入れて割り切っている、という感じ。良い。

## 自分の感想

スピードが速いのは素晴らしいんだけど、それだけだとSeleniumじゃなくてCypressを使う強い動機には、僕はならないなと思った。Seleniumだと複数ブラウザに対応してるし、ユーザー操作を再現できるし、あんまりトリックがないし。安心。

だけど、SPAとかみたいにクライアント側で色々やるアプリケーションだと、その裏側をJSで直接簡単に操作したり、DOMのイベントを直接Listenしたりできるのは便利そう。なので、Cypressを使うならそういうところかなと思った。

ということで、LTで、元々はGebの話でもしようかと思ってたんだけど、Cypressの紹介をすることにした。(๑•̀ㅂ•́)و✧

seleniumjp.connpass.com

お花が綺麗で嬉しかったのかな?

想像をしないで聞くのって、難しい。

娘が「お花が綺麗だったんだよ」って言うのを聞いて(あぁ、綺麗なお花を見て、嬉しい気持ちになったんだな)って思ったけど、実は「それで、悲しくなった」と考えているのかもしれない。

自分の想像は、そんなふうにして、簡単に入りこんでしまう。だから、「事実」を聞いたときには、そこから相手の気持ちを想像するんじゃなくて、できるだけ、相手の口から気持ちを説明してもらうようにしたいなと思っている。

「お花が綺麗だったんだね。それで、どういう気持ちになったの?」「この前、枯れちゃったお花のことを思い出して、悲しくなったの」「そっか。悲しい気持ちになったんだね」

娘だと、実際にはあんまり気にしなくても大丈夫なんだけど、大人の場合は、あんまり表に出てこないし、相手も気を遣っているので、何枚か奥に気持ちが隠れてたりする。

気持ちを言葉で直接聞いたとしても、結局、それが気持ちを素直に表してるかというと、分かんないけど、自分の想像を紛れ込ませてしまうよりは、相手の言葉を受け止める方が、良いかなと思ってる。

自分用メモ:遊び用 GKE + Ingress + SSL

ほんとうにただの自分用のメモだよ。何となく個人用にGKEのクラスターを持っといてもいいかなと思ったので作ることにした。ドメイン代のけて、月3000円くらいで済むのかな?GKEクラスターが1000円弱、ロードバランサーが2000円ぐらい?Javaのアプリを動かすにはちょっとメモリが少ないかなぁという気もするけど。とりあえず満足。

## GKEクラスターを作る

えいのうさんのを読みながら作った。

blog.a-know.me

Google Cloud SDKを最初はapt-getで入れてみたんだけど、やっぱりダウンロード版にしとこと思ったから、入れ直した。

## ドメイン

そういえば、Google Domainsにdevドメイン持ってたなと思い出したので、Ingressを作ってマッピングすることにしてみた。

cloud.google.com

DNSのAレコードを登録してIPアドレスが引けるようになったから、アクセスしてみようと思ったら、devドメインってSSLじゃないと使えないんだった。あぁ。

## 証明書

Let's Encryptを使うんかなぁと思ってたらGoogle-managed SSL certificatesってのがあるっぽいから、一旦さっき作ったIngressをdeleteして、やってみた。裏側はLet's Encryptを使ってて、それをGoogleが管理してくれる仕組みみたいね。便利。

cloud.google.com

10分ぐらい待ったら、証明書のステータスがFailedNotVisibleになって、えーってなってたらDNSSECをOnにしないといけないみたいなのを見つけたから、Google DomainsからOnにして、もっかい作り直したらいけたや。

❯ curl https://gke.shiiba.dev/
Hello, world!
Version: 1.0.0
Hostname: helloweb-76bc56cf57-56d2f

(∩´∀`)∩ワーイ

## リソース

gist.github.com

伝えるふりかえり、聞くふりかえり

お。久しぶりの日記だ。

## 毎日のふりかえり

最近サポートしてる人と、一日の終わりに毎日2人でふりかえりをすることにした。話を聞こうと思って。そのふりかえりをしながら、そういえば、ふりかえりのときってその対象がチームでも個人でも「伝えることを中心にするとき」と「聞くことを中心にするとき」とがあるなぁってふと思った。

## 伝えるふりかえり

何か気づきが得られたら良さそうだなって思うときは、伝えるふりかえりになることが多い。

相手やチームから出てきた意見を元にして「あなたたちにはこんなに良い部分がありますね」とか「実はこれだけお互いに感謝してることが分かりましたね。じゃ、それを共有する場をもっと持ったら良さそうですね」とか「自分たちはまだまだだって言ってますけど、先月に比べてこんなに良くなってますね」って伝えたり。

課題の場合も、意見を元に少し深堀りして「その課題の本当の原因は、ここにありそうですね」とか「その課題に対しては、こういうやり方がありますけど試してみますか?」って伝えたり。

次にどういう風に行動すれば、その人たちに何か新しいものが見えてくるかを、伝えるような感じかなぁ。

## 聞くふりかえり

それに対して、相手やチームの中に答えを探しにいくときは、聞くふりかえりになることが多いかな。

ひとつずつ、相手の言葉をゆっくり待って、そこに対して問いかけて、相手の心の中を一緒に探検する感じ。「そっか、そう思ったんだね。じゃあ、そう思ったのはどうしてかな?」「なるほど、それは良かったね」とか。「そのとき取った行動は良かったね」とか。「じゃあ、次はどうしようと思ってるとかあるの?」とか。このときは、解決策を探そうとするより、気持ちに寄り添って、一緒にうろうろしてみたいな。という気持ち。

意識してるのは事実を否定しないこと。「そういう気持ちになった」という事実や、「そういう行動をとった」という事実を否定しないこと。ただ、これ、自分でかなり意識してそういうモードにしてないと「自分だったらこう考える」って言ってしまってダメ。気をつけたい。

## いずれにしても

伝えるふりかえりも、聞くふりかえりも、相手の言葉を聞くことから始まるので、聞くことが一番大切よなー。と思いながら、娘のとりとめもない話を「うんーうんー」って言いながら聞いてる。

MicronautでPetClinicを実装してNative Image化したら300msくらいで起動したーはやいー

Spring PetClinic をSpringの代わりに Micronaut を使って実装してみた。

github.com

PetClinicなので単純なHelloWorldじゃなくて、ThymeleafとかJPA(Hibernate)とかDIとかを使ってるんだけど、GraalVM のNative Imageでビルドしたら200-400msくらいで起動する。はやいー。

この画像だと324msで起動してる:

Screen Capture

全部実装したわけじゃなくて結構まだぐちゃぐちゃしてるけど、基本的な機能は動くようにしておいた。

## 使い方

Native Imageじゃない場合から

### 1. git cloneして

git clone https://github.com/bufferings/micronaut-petclinic.git
cd micronaut-petclinic

### 2. PostgreSQLを起動して

このPetClinicはPostgreSQLを使う。Docker Composeでデータも用意しておいたのでこれでデータ入りのPostgreSQLが立ち上がる:

docker-compose up

### 3. アプリを実行

./mvnw compile exec:exec

### 3-2. JARを使いたかったらこう

./mvnw package
java -jar target/micronaut-*.jar

### 4. PetClinicにアクセスする

http://localhost:8080/

JARファイルだと僕のノートPCだとだいたい5秒くらいで起動するかな。

## Native Image化

じゃ、Native Imageを作ろう

MicronautがGraalVMのNative Imageのビルドを色々サポートしてくれてるので、このPetClinicもNative Imageとしてビルドして実行できるようにしといた。

./mvnw package && docker build -t micronaut-petclinic .

10分くらいかかるから ☕ 飲みながら待ってる。メモリも4GBくらい使うから、OOMで落ちるときはDockerに対するメモリの割り当て見てみてね。

で、こんな感じで起動する:

# Docker for Mac or Windows
export HOST_NAME=host.docker.internal
# Linux
export HOST_NAME=172.17.0.1

docker run --rm -p 8080:8080 -e JDBC_URL=jdbc:postgresql://${HOST_NAME}:5432/petclinic micronaut-petclinic

300msぐらいで起動するよー (๑•̀ㅂ•́)و✧

まだまだGraalVMもMicronautもこれからって感じだけど、楽しみだなー!

肯定感を持って成長していって欲しい

娘達を見てて、「まだだ!まだ足りない!掛け算はできたけど、割り算はまだ知らないだろう?」って言って育てるよりも「すごい!できた!天才!だが、僕らには大きな目標がある!だから次は割り算やろう!」って言って育てたいと思っている。

何かができなくても「どういうことだ!これ、もう習っただろう?」とか言わずに「ほう・・・今回の相手は手強かったな!仕方がない。次は倒せるようにしておこう!」って言って育てたいと思っている。

だから、それと同じことを、チームと接するときにもやりたいなと思う。できたことをほめる。目的地を共有する。次に進む一歩を一緒に考える。これだけのことなんだけど、娘達に対しても、チームに対しても、そう接するのは、なんか難しい。もっと褒めたい。

あ、ビルド終わった。じゃ!