ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

マネージャーの最初の仕事は嫌われること論

Lemur Looking Up こんにちは。人間中心設計(HCD)専門家のやまぐち(@hiro93n)です。

主にプロダクトマネージャーやQAエンジニアの部署でマネージャーをやっています。キャリア的にはデザインの部署のマネージャーのやりたさもあるのでデザイナーにかかる課題解決などにも首を突っ込んでいます。マネージャーばかりでゲシュタルト崩壊しそうですね。自分はしました。

そんなわけでこの記事はユニファAdvent Calender 2020、22日目の記事です。

続きを読む

写真からLINEスタンプ風画像の自動生成

こんにちは、データエンジニアリングチームの宮崎です。この記事は、UniFaアドベントカレンダー2020の21日目の記事となります。 最近、文字を自由に編集できるLINEスタンプがあることを知りました。いつの間にか色々進化していますね・・・! そこで今回はDeepLearningのモデルを使って、以下のように写真からLINEスタンプ風画像の自動作成を行ってみたいと思います。

f:id:unifa_tech:20201214163238j:plain
入力画像および出力画像例

続きを読む

リスクベースドテストやってみた話

f:id:unifa_tech:20201218174807p:plain

ごあいさつ

こんにちは!ユニファアドベンドカレンダーをご覧頂きありがとうございます! ユニファQAチームの斉藤です。 このブログは、UniFaアドベントカレンダー 20日目となります。

今年はコロナで大変な1年でしたね。ユニファQAチームも今年の早い時期からフルリモートに切り替えて活動を行ってきました。もともとリモート勤務可能ではありましたが、案件が去年の2倍以上に増えたり、メンバーの入れ替わりがあったりしたので、フルリモート体制で乗り切るには大変な1年だったなぁと感じています。

そんな中ですが、新しい取り組みにもチャレンジしてみました。リスクベースドテストです。 今回はそこから学んだことをふりかえってみたいと思います。

新しい取り組み 「リスクベーステストを始めよう!」

冒頭のごあいさつさらっと記載しましたが、今年はテスト案件が去年に比べて大幅に増えました。

去年までは当時のQAチームメンバーでもなんとか回せる量だったのですが、今年の春頃から数か月規模の案件が並行で走り、秋頃からさらに増えて、現在は多くの案件が並行して動いています。案件が増えることは計画内だったので、今年の春頃から「案件が増えた時にそなえて、テストを考えねば」という話題はチーム内であがっていました。

そこで当時のQAチームリーダーから提案されたのが、リスクベースドテストでした。

リスクベーステストって?

では、リスクベースドテストとは何でしょうか。

JSTQB用語集で引くと、以下のような記述が出てきます。

リスクベースドテスト(risk-based testing): プロジェクトの初期段階からプロダクトリスクのレベルを低減させ、ステークホルダにその状態を通知するテストの方法。プロダクトリスクの識別の他、テストプロセスをガイドする際のリスクレベルの活用もこれに含まれる。

プロダクトリスクというものを識別し…テストプロセスをガイドする…なるほどわからん

私の理解をざっくりいうと、要はプロダクトリスク(そのプロダクトで起きてほしくない事象)の重大度と発生率を整理して定義し、それに基づいてテストする機能やしない機能を選んでいく、というテスト戦略です。

プロダクトリスクのリスクタイプやリスクレベルも、以下のようにリーダーの方で定義して頂いたので「それに基づいてテストする機能やしない機能を選んでいくのだよ」の具体的な方法を、いよいよQAチームメンバーで考えていくことになりました。

f:id:unifa_tech:20201218140416p:plainf:id:unifa_tech:20201218142954p:plain

ユニファQAチームで取り組んだやり方

ユニファQAチームは、メンバーによってテスト設計から実装までのやり方が少し異なります。このリスクベースドテストを導入しようとしている時は、複数プロダクトにそれぞれメンバーが分かれてアサインされていました。 いつもだと、それぞれのプロダクトに合わせてメンバーがテスト設計をしますが、このリスクベースドテストを導入するなら、複数プロダクト統一したやり方じゃないとレビューがしにくいな~と考えました。

そこで複数プロダクト間である程度、統一できるやり方として、テスト観点を抽象化したリスク設定要素、基本リスクレベル、補正リスクレベルという項目を使ってみようと考えました。

f:id:unifa_tech:20201218140656p:plain
QAチームメンバーに提案した時の図

リスク設定要素

f:id:unifa_tech:20201218143147p:plain リスク設定要素とはいわゆるテスト観点です。 ユニファQAチームで行うリスクベースドテストでは、このテスト観点をできるだけ抽象化したものをリスク設定要素と名付け、これに重みづけすることで「テストする機能やしない機能を選ぶ」ことをやってみようと考えました。

このリスク設定要素(テスト観点)は、ユニファQAチームメンバーの認識をそろえるため、全員参加でマインドマップを使ってブレストして決定しました。

基本リスクレベル

f:id:unifa_tech:20201218143319p:plain 次に基本リスクレベルです。 基本リスクレベルとは、リスク設定要素の基本的な重みづけです。 リスク設定要素とリスクタイプでマトリクスを作り、QAチーム全員で話し合いながら1~5点の間で点数をふっていきました。(5点がリスクとして一番重い点数です) f:id:unifa_tech:20201218143348p:plain 全てのリスク設定要素に基本リスクレベルとして点数を設定したら、次にそれを、プロダクトリスクレベルへマッピングを行いました。 リスク設定要素のもつ下限値~上限値を均等にリスクレベルへわりつけ、該当するリスク設定要素をマッピングして、QAチームで眺めてみました。 結果、リスクレベルとリスク設定要素の組合せに違和感はないね!ということで、基本リスクレベルの設定が完了できました。

補正リスクレベル

基本リスクレベルの設定が完了しました。この後のフローとしては、機能・画面ごとにリスク設定要素をあてはめて、QA保証対象以上のリスク設定要素のみに対しテスト設計をしていく、という形にいったん整いました。

ただ機能・画面によっては、リスク設定要素(基本リスクレベル)通りにあてはめてしまうと、具合が悪いというものもあります。 たとえば、リスク設定要素の「バリデーション」や「画面UI」は、QA保証対象外ですが、機能・画面のビジネス的な重要度によっては、QA保証対象とした方がよいこともあります。

これを補うために、補正リスクレベルを使うことにしました。 f:id:unifa_tech:20201218143420p:plain 補正リスクレベルとは「基本リスクレベルを見ると、QA対象もしくは対象外だけど、担当QA判断によってレベルを調整してよい」というものです。

レビュー時にレビューアに対して「ここは重要もしくは重要でないと判断したから、確認してくれ」というアピールとしても使えると考えました。

リスクベースドテストやってみた結果

やってみた結果は以下です。

①工数はあまり削減できなかった

②短期間のテスト実行で、重要度の高いバグが検出できた

①については、もともとリスクベースドテストは「工数削減」が目的ではなく、「少ないリソースで品質を担保するためにテストするしないをプロダクトリスクに基づいて決めていく」やり方なので正しいのですが、今回は「テストしない」と決めた部分についても

  • QAチーム以外にテストしてもらおう!
  • QAチーム以外にテスト依頼してOKもらったが、「テストするからチェックリスト下さい」となりQAチームにタスクが戻ってきた!

という動きをしてしまい、結果、QAチームはQA保証対象外のリスク設定要素についても、なにがしかの形でテスト設計することで工数削減効果はあまりなかった、という結果になりました。

リスクベースドテスト時、工数も適正な量に抑えたいのであれば、QA保証対象外部分については「やらない」という選択をすること、「やらない」代わりに問題発生した時はどのようにするか等をステークホルダーと話し合っておくことが必要そうだな、と感じました。

②はまさに、リスクベースドテストで狙いたいところだったので、満足いく結果でした!

これから

リスクベースドテストをやってみたのは2020年初夏~秋頃のテスト案件なのですが、現在ユニファQAチームではリスクベースドテストを行っていません。

理由としては、案件がさらに増えたため外部QA会社さんに入って頂いたりと大幅なQA体制変更があったこと、並行して複数案件が走っているので日々変わりゆく状況変化に対応することの方が、QAチームとしても重要になっているためです。

ユニファQAチームって、品質をあげるための活動は何でもできることが魅力です。 今はQA体制の安定化に力を入れていますが、いつかリスクベースドテストももっとブラッシュアップした形で取り組みたいと思っています!

ここまでお読み頂き、ありがとうございました! まだまだコロナで大変な時期続きそうですが、体調に気をつけて、元気にQA&テストエンジョイしていきましょうね!では!

ユニファQAチームではQAエンジニアさん、テスト自動化エンジニアさんを大募集しています!

カジュアル面談も大歓迎です!

「ちょっとならお話してあげようかな」って思われた方はWantedlyの求人ページから「話を聞きに行きたい」ボタンをクリックお願いしまーーーーーーす!

www.wantedly.com

www.wantedly.com

心理的安全性への第一歩としてのふりかえり

心理的安全性への第一歩としてのふりかえり

はじめに

こんにちは。スクラムマスターの渡部です。

最近、正式に6チーム(開発が動いている殆どのチーム)でふりかえりの支援を受け持つことになりました。

実は、そのふりかえりの支援は、僕から「やらせて欲しい!」と頼み込んで実施に至ったのですが、その際に話したことの一部を、詳しい解説付きでブログとして残そうと思います。

また、はじめて1ヶ月半ほどしか経っていませんが、やってみたメンバーからの感想だったり、こんなことトライしてるよ!ということも紹介しようと思います。

※ この記事は、ユニファAdvent CalendarふりかえりAdvent Calendarの19日目の記事です。

目次
  • ふりかえり支援をしたいと思ったきっかけ
  • 心理的安全性を高めるには?
  • 改めて、いま、僕がふりかえりでやりたいと思っていること
  • 実際にやってみて出てきたトライ
  • やってみたみんなからのフィードバック
  • おしまい

ふりかえり支援をしたいと思ったきっかけ

ユニファは、まだまだ小さな会社です。色んな面で十分なことなんてありません。見渡せば「やらなきゃいけないこと」がゴロゴロ転がっています。それはとても有り難いことで、めちゃくちゃやり甲斐のあることでもあります。

僕は2019年3月に入社したのですが、個人的な感覚としては、入社直後から常にハイペースを維持し続けている感覚があります。(僕から見える範囲は極一部ですが、全社員、本当にめちゃくちゃ頑張ってるなと思っています。)

そんな中、この頃、ふとした会話の中で「お、それ地味にめちゃくちゃ良さげなカイゼンポイントだね!!ちゃんとした場所で相談してみるね!」ということがポツポツと出てくるようになってきたなと感じていました。

そして、そのようなことを「たまたま会話してたから、今回偶然気づけただけ」で終わらせてしまうのは勿体ないとも思いました。

話しやすい適切な場を設けることで、セレンディピティ(ふとした偶然をきっかけに幸運を掴み取ること)の機会を増やす、つまり、偶然からの幸運を意図的に増やしたいと思ったのです。

そしてそのためには、先ずは、心理的安全性を高めなければと考えたのです。

続きを読む

分かりそうで分からないでも分かった気になれる午睡チェックの仕組み補足

みなさんこんにちは。

午睡チェックのディレクター/スクラムマスターをしている保坂です。

この記事は、UniFaアドベントカレンダー 18日目の記事となっております。

先日AWS Startup Architecture of the Year Japan 2020 Live Finaleで、ルクミー午睡チェックのアーキテクチャが評価され、ユニファが優勝することができました。

tech.unifa-e.com

当日の生中継を見ていて優勝とった瞬間、とても興奮してslackに画面キャプチャを貼りまくっていたことを思い出します。 Well-Architected な仕組みに関してブログを見てください。

tech.unifa-e.com

さて、今回のブログでは、午睡チェックの仕組みの補足と、施策を実装してからの失敗談を書きたいと思います。

■センサーとタブレットの間

f:id:unifa_tech:20201215133816p:plain
その昔、冷静と情熱の間 って映画がありましたね。。

午睡チェックの構成は大きく分けると、

続きを読む

すき焼き鍋からタスク並列処理

この記事はユニファAdvent Calender 2020の17日目の記事です。

こんにちは、プロダクトエンジニアリング部のちょうです。最近天気は段々寒くなってきましたね。こんな時期欠かせない料理といえば鍋でしょう。鍋は煮ながら食べる料理ですゆえ、準備の手間はかからないし、好きな食材をどんどんいれることができます。

どころで、最近どれぐらい早く鍋をできるのを考えていました。例えば、炊飯器を15分でご飯を炊けるとする。その15分で鍋の食材を何も処理していない状態からホカホカの鍋にできるのでしょうか。

ここで、この鍋を人気のすき焼き鍋にしましょう。すきやき鍋は必要な食材はこちらです。

レシピから抜粋

  • 牛ロース肉(薄切り)
  • ねぎ
  • しらたき
  • えのきたけ
  • 焼き豆腐
  • しいたけ
  • 白菜
  • サラダ油

割り下については、市販のすき焼きのたれを代用。

ざっくりな料理の流れは

  1. 具材の下処理
  2. サラダ油でねぎと牛肉を炒めて、すきやきのたれを回しかける
  3. 鍋に具材を入れて煮込み

家に二口のコンロはあれば、炒めるのと鍋の煮込みを同時にできます。そして下処理も同時にします。フライパンが熱くなるまではちょっと時間がいります。鍋に水とすきやきのたれを入れて沸騰するまでは5分ぐらいかかります。なので

  • 鍋に水とすきやきのたれを入れて煮る
  • ねぎを下処理する
  • フライパンにサラダ油を入れて中火にする
  • 牛ロース肉を下処理する
  • フライパンが熱くなったらネギをフライパンにいれる
  • ネギに焦げ目がちょっとでたら、牛ロース肉をフライパンいれる
  • のこり時間でほかの具材を下処理する(硬いから柔らかいもの順)
  • 硬いものから柔らかいもの順で鍋にいれる

が一番効率でしょう。

すきやき鍋料理の流れ
すきやき鍋料理の流れ

この料理の流れを技術的な視点からみると、コンロ2口プラス料理する人で実質三プロセッサーです!言い方はちょっと変かもしれませんが(料理する人はコンロではない)、同時に処理することでスピードが上がったのはたしかです。

並列処理の中で、これはタスク並列処理と分類されると思います。もうひとつ、データ並列処理がありますが、それはすべてのプロセッサーはが別々のデータに同時に同じ処理を行うというタイプです。

タスク並列処理では、各タスクの間依頼関係があると、その前後関係が決められます。逆に依頼関係がないと、タスクは同時に行うことができます。例えば、フライパンでネギと牛肉を炒めるのは、先にネギをいれるのです。牛肉はあとなので、 ネギ > 牛肉 という関係があります。そして、フライパンが熱くなるまでネギをいれないので、 フライパンが熱くなる > ネギ という関係があります。最後、ネギを下処理しないと、フライパンにいれないので、 下処理前のネギ > ネギ という関係があります(同じく 下処理前の牛肉 > 牛肉 )。まとめると

  • ネギ > 牛肉
  • フライパンが熱くなる > ネギ
  • 下処理前のネギ > ネギ
  • 下処理前の牛肉 > 牛肉

並列処理では、この関係を満たされば問題ないです。一つの答えは

料理する人 コンロ
ネギを下処理する
牛肉を下処理する サラダ油を入れ、中火
ネギをいれる
牛肉をいれる

タスク並列処理ではどれぐらい早くなるのは、依頼関係の洗い出し及びプロセスの設計が重要です。

ネギの下処理とコンロの中火を一緒やってしまうと、下処理に時間がかかって、コンロに警告が出て自動的に止まることもあります。安全のために、ネギを先に処理するのがおすすめです。こういうタスク間で直接ではない関係性も含めて考慮しましょう。

ではプログラムでシミュレーションしましょう。

import org.junit.jupiter.api.Test
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

class SukiyakiTest {

    class StoveCell() : Cell() {
        private var waitingForBeef = false

        override fun receive(context: CellContext, event: Event) {
            when (event) {
                TurnOnStoveEvent -> context.schedule(500, TimeUnit.MILLISECONDS, FryingPanReadyEvent)
                FryingPanReadyEvent -> context.parent.tell(event)
                AddLeekToFryingPanEvent -> {
                    waitingForBeef = true
                    context.schedule(2000, TimeUnit.MILLISECONDS, WaitingForBeefEvent)
                }
                AddBeefToFryingPanEvent -> {
                    if (waitingForBeef) {
                        waitingForBeef = false
                    }
                    context.schedule(4000, TimeUnit.MILLISECONDS, DoneEvent)
                }
                WaitingForBeefEvent -> if (waitingForBeef) {
                    context.logger.info("牛肉を待つ")
                }
                DoneEvent -> context.parent.tell(event)
                PoisonPill -> context.stopSelf()
            }
        }
    }

    class ChefCell(private val latch: CountDownLatch) : Cell() {
        private var _stove: CellRef? = null

        private val stove: CellRef
            get() = _stove!!

        override fun start(context: CellContext) {
            _stove = context.startChild(StoveCell())
            context.logger.info("ネギを下処理する")
            context.schedule(1000, TimeUnit.MILLISECONDS, TurnOnStoveEvent)
        }

        override fun receive(context: CellContext, event: Event) {
            when (event) {
                TurnOnStoveEvent -> {
                    stove.tell(TurnOnStoveEvent)
                    context.logger.info("フライパンにサラダ油をいれ、中火")
                    context.logger.info("牛肉を下処理する")
                    context.schedule(2000, TimeUnit.MILLISECONDS, AddBeefToFryingPanEvent)
                }
                FryingPanReadyEvent -> {
                    context.logger.info("フライパンにネギをいれる")
                    stove.tell(AddLeekToFryingPanEvent)
                }
                AddBeefToFryingPanEvent -> {
                    context.logger.info("フライパンに牛肉をいれる")
                    stove.tell(event)
                }
                DoneEvent -> {
                    context.logger.info("できあがり")
                    stove.tell(PoisonPill) // to stop stove
                    latch.countDown()
                }
            }
        }
    }

    object TurnOnStoveEvent : Event
    object FryingPanReadyEvent : Event
    object AddLeekToFryingPanEvent : Event
    object WaitingForBeefEvent : Event
    object AddBeefToFryingPanEvent : Event
    object DoneEvent : Event

    @Test
    fun test() {
        val latch = CountDownLatch(1)
        val system = CellSystem()
        system.add(ChefCell(latch))
        system.start()
        latch.await()
        system.stop()
    }
}

CellSystemが私が作ったActorモデルのスケジューラーシステムです。Actorモデル自体もタスク並列処理にピッタリするモデルです。コードの中各ステップの時間:

  • ネギ処理 1
  • 牛肉処理 2
  • フライパン熱くなるまで 1
  • ネギを入れてから牛肉を入れるまで 2
  • 牛肉をいれてからできあがり 4

単位は秒ですが、実質は10秒20秒単位と思ってください。

結果

2020-11-30 16:42:32.888 [worker-0] INFO  cell://ChefCell - ネギを下処理する
2020-11-30 16:42:33.897 [worker-0] INFO  cell://ChefCell - フライパンにサラダ油をいれ、中火
2020-11-30 16:42:33.897 [worker-0] INFO  cell://ChefCell - 牛肉を下処理する
2020-11-30 16:42:34.400 [worker-1] INFO  cell://ChefCell - フライパンにネギをいれる
2020-11-30 16:42:35.902 [worker-1] INFO  cell://ChefCell - フライパンに牛肉をいれる
2020-11-30 16:42:39.908 [worker-1] INFO  cell://ChefCell - できあがり

ここでworker-0は料理をする人です。worker-1はコンロです。

いかがでしょうか。普段の生活からも並列処理に関する知識も潜んでいますね。並列処理が人間の生活をモデルにしたといっても過言ではありませんね。時間があったら、ぜひまわりのことをタスク並列処理で考えてみてください。

最後に、ユニファが一緒に働いてくれるメンバーを募集しています。興味がある方ぜひ見てください。

採用情報 - ユニファ株式会社

Local Development with LocalStack

こんにちは。プロダクトエンジニアリング部の杉本です。

この記事は、UniFaアドベントカレンダー2020 の16日目の記事になります。
(私事ですが、2020年6月にユニファにjoinして半年がたち、初めてのブログ投稿です!)

皆さんは、AWSマネージドサービスに依存する機能を開発する場合、ローカルでどのように動作確認をされていますか?

  • 実際のAWS環境にローカルから接続する
  • レスポンスをスタブ化する*1
  • ローカルはそこそこに、とりあえず検証環境にデプロイして確認する

他にも様々あり、プロジェクトのフェーズや事情によっていずれも選択肢になりえると思います。
でもそれに相まって、多少なりともかかるコスト誤接続や誤操作によるリスク動作確認不十分な品質 など課題もでてきますよね。。。

今回は、そんなときにオススメな LocalStackを紹介したいと思います。

続きを読む