give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

【Ruby】irbとPry、どっちを使うか聞いたらirbの方がちょっと多かった

Q. irbとPry、どっちが人気なの?

僕がメンターをやっているフィヨルドブートキャンプで、「RubyのREPL(対話型インタプリタ)にはirbPryがあるけど、どっちが人気なんですか?また、どっちを使えばいいんですか?」という質問がありました。

僕が勤めているソニックガーデンではirbユーザーの方が多い印象なのですが、社外のエンジニアさんだとPryを使っている人もよく見かけます。
利用率については僕が推測で答えるよりも、アンケートを採ってみた方が確実だろうと思い、Twitterでアンケートしてみました。

結果は以下のようになりました。

ご覧のとおり、irbとPryの利用率はほぼ半々ですが、irbの方がちょっとだけ多い、という結果が得られました。
回答数は524件だったので、それなりに信頼して良い数字になってるんじゃないかなーと思います。(少なくとも日本においては)

Q. irbとPry、どっちを使えばいいの?

どっちを使えばいいのか、という質問は回答が難しいですね。
正直に答えるなら、「個人の好みなのでどっちでも良い」となります。

僕の場合、簡単なコードの動作確認ならirbで機能で十分ですし、詳細な調査が必要になる場合はRubyMine上でデバッグ実行したりするので、irbしか使っていません。

昔はirbの機能がシンプルすぎて「Pryの方が圧倒的に便利じゃん」という風潮もあったように思いますが、最近はirbもいろいろとパワーアップしてきているので、ますます「irbでいいのでは」という気がしています。(Pryをふだん使わないので、どれぐらいの機能差があるのか正確に把握しているわけではありませんが)

特に、Ruby初心者の方は別途gemのインストールが必要になるPryよりも、いつでもどこでもすぐに使えるirbを選んだ方が手っ取り早いんじゃないかなーと思います。

ただ、最初に書いたとおり「どっちでもいい」と思っているので、「Pryを使ってみたい」という初心者さんがいても、それはそれで全然構わないと思います!

まとめ

  • irbとPryの利用者を比べると、irbの方がちょっと多い
  • irbとPry、どっちでも好きな方を使えば良い(僕があえて勧めるならirb)

【Ruby版】xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義)

はじめに

テストダブル(Test Double)について、わかりやすく解説した技術記事はないかな〜と探していたところ、こちらのブログ記事を見つけました。

goyoki.hatenablog.com

とても詳しく解説されていたので、まさに打ってつけだったのですが、ふだん僕はRubyを使っているのでサンプルコードをRubyにしてみたいな〜と思いました。

そこで今回のエントリでは、原著者の id:goyoki さんの許諾をいただいた上で、上記のブログ記事の説明文を維持したまま、サンプルコードだけをRubyに書き直してみました。(goyokiさん、どうもありがとうございます!)
ただし、Ruby版のコードにあわせて説明文を改変した箇所もいくつかあります。

それでは以下がRuby版の「xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義)」です。

(もくじ)

【Ruby版】xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義)

 最近、昔作ったTest Doubleの解説資料を参照・引用してくれる方がちらほら出ていて恐縮しているのですが、見直してみると結構わかりにくい資料なので今回文章としてまとめたいと思います。内容は世間一般的に言われているMock、Stub、Fake、Dummyといった言葉の定義になります。

Test Doubleとは

 Test Doubleとは、テスト実行時に、テスト対象が依存しているコンポーネントと置き換わるものです。「テスト代役」と訳されることもあります。世の中でMock、Stub、Fake、Dummyなどと呼ばれているものの総称に位置づけられます。
 簡単な例を以下に示します。このコードでは、テストメソッド「テストコード」がメソッド「テスト対象」をテストしています。また「テスト対象」は、中でメソッド「外部メソッド」を実行しています。なお「外部メソッド」はテスト対象でないとします。

def test_テストコード
  assert_equal 期待値, テスト対象
end

def テスト対象
  # ...
  外部メソッド
  # ...
end

 上記の「外部メソッド」のようなテスト対象の依存要素は、例えば以下の理由でテスト中に実行したくないことがあります。

  • 実行上の制約を持つ。例えば「テスト環境にないハードウェアを制御する」「勝手に変更してはいけない顧客のDBを操作する」「実行にコストがかかる」「OSや外部ライブラリのAPIであり挙動をコントロールできない」といった制約を持つ。
  • テストのために自由に操作したい。例えば『「外部メソッド」から例外を投げて正しく動くかチェックする』『「外部メソッド」が呼び出されたかチェックする』のように操作や検証に活用したい

 こうした状況に対応するためには、「外部メソッド」をテスト実行時に都合の良い代替と置き換えないといけません。Test Doubleとは、その「外部メソッド」と置き換わるものが該当します。
 図にすると以下のような感じです。本番環境での依存コンポーネントを、テストの際はTest Doubleに置き換えていきます。

f:id:JunichiIto:20200806074809p:plain

※今回は入門用のテキストということで、コードをかなり簡略化して書いていきます。実際はTest Doubleは一般的にオブジェクトであり、Dependency InjectionやDependency Lookupといった仕組みで本物と置き換えます。詳細は「Test Double Patterns at XUnitPatterns.com」を参照ください。

xUnit Test PatternsのTest Doubleパターン

 このTest Doubleの定義や分類例には、有力なものにユニットテストの実装パターン集であるxUnit Test Patterns(index at XUnitPatterns.comおよび同名の書籍)があります。そこではTest Doubleを用途に応じて以下のように分類しています。

  • Test Double Pattern
    • Test Stub
    • Test Spy
    • Mock Object
    • Fake Object
    • (Dummy Object)

 具体的には、Test Doubleに含まれる「Test Stub」「Test Spy」「Mock Oject」「Fake Object」の4つ+Test Doubleに類似した「Dummy Object」の1つの計5つに分類しています。
 なおTest Doubleの定義はいろいろな流儀があり、このxUnit Test Patternsの定義がデファクトスタンダードというわけではありません。ただ分類が明快なほか、Martin Fowlerや id:t_wada さんなどユニットテストの世界で有力な技術者が理解を示しているので、個人的に推奨できる分類と判断しています。それぞれの詳細は後述していきます。

前提:間接入力と間接出力

 なお上記の5つは、おもに間接入力と間接出力の扱いに応じて分類されています。具体的な説明に入る前に、その間接入力と間接出力について説明します。

間接入力

 まず間接入力はテストコードから見えないテスト対象への入力です。
 コードでの例を示します。ここではメソッド「テスト対象」が中で「外部メソッド」を呼び、その戻り値を使用しています。

def テスト対象
  # ...
  answer = 外部メソッド
  # ...
end

def test_テストコード
  assert_equal 期待値, テスト対象
end

 ここでの「外部メソッド」の戻り値のように、テストコードから直接見えないがテスト対象に影響を与える入力が、間接入力となります。なお間接入力には、例えばテスト対象が依存するオブジェクトからの例外発生も含まれます。

間接出力

 一方間接出力はテストコードから見えないテスト対象の出力です。
 コードの例を示します。テスト対象が中で「外部メソッド」を呼んでいますが、そこで「外部メソッド」に引数を出力しています。

def テスト対象
  # ...
  外部メソッド(x)
  # ...
end

def test_テストコード
  assert_equal 期待値, テスト対象
end

 ここでの「外部メソッド」への引数xのように、テストコードから直接見えないが、テスト対象が外に出力しているものが、間接出力となります。なお間接出力には「外部メソッドが実行されたか」「複数のメソッドが順番通り呼ばれたか」のようなメソッド呼出しの有無も含まれます。
 間接出力・間接入力の関係図は以下のようになります。

f:id:JunichiIto:20200806075214p:plain

Test Double詳細

 では間接入力・間接出力の定義を踏まえて、各Test Doubleの詳細を説明します。

(注:以下のコード例は実行可能なRubyコードとして元記事のサンプルコードを一部改変しています。また、サンプルコードは以下のリポジトリに置いています)

Dummy Object

 Dummy Objectはテストに影響を与えない代替オブジェクトです。
 かなり極端ですが、コードの例を以下に示します(変なコードですがとりあえずTDDでの仮実装中とでも考えてください)。

require 'minitest/autorun'

class DummyObjectTest < Minitest::Test
  class Foo
  end

  def テスト対象(foo)
    10
  end

  def test_テストコード
    foo = Foo.new # Dummy Object
    期待値 = 10
    assert_equal 期待値, テスト対象(foo)
  end
end

 上記の例では、「テスト対象」は引数を持ちますが、出力である戻り値「10」と引数は全く無関係です。そこに指定される「foo」はテストに影響を与えないという点で、まさにDummy Objectに該当します。

Test Stub

 テスト対象への間接入力を操作するTest Doubleは、Test Stubと分類されます。テストでは、任意の間接入力がテスト対象に出力されるように、間接入力をTest Stubに事前設定して使用します。例えば以下のような感じです。

require 'minitest/autorun'

class StubTest < Minitest::Test
  # Test Stubとして動作する代替関数
  def 外部メソッド
    @間接入力値
  end

  def テスト対象
    # ...
    answer = 外部メソッド
    # ...
    answer * 2
  end

  def test_テストコード
    @間接入力値 = 100 # 外部メソッドがテストにとって望ましい値をテスト対象に返すようセット
    期待値 = 200
    assert_equal 期待値, テスト対象
  end
end

 上記のコードでは、「外部メソッド」による間接入力「間接入力値」を、テストコード上で事前設定してからテストを実行しています。そのように望ましいように間接入力を操作できるオブジェクトが、Test Stubです。

f:id:JunichiIto:20200806075447p:plain

フレームワークを利用するコード例

以下はMinitestのstub機能を利用した場合のコード例です。

(注:この項、およびそのあとに登場する「フレームワークを利用するコード例」はRuby版独自のサンプルコードです)

require 'minitest/autorun'

class StubTest < Minitest::Test
  # このメソッドはstubに置き換えられるため、実際には呼ばれない
  def 外部メソッド
    raise "Don't call me!"
  end

  def テスト対象
    # ...
    answer = 外部メソッド
    # ...
    answer * 2
  end

  def test_テストコード
    間接入力値 = 100
    stub(:外部メソッド, 間接入力値) do
      期待値 = 200
      assert_equal 期待値, テスト対象
    end
  end
end
Test Spy

 テスト対象の間接出力を記録し、それをテストコードから参照可能にするTest Doubleは、Test Spyと分類されます。テストでは、テスト対象の間接出力を記録させ、その後テストコード上でその間接出力を検証します。なお間接入力を操作することもあります。
 例えば以下のような感じです。

require 'minitest/autorun'

class SpyTest < Minitest::Test
  # Test Spyとして動作する代替関数
  def 外部メソッド(input)
    @間接出力値 = input
  end

  def テスト対象
    # ...
    x = 100
    外部メソッド(x)
    # ...
  end

  def test_テストコード
    テスト対象
    期待値 = 100
    assert_equal 期待値, @間接出力値 # Test Spyに記録させておいた間接出力値を比較検証する
  end
end

 上記のコードでは、「外部メソッド」への間接出力をいったん「間接出力値」に保持します。テストコードはテスト実行後それを参照することで、間接出力が適切だったかチェックします。こうした間接出力を保持するものがTest Spyとなります。

f:id:JunichiIto:20200806075548p:plain

フレームワークを利用するコード例

以下はspy gemのspy機能を利用した場合のコード例です。

require 'minitest/autorun'
require 'spy/integration'

class SpyTest < Minitest::Test
  # このメソッドはspyに置き換えられるため、実際には呼ばれない
  def 外部メソッド(input)
    raise "Don't call me!"
  end

  def テスト対象
    # ...
    x = 100
    外部メソッド(x)
    # ...
  end

  def test_テストコード
    Spy.on(self, :外部メソッド)
    テスト対象
    期待値 = 100
    assert_received_with self, :外部メソッド, 期待値
  end
end
Mock Object

 以下の用途をあわせもつTest Doubleは、Mock Objectと分類されます。

  • テスト対象の間接出力の期待結果を持っています。
  • テスト対象を実行している間、テスト対象の間接出力を取得します。
  • 間接出力を確保できたら、Mock Objectの中でその期待結果と比較検証し、成功か失敗か判定します。
  • テストコードは、テスト対象を実行後、Mock Objectから検証の成功・失敗の情報を受け取ります。
  • Test Stubの機能を包含していることもあります。

 非常に簡略化した例ですが、例えば以下のようなものです。

require 'minitest/autorun'

class MockTest < Minitest::Test
  # Mock Objectとして動作する代替関数
  def 外部メソッド(input)
    if @期待する間接出力値 == input
      @テスト成功フラグ = true
      return
    end
    @テスト成功フラグ = false
  end

  def テスト対象
    # ...
    x = 999
    外部メソッド(x)
    # ...
  end

  def test_テストコード
    @期待する間接出力値 = 999 # Mock内で比較に用いる期待値
    テスト対象
    assert @テスト成功フラグ # Mock内で検証した結果をチェック
  end
end

 ここでは、「外部メソッド」が、テスト対象から間接出力を受け取るとともに、同時にその検証も行っています。そしてテストコードでは、その検証結果を見てテスト結果を決めています。このように間接出力の検証能力を持つのがMock Objectです。
 なおMock ObjectとTest Spyは両方とも間接出力を検証するためのTest Doubleです。ただ「Mock ObjectはMock Object内で間接出力結果を評価する」のに対し、「Test Spyは間接出力を保持するだけで、間接出力結果の評価は後からテストコード上で行う」という違いがあります。

f:id:JunichiIto:20200806075705p:plain

フレームワークを利用するコード例

以下はMinitestのmock機能を利用した場合のコード例です。
そのままでは「外部メソッド」をモックに置き換えることができないため、Fooクラスを導入し、「テスト対象」を呼び出す際にFooクラスのMock Objectを引数として渡すようにしました。

require 'minitest/autorun'

class MockTest < Minitest::Test
  class Foo
    # このメソッドはmockに置き換えられるため、実際には呼ばれない
    def 外部メソッド(input)
      raise "Don't call me!"
    end
  end

  def テスト対象(foo)
    # ...
    x = 999
    foo.外部メソッド(x)
    # ...
  end

  def test_テストコード
    mock_foo = MiniTest::Mock.new
    モックの戻り値 = nil
    期待する間接出力値 = [999]
    mock_foo.expect(:外部メソッド, モックの戻り値, 期待する間接出力値)
    テスト対象(mock_foo)
    mock_foo.verify
  end
end
Fake Object

 テスト実行中、代替元の本物と同じように動けるTest DoubleはFake Objectと分類されます。Fake Objectは間接出力を受け取り、間接入力を操作しますが、あくまでそれも本物と同じように処理したり出力したりします。

補足

 説明は以上ですが、おまけの補足解説をいくつか行いたいと思います。

分類方法

 Test Doubleの分類方法は以下のような感じです。

  • テストの範囲内で本物と同じように動作するTest DoubleはFake Object。
  • 内部のパラメータや状態がなんでもあってもテストに影響を及ぼさない代替オブジェクトなら、Dummy Object
  • 上記以外で、テスト対象の間接出力を受け取り、かつ自身でその検証を行うTest DoubleはMock Object
  • 上記以外で、テスト対象の間接出力を受け取りそれをあとから参照可能にするTest DoubleはTest Spy
  • 上記以外で、テスト対象の間接入力を操作できるTest DoubleはTest Stub
Test Doubleの用途

 Test Doubleはしばしば以下の用途で用いられます。

  • コスト的・時間的・環境的にテストで実行困難なコンポーネントを置き換える
  • テストを高速化する。例えばDBをメモリ上のFake Objectに置き換えたりする。
  • まだ実装していないコードの代替となる。
  • 複雑で使いにくいコードを簡略化する。例えば多数のクラスや設定に依存しているコンポーネントを、単純なTest Doubleに置き換えてしまう。
  • テスト対象の間接出力を取得し検証する。
  • テスト対象の間接入力を操作する。

(Ruby版の記事はここまで)

参考情報

記事のタイトルに含まれている「xUnit Test Patterns」は書籍(洋書)としても出版されています。

困っている顧客に対しては正論(もしくは持論)をいきなりぶつけても、心の底からは満足してもらえないのでは?という話

最近、僕の持っているギターアンプがときどき「キーン」という耳障りな高音ノイズを発するようになりました。
ですが、毎日ノイズが発生するわけではなく、忘れた頃にたまに鳴るだけです。

f:id:JunichiIto:20200426112055j:plain

しばらく様子を見つつ使っていたのですが、いつまでたっても「たまに出るノイズ」は直らないので、修理に持っていくことにしました。
とはいえ、ギターアンプの修理なんて今まで頼んだことがないので、どこに持っていけばいいかわかりません。
僕が住んでいるのは兵庫県西脇市という田舎町なので、大阪や神戸の楽器店まで運ぶのもちょっと大変です。

どこか近くにいいお店はないかなー、とネットを調べたところ、片道30分ぐらいで行けるギターアンプ専門店があることを知りました。
Facebookページの修理実績を見るとビンテージ ギターアンプの修理なんかも手がけていて、なかなか信頼できそうな雰囲気でした。

「よし、ここに修理を頼んでみよう」と決心し、事前に電話を入れてアンプを持っていきました。

なんかしっくりこない店員さんとの会話

そのお店はどうやら個人でやっているような小さな楽器店で、店員さん兼オーナーみたいなおじさんが出てきました。(名札もなく、名前も名乗らなかったので、「オーナーかも?」というのは僕の推測です)

店員さんとの会話はこんな感じでした。

「かくかくしかじか、こんなノイズが出て困ってるんですよ。原因もさっぱり不明で」

(ギターを繋いでアンプをチェックする店員さん。しかし、タイミングが悪く、ここではノイズが発生しない)

「今はノイズが出てないですけど、家だとこんなノイズが出るんです(と、ノイズが発生したときに撮っておいた動画を見せる)」

店員「あー、これは悪いのはアンプじゃなくて、たぶん外のノイズを拾ってるせいですね。ほら、ギターのボリュームを絞ったらアンプのノイズも消えてるでしょ?これは外のノイズを拾ってるせいだから」

「そうなんですか?でも、最近急にノイズが出始めたし、これといってノイズの発生源になりそうなものもないし、僕には心当たりがないんですよね・・・」

店員「いや、それはわからないよ!ノイズなんて目に見えないから!最近はいろんなものが電磁波を発してるでしょ?スマホとか、電子レンジとか」

「ええ、電子レンジの可能性も疑ったことがあるんですが、電子レンジを使ってないときもノイズが鳴るんですよ」

店員「まあ、そりゃそうですよ。そんなの関係なくて、動いてないように見えたっていろんなところから電磁波は出てるんですよ」

「(うーん、なんかいまいち説明になってない気が・・・。まあ、いいや)で、どうしたらノイズの発生源がわかるんでしょう?」

店員「今度ノイズが出たら家の外に持ち出して鳴らしてみたら?あ、家の外っていうのは、家のすぐ外じゃなくて、家から遠く離れたところ、っていう意味ね。家のそばだと結局ノイズが入ってくるから」

「はあ・・・。(このアンプ、20kgもあるのにいちいちそんな面倒なことやれるかい!ってか、そんなことしたって根本原因はわからんやろ)」

・・・みたいな感じで、この店員さんからは終始「あー、あんたはわかってないなー」みたいな物言いをされました。
実際の口調は決してキツいわけではないのですが、どこかそういう気持ちが透けて見えるように僕は感じました。

いや、本人はきっとイヤミを言ってる自覚はなくて、きっと「自分の知見を率直に話しているだけ」なんだと思います。
でも、全然僕の困りごとに寄り添ってくれる姿勢を感じなかったんですよねえ。逆に何を尋ねても突き放される印象でした。
「アンプのノイズ、困りますよねえ。わかりますよ〜」のひとことぐらいあれば、全然違ったんですが・・・。

結局、ノイズの原因はアンプの故障ではない、ということで、この日は何もせずにそのままアンプを持ち帰ることになりました。
なお、それから一週間ほど経ちましたが、それ以来ノイズが発生していないので、原因の追及はできておりません💧

「INPUT 2なんて使っても意味ないから」→ ???

ところで、この店員さんと話していて決定的に不愉快だったできごとがあります。
それはこちらが尋ねてもないポイントに勝手にダメ出しをしてきたことです。

少し専門的な話になりますが、このアンプにはINPUT 1とINPUT 2という2つのインプットがあります。

f:id:JunichiIto:20200725160650j:plain

2つのインプットの違いは単純にいうと、INPUT 1に差し込むと大きな音がして、INPUT 2は小さな音で鳴ります。(参考
で、僕はふだんINPUT 2を使ってるんですが、この店員さんはどうもそれが気に入らなかったみたいです。

店員「これ、ふだんINPUTはどっち使ってるの?」

「INPUT 2です」

店員「INPUT 2なの?(明らかに何か言いたそうな数秒間の沈黙)・・・まあ、いいですけどね。INPUT 2でも」

みたいなことを言うので、「このおっちゃん、INPUT 2に絶対不満を持ってるよな」というのは十分伝わってきました。

そしたら案の定、そのあとの別の会話の中で、

店員あと、このアンプ、ふつうはINPUT 1を使うからね。INPUT 2なんて使っても意味ないから

と言ってきました。はい、ダメ出し〜!

なんで聞いてもないのにそんなこと言われなあかんねん。ノイズの話と関係ないやん!

ただ、僕はその場ではとっさに言い返すことができず、「はあ、そうなんですか」としか返事ができませんでした。

でも、たしか僕はINPUT 1とINPUT 2を何度か弾き比べてINPUT 2を選んだはずなんです。

INPUT 1とINPUT 2は音量だけじゃなくて音色も違う!(検証済み)

家に帰ってからあらためてINPUT 1とINPUT 2で弾き比べたんですが、INPUT 1と2の違いは音量だけではありません。
INPUT 2の方が丸くて温かい僕好みの音がします。
細かい話をすると、アンプのボリュームを6ぐらいまで上げて、ギターのボリュームをちょうどいい音量まで上げると、すごくいい音がします。
INPUT 1では同じ音が出ません。INPUT 2に比べると少し鋭くて冷たい音がします。

「そうだ!たしかそれで僕はINPUT 1じゃなくてINPUT 2を使ってたんだ」とそのとき思い出しました。

実際、海外の掲示板では僕と同じように「INPUT 1と2では音が違う。自分はINPUT 2の方が好きだ」という意見をいくつか見かけました。

おい、INPUT 2にも意味あるやん!
なんで勝手に意味ないって決めつけるねん!
お前の知識の方が間違ってるやろーー!!

と、こんなふうに、終始上から目線で話されるわ、突然ダメ出しされるわ、さらにダメ出しの知識も間違ってるわで、「もうこの店には二度と行きたくないなー」という印象を強く持ってしまいました。

家から割と近いし、置いてるギターやアンプもなかなか通だし、最初は「へー、こんな店が近くにあるなら応援したいな〜」と思ったんですが。
ああ、残念!

まとめ:この店員さん、もしかして反面教師になってない?

・・・で終わってしまうと、ただの愚痴日記で終わってしまうので、もう少し続きます。

たしかに、このお店の態度はイマイチですごく気分が悪かったのですが、ひるがえって、自分はIT技術の専門家としてこんな態度を取ってないかなーというのがちょっと気になりました。

お店の人はたぶん「あんた(僕のこと)より自分の方が知識が豊富だ」と思い込んでいて、普通に自分の知識を話したり、アドバイスをしてあげたりした気になってるはずです。(きっと、本人は「いい接客をしてあげた」と思ってそう)

ただ、視点がちょっと自分本位で、この人には「お客さんの悩みを一緒に解決してあげようとする姿勢」が足りなかったように思います。
あと、「INPUT 2なんて使っても意味ないから」というような自分の勝手な思い込みもあったりしました。

その結果、僕をこのお店のファンにすることはできませんでした。ファンにするどころか、反感を買って終わりました。

でも、僕も仕事でお客さんと話したり、プログラミング初心者の人にプログラミングを教えたりするときに、同じような態度を取ったりしていないかな?というのが気になりました。

自分では気を付けてるつもりだけど、知らず知らずのうちに上から目線で話してたらイヤだなー、と。
ほら、楽器屋の店員さんじゃなくても、お医者さんとかでこういう人、たまにいません?

「腰痛がずっと治らないんです」
医者「うーん、レントゲンを見ても何もないんだけど」
「でも痛いんですよ(だからなんとかして〜)」
医者「しばらくすればよくなるんじゃないですかね」
「・・・。(いや、今すぐ楽になる方法を教えてほしいんだけどさあ)」

たとえすぐに治せる方法がなかったとしても、せめて一緒に問題解決しようという態度ぐらい見せてほしいんですよ、こっちとしては😣

こういう相談事って、専門家からの「なるほど、それは大変ですね。よくわかります」というひとことがあるかないかで、クライアント側の安堵感が大きく変わってくると思います。
今回のやりとりを通じて、クライアント側の気持ちがよくわかりました。

このお店の店員さんを他山の石にして、僕もちょっと気を引き締めようと思います。

というわけで、この話はおしまい!