くりにっき

フルスタックキュアエンジニアです

今年作ったもの(2020年版)

今年新しく作ったものを作った順に紹介

go-mod-tidy-pr

sue445.hatenablog.com

今年の頭に作ったやつでたまにPRきてる。

最近GitHub本体のdependabotの方で go mod tidy 正式対応されたので go-mod-tidy-prはお役御免かなと思ってdeprecateにしました

github.blog

prismdb-ruby, faker-pretty_series

下記エントリ参照

sue445.hatenablog.com

gcp-kmsenv

下記エントリ参照

sue445.hatenablog.com

gcp-secretmanagerenv

下記エントリ参照

sue445.hatenablog.com

プリマップ

個人的には今年のイチオシ

sue445.hatenablog.com

sue445.hatenablog.com

今まで使ったことのないアーキテクチャで作りつつも、割と爆速で動くというのがウリ。

しかもGCPの費用が1ヶ月で1ドル切ってるのでお財布的にも嬉しい。(リリース直後にプチバズってアクセス増えたけどそれでも1ドルちょっと超えたくらい)

f:id:sue445:20201222205635p:plain

Cloud Functions実行時に毎回Secret Managerから機微情報を読み込んでるのでそこをGoのビルド時にいい感じに埋め込めばほぼ0円にできそうだが*1、今のままでも十分安いのでそこまでのモチベーションはないw

*1:FirestoreのQuota超えることもあるので完全に0円は無理

今年買ってよかったもの(2020年版)

Amazon楽天などの購入履歴からピックアップ

エクスジェル アウルカンフィ3Dプレミアム

exgel.jp

家だと座椅子なのですが座椅子で仕事しててクッションに不満があった時に同僚に勧められて購入。

長時間座ってても尻が痛くならないのが良い

Oculus Rift S

www.oculus.com

ハッピーおしゃれタイム をやりたくて購入。*1

けどGoogle Earth VRで昔住んでた場所にVR帰省したり、バーチャルデスクトップでライブのBDや配信イベントの鑑賞してる方が多いかもしれない。

仮想大画面でライブ見るのいいですね。(目がむっちゃ疲れるけど)

プリパラ LIVE COLLECTION

avex.jp

avex.jp

avex.jp

「プリパラミュージックコレクションがあるならライブ版もあるのでは?」ということでググって見つけたので購入。

プリパラやプリ☆チャンのCGライブが好きなのでCGライブだけをずっと見れるのは神。

これをBGM(Back Ground Movie)でコードを書くとテンションMAX!

難点は推し*2のパートで手が完全に止まること。

KPro01

www.owltech.co.jp

Makuake で支援してたのが先月届いたので使ってます。

操作に慣れるまでに時間がかかったけど慣れたらかなり快適。

フル充電で9時間持つので仕事で使うには十分

*1:Quest 2じゃなくてRift Sなのは購入時点でQuest 2が発表されていなかったため

*2:アロマゲドンとガァルマゲドン

細かすぎて伝わりづらいプリマップの改修ポイント #プリッカソン

これは #プリッカソン Advent Calendar 2020 - Adventar の2日目です。

10月の プリマップ 公開以降見た目はほとんど変わってないのですが裏側をちょいちょい改善してるのでメモ

sue445.hatenablog.com

ジオコーディング前に住所をいい感じにしている

公式の店舗リストの住所には「東京都新宿区西新宿1-11-1ゲームホビー館」のように建物名が入ってることが多いのですが、この手の住所をそのままジオコーディング(住所から緯度経度を取得)すると高確率で意図しない緯度経度が返ってくるため「東京都新宿区西新宿1-11-1」のように建物名を削ってやる必要があります。

この手の前処理はリリース前から実装自体はしてたものの、日本の住所表記はバリエーションが豊富なのでリリース以降も気づきベースでちょいちょい修正してました

プロダクトコードだけ見ると正規表現がアレなのでテストコード見た方が分かりやすいと思います。

データ集計処理の高速化

2200件近い店舗情報をFirestoreに一度に保存する関係上GCPのPub/Subのtopic *1 を使ってCloud Functionsで大量に並列処理をしているのですが、topicに積むところが遅かったのでパフォーマンスチューニングしました。

デバッグログ仕込んで各処理の実行時間を計測したら改善できそうな処理が2箇所あったのでそれぞれ修正。

github.com

2200件もあるとPub/Subのtopicに積むだけで遅いのでgoroutineで並列化しました。ここだけで50秒から1~7秒くらいに改善。(実行時間があまり安定しない・・・)

github.com

削除された店舗一覧を調べるために「Firestoreに保存済の店舗一覧 - prismdbで取得した店舗一覧」みたいなことをやるためにGoのslice同士の引き算を行ってるのですが、そこの処理が遅かったのを改修。 ベンチマークレベルだと300倍速くなっていてCloud Functions上でも9秒から1秒前後に改善。

改修前はトータルで1分弱かかってた処理が4~8秒で終わるようになりました。

公式のデータが間違っていたのでタカラトミーアーツさんのサポートに報告して直してもらった

プリマップでは間接的*2に公式の店舗リストを使ってるのですが公式のデータが一部間違ってるせいでプリマップのデータも変なことになってたのでタカラトミーアーツさんに報告して直してもらいました。

件数多かったけど報告したその日のうちに全部直してもらったので不満を持ってる人はどんどん報告すればいいと思います。

余談ですがタカラトミーアーツサポートへの連絡先は2つありました。

faq.takaratomy-arts.co.jp

www.takaratomy-arts.co.jp

1つ目は返信不要なやつで2つ目は返信を受け取りたい時のものかと思われます。僕は修正報告を受けたかったので後者で報告しました。

プリパラとプリ☆チャンで店舗名が微妙に食い違ってるんで名寄せした

github.com

プリマップでは店舗名をFirestoreのコレクションのKeyにしてるのでKeyが食い違うと同一店舗なのにプリパラとプリ☆チャンで1つずつ出てくる状態になって不便です。(こういうのが2200件中50件くらいありました)

明らかに情報が間違ってる場合には公式に直してもらえばいいんですが、今回のケースは完全にこっちの都合なのでプリマップ側で名寄せしてます。

ほとんどの場合「△△△△△(ゲームセンターや量販店の名前) ○○(支店名)店」と「△△△△△ ○○」で表記ゆれしてたので機械的に「店」を削るだけで済んだのですが*3、それでも名寄せできなかったものがあったのでそこだけ名寄せ用のマッピングを作りました *4

今更だけどアイカツ!16話のチケット5万枚を2秒で売り切るシステムについて本気で考察してみた

発端

普通の人は5万枚のチケットを一瞬でさばいた神崎美月さん*1知名度に着目しがちですが、僕は職業柄システムの方が気になりました。

お前誰よ?

会社でインフラを見ています。業務でのインフラ歴は4〜5年くらい。社内だとAWSGCP両方分かるマンとして認知されています。

前置き

ネタなので過度なツッコミは禁止

構成

webサーバ

チケット5万枚を2秒で完売させるためには雑な見積もりでサーバ数百台必要だと仮定します。

これだけの規模をオンプレミスで調達するのは大変なのでAWSGCPなどのクラウドを使うことになると思います。

オートスケール(アクセス数やサーバの負荷に応じて自動でサーバを増減させる)という手もあるのですが、2秒で数百台までスケールさせるのはさすがに厳しいと思います。

参考:自社で運用してるシステムでGKEのオートスケールを導入しているのですがリクエスト数急増でサーバ5台増やすのに10分くらいかかっています。*2

チケット販売開始の日時はあらかじめ決まっているからその日時に合わせてあらかじめサーバをその台数まで増やして待機させておくのがベターでしょう。

これはAWSGCPのどちらにも言えることですが、1つのAWSアカウントやGCPプロジェクトで作れるサーバの台数には上限があり数百台というのは初期状態では作れないのでサポートに問い合わせて上限緩和申請をすることになります。クラウド事業者や希望台数にもよってサポートでの対応完了日数が変わるのでなんとも言えないけど僕ならバッファ込で2〜3営業日くらいは見込みます。

データベース

チケットの在庫やチケットを販売したという情報を持つためにデータベースは必須です。(以降、データベース = RDBを前提に書きます。FirestoreやDynamoDBのようなKVSは知らん)

webサーバは横に並べることで負荷分散できるのですがデータベースは簡単に増やせないしオートスケールもできないのでここがボトルネックになりがちです。

AWSのRDS, AuroraやGCPのCloud SQLのようなフルマネージドなデータベースを使えばスケールアップ・スケールダウンはできるのですが、あくまでも手動だしスペック変更時に基本的に数分間のダウンタイムが発生するのでこれも予めつよつよスペックにしとく必要があります。

僕なら迷わず一番高いスペックを選びます。

決済システム

今日日自前で決済システムを持つことはあまりなく、決済専用の外部サービスを使うことが多いです。

自分のアプリケーション(チケット販売サイト)が秒間2.5万リクエストに耐えられてもその先の決済システムが耐えられないと思います。(2秒間に5万リクエストとかなんて下手なDDoSより量が多いし普通にネットワークで詰まるのでは...)

負荷試験

神崎美月さんといえばアイカツ界の超メジャーアイドル。そのライブチケットとなれば当然負荷も予測されるので負荷試験も必要でしょう。

負荷試験をしないと実際にどれだけのサーバ台数が必要なのか分からないので僕ならやります。

当日の監視

未曾有のアクセスが来るのが分かってるのであれば当日立ち会っての監視も当然やるでしょう。僕なら絶対やります

まとめ

余談

*1:https://dic.pixiv.net/a/%E7%A5%9E%E5%B4%8E%E7%BE%8E%E6%9C%88

*2:GKEだとpodをオートスケールする Horizontal Pod Autoscaler(通称HPA)とpodを収容してるnode(≒サーバ)をオートスケールするクラスタオートスケーラーの2種類のオートスケールがあるのですがここで言ってるのは後者の方です

Goで複数パッケージ構成のプロジェクトでもちゃんとカバレッジ集計する(GitLab編)

tl;dr;

gcov + lcovが無難

Goで複数パッケージ構成の場合何が問題か?

Goでテストを実行する時に -cover をつけるとお手軽にカバレッジが集計できるのですが、1パッケージずつしか出力されないので複数パッケージ構成のプロジェクトの時にプロジェクト全体のカバレッジが集計できずに困ります。*1

$ go test -count=1 -cover ./...

ok      gitlab.com/sue445/tanuki_reminder   0.066s  coverage: 77.2% of statements
ok      gitlab.com/sue445/tanuki_reminder/enum  0.026s  coverage: 100.0% of statements

GitHubの場合

songmu.jp

にもあるように

  1. -coverprofile coverage.out でgcov形式で出力
  2. jandelgado/gcov2lcov-action でlcov形式に変換
  3. lcovファイルをCoverallsに送信

が今の所最適解だと思います。

GitLabの場合

GitHubと違いGitLabはカバレッジ集計に標準対応しているというのがあります。

https://docs.gitlab.com/ee/ci/pipelines/settings.html#test-coverage-parsing

CIのログに出力されたテストカバレッジっぽい文字列を正規表現で抽出するという割と力技な仕組なのですが、各言語のデファクトツールに関してはだいたい正規表現が用意されています。

f:id:sue445:20201108170855p:plain

GitLabのリポジトリ上で正規表現を設定しておくだけで最終的にリポジトリ上でカバレッジの推移のグラフが見れます。 *2

f:id:sue445:20201108171107p:plain

Coverallsに比べればだいぶ貧弱なのですがリポジトリに標準機能でついているという強みはあります。

もしこれで困る場合はCoverallsのような他ツールを使うか*3、lcovで生成したカバレッジレポートをGitLab Pagesにアップロードすればいいと思います。

本題:GitLabで複数パッケージ構成のGoプロジェクトのカバレッジを集計したい

tanuki_reminder だと当初 /coverage: \d+.\d+% of statements/ のような正規表現カバレッジを収集してたのですが、1つのログに複数のカバレッジが出ていると後の方にあるものがそのジョブのカバレッジとして採用されて複数パッケージ構成になった時に変なことになりました。*4

f:id:sue445:20201108172019p:plain

いくつか手法を考えたのですが、lcovのgenhtmlコマンドでHTML形式のカバレッジレポートを作る時にプロジェクト全体のカバレッジがログに出力されることに着目してこいつを利用するようにしました

f:id:sue445:20201108173154p:plain

.gitlab-ci.ymlのサンプル

test:
  image: golang:1.15

  stage: test

  variables:
    GCOV2LCOV_VERSION: v1.0.4

  script:
    - mkdir -p coverage/
    - go test -coverprofile coverage/coverage.out -covermode atomic ./...

    # Install and run gcov2lcov
    - wget https://github.com/jandelgado/gcov2lcov/releases/download/${GCOV2LCOV_VERSION}/gcov2lcov-linux-amd64.tar.gz -q -O - | tar xvzf - --strip 1
    - chmod 755 gcov2lcov-linux-amd64
    - export GOROOT=$(go env GOROOT)
    - ./gcov2lcov-linux-amd64 -infile "coverage/coverage.out" -outfile "coverage/coverage.lcov"

    # Install and run lcov
    - apt-get update
    - apt-get install -y lcov
    - genhtml coverage/coverage.lcov -o coverage/

  # regular expression for genhtml
  coverage: '/lines.*\d+\.\d+%/'

  artifacts:
    paths:
      - coverage/

lcovを使ってるので後半部分はGo以外でも流用できそう

compact_blank gemのメンテを終了した

Rails 6.1.0.rc1がリリースされましたが*1activesupport本体に #compact_blank#compact_blank! のメソッドが入った*2ので6.1.0.rc1以降と一緒に入れられないようにしてメンテ終了宣言しました。

github.com

rubocop v1系 + rubocop-rspec v1系でエラーになる件の回避策

tl;dr;

どっちのケースの場合もrubocop-rspec v2.0.0.preで直ってるので Gemfile に下記のように書けば直る

group :development do
  gem "rubocop-rspec", ">= 2.0.0.pre", require: false
end

ケース1: rubocop-rspec内でエラーになる

エラー内容

-dデバッグ出力)を有効にしないとエラー内容が表示されないので注意

$ bundle exec rubocop -d

(中略)

An error occurred while RSpec/FactoryBot/CreateList cop was inspecting /Users/sue445/workspace/github.com/sue445/app-stat-api/spec/app_spec.rb:1:0.
undefined method `to_sym' for nil:NilClass
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/mixin/configurable_enforced_style.rb:69:in `style'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-rspec-1.42.0/lib/rubocop/cop/rspec/factory_bot/create_list.rb:51:in `on_block'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:100:in `public_send'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:100:in `block (2 levels) in trigger_responding_cops'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:166:in `with_cop_error_handling'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:99:in `block in trigger_responding_cops'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:98:in `each'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:98:in `trigger_responding_cops'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:69:in `on_block'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-ast-1.1.0/lib/rubocop/ast/traversal.rb:20:in `walk'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/commissioner.rb:86:in `investigate'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/team.rb:157:in `investigate_partial'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cop/team.rb:83:in `investigate'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:315:in `inspect_file'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:259:in `block in do_inspection_loop'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:293:in `block in iterate_until_no_changes'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:286:in `loop'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:286:in `iterate_until_no_changes'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:255:in `do_inspection_loop'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:132:in `block in file_offenses'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:157:in `file_offense_cache'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:131:in `file_offenses'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:122:in `process_file'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:101:in `block in each_inspected_file'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:100:in `each'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:100:in `reduce'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:100:in `each_inspected_file'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:86:in `inspect_files'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/runner.rb:47:in `run'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli/command/execute_runner.rb:25:in `execute_runner'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli/command/execute_runner.rb:17:in `run'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli/command.rb:11:in `run'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli/environment.rb:18:in `run'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli.rb:65:in `run_command'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli.rb:72:in `execute_runners'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/lib/rubocop/cli.rb:41:in `run'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/exe/rubocop:13:in `block in <top (required)>'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/benchmark.rb:308:in `realtime'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/gems/rubocop-1.1.0/exe/rubocop:12:in `<top (required)>'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/bin/rubocop:23:in `load'
/Users/sue445/workspace/github.com/sue445/app-stat-api/vendor/bundle/ruby/2.7.0/bin/rubocop:23:in `<top (required)>'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/cli/exec.rb:63:in `load'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/cli/exec.rb:63:in `kernel_load'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/cli/exec.rb:28:in `run'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/cli.rb:476:in `exec'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/vendor/thor/lib/thor.rb:399:in `dispatch'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/cli.rb:30:in `dispatch'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/vendor/thor/lib/thor/base.rb:476:in `start'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/cli.rb:24:in `start'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/libexec/bundle:46:in `block in <top (required)>'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/2.7.0/bundler/friendly_errors.rb:123:in `with_friendly_errors'
/Users/sue445/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/libexec/bundle:34:in `<top (required)>'
/Users/sue445/.rbenv/versions/2.7.2/bin/bundle:23:in `load'
/Users/sue445/.rbenv/versions/2.7.2/bin/bundle:23:in `<main>'

関連するissue

github.com

実際に対応したコミット

https://github.com/sue445/app-stat-api/pull/631/commits/7013ba612d2f1f663e2b8e60816655de6770602e

ケース2: 意図せずrubocop-rspecのバージョンが下がる

https://github.com/sue445/twittodon/pull/441/commits/3abc21db31db2532283af5ef790ea32e62a804cb#diff-89cade48462044ee1b672dc5f4c3ec250fbd29effcd8932096a23c1283c6731fR102

注意点

https://github.com/rubocop-hq/rubocop-rspec/blob/v2.0.0.pre/CHANGELOG.md#200pre-2020-10-22

v2で 下記のようなbreaking changeがあるので注意

(Potentially breaking) Change namespace of several cops (Capybara/* -> RSpec/Capybara/*, FactoryBot/* -> RSpec/FactoryBot/*, Rails/* -> RSpec/Rails/*).