テスト勉強会第2弾「テストを知ろう」

はじめに

以前行った初回となる勉強会「テストとは」に続き、2回目の勉強会になります。

(前回の記事↓)

tech.88oct.co.jp

 

今回もDeNA SWETグループの平田さんを講師にお招きして、開催して頂きました!実際に勉強会で見せてくださった資料と共に紹介していきます。

 

目的

今回の勉強会の目的は「『テスト』について『まず』知ること」です。普段何気なく口にしている「テスト」とういうものについて再定義を行うことで

  1. プロダクトを作るにあたってのより良いものを作れる
  2. 共通語彙により意思の疎通ができる

という2つを達成しようという狙いがありました。

 

結構な人数が集まりました。この人数なら別のスペース確保しておけば良かったとも・・。

f:id:yohei-fujii:20191018202050p:plain
 

内容

まずは前回の復習からです。

  • テストというのは「現在の状況」を教えてくれる
  • テストがあるから品質がよくなる訳ではなく、プロダクトをよくするのは我々であるということ
  • 知っておくと良い品質良識(狩野モデル・VerificationとValidation・CheckingとTesting)

改めてテストの意義と分類について整理しました。

 

そして今回のテーマは「テスト技法を知ろう」です。さまざななテスト技法がある中で、今回はその中でも2つを取り上げていただきました。

 

同値分割・境界値分析

1つ目が、「同値分割・境界値分析」です。

f:id:yohei-fujii:20191018130405p:plain

聞きなれない言葉ですが、意味を理解すると難しくはありません。何気なく普段からテストで行っていることを言語化しただけとのことです。

f:id:yohei-fujii:20191018130409p:plain

連続性の説明と注意点です。確かに、ここにあるように、お金と言っても米ドルのような通過になってくると、一概に連続性の単位が同じである訳ではないので気をつけなければなりませんね。

そしてなぜ境界値に気をつけなければならないのかという点ですが、日本語の難しさも絡んできます。

f:id:yohei-fujii:20191018130835p:plain

 

むむ。これ見ると確かに!と感じるかと思います。人によっては認識がズレる箇所でもありえます。

 

ここで平田さんが、わかりやすい図を出してくださいました。例として、パスワード登録をあげてくださっています。

 

f:id:yohei-fujii:20191018131937p:plain

テスト結果によってグループ分けされた値の集まりを「同値クラス」と言うそうです。そしてその中でも、有効であるものと無効であるものがあります。

 

上の例では、4文字未満、そして16文字以上である場合に、パスワードとしては無効になってしまいますので、「無効同値クラス」と呼ばれます。また使用禁止である「@」が入っている場合ももちろん無効となりますね。

 

そしてこの情報を元に、「同値クラステスト」と「境界値クラステスト」を行います。

f:id:yohei-fujii:20191018152827p:plain

同値クラステスト」では、「有効」「無効」それぞれのクラスから代表値を、「境界値クラステスト」では、境界値と境界値の1つ上と1つ下を選ぶんですね!

 

テストケースの例として以下のようにわかりやすく説明されています。

f:id:yohei-fujii:20191018195121p:plain

 NGのケースで、2つ目に挙げられているのがポイントですね。2つの要因を組み合わせないということ。確かに同時にやると、何が原因でテストが落ちるのかわかりにくくなります。この考え方は、普段実装する上で、不具合やエラーを探す時の考え方としても大切だと感じました。

デシジョンテーブル

そして2つ目が「デシジョンテーブル」つまり「意思決定表」です。

条件が複数ある場合に利用されるテスト技法です。

 

 「ソフトウェアテストの教科書」にあった例を参考に、説明して頂きました。

f:id:yohei-fujii:20191018195644p:plain

遊園地の乗り物などは、身長制限や年齢制限があったりもします。その際には、どのようにテーブルを作るのでしょうか。

f:id:yohei-fujii:20191018195739p:plain

まず、このように枝分かれした図をイメージします。今回は3つの条件があり、それぞれに対して、YesとNoがあります。つまり、8通りのケースが考えられます。

 

そしてそこからこの8パターンをテーブルに落とし込みます。

f:id:yohei-fujii:20191018200303p:plain

1番の人、つまり3つの条件を全てクリアした人が乗れる人ということになります。 

 

本来であればもっと複雑化していくと思います。他の例だと、会員種別やお金の割引率に関するものも紹介して頂きました。(「因子」や「水準」といった話も出てきましたが、長くなりますので割愛させて頂きます)

 

この説明を行いながら、このデシジョンテーブルを作るには仕様を把握していなければならないということを指摘されていました。。つまり「副次的効果として仕様確認ができる」という点がデシジョンテーブルにはあるということです。

 

テストケースの整理にもなるし、仕様把握の確認にもなるし、便利ですね!

さいごに

今回挙げてくださったテスト技法は、どのテストにおいても有効なものだということで、ご紹介頂きましたが、確かにわかりやすく、知っておいて良かったなと思うものでした。

 

最後に参考図書として、3つを挙げて頂きましたが、社内図書にもあるので、一通り読んでみようと思います。

  1. ソフトウェアテストの教科書
  2. はじめて学ぶソフトウェアのテスト技法
  3. ソフトウェアテスト技法ドリル

 

 

 

 

Amazon SageMaker のアノテーションツールを試してみる

こんにちは、開発部門インターンの飯沼です。この記事では機械学習導入の第1段階としてSageMakerのアノテーションツールを使ってみようと思います。

Amazon SageMaker とは?

Amazon Web Service の展開する機械学習プラットフォームです。Jupyter Notebook をインターフェースとして、機械学習モデルを構築、トレーニング、デプロイするためのモジュールが用意されています。Tensorflow や Chainer など、メジャーなフレーム枠をサポートしているほか、オリジナルのアルゴリズムフレームワークなどを組み込むことも可能となっています。また、S3と連携させることで、S3内のファイルでトレーニングすることもできます。

Amazon S3 にファイルをアップロード

最初に Amazon S3 にファイルをアップロードしておきます。S3 に適当な名前でバケットを作成して、その中にデータを入れておきます。設定は全てデフォルトのままで大丈夫です。

Amazon SageMaker でアノテーション

ラベリングジョブの設定

最初にラベリングジョブを作成します。SageMaker のトップから「ラベリングジョブ」→「ラベリングジョブの作成」と進んでジョブの詳細指定のページを開きます。

f:id:iinuma0710:20191024114922p:plain

ジョブの詳細設定

 「入力データセットの場所」にはアノテーションを行うファイルを書き出した .manifest ファイルを指定する必要がありますが、「マニフェストファイルを作成します」をクリックして、データが置いてあるフォルダを選択すると自動的に生成してくれます。

f:id:iinuma0710:20191024170644j:plain

manifest ファイルの自動生成

適切な権限がないとIAM ロールが作成できなかったり、S3 のファイルにアクセスできないと怒られたりするので、何かエラーを吐かれたら権限周りの担当者に相談するのがいいと思います。

最後に、タスクのタイプを指定します。ここではセグメンテーションがしたいので、「意味論的なセグメント化」を選択します。

f:id:iinuma0710:20191024134140p:plain

タスクのタイプ選択

 「拡張画像アクセスを有効にする」にチェックを入れて「次へ」をクリックします。

 

ワーカーの選択

ジョブの設定ができたら、アノテーションを行うワーカーの選択を行います。アノテーションは社内メンバーで行いたいのでプライベートを選択して、適当なプライベートチームを指定します。パブリックを選択してしまうと、外部の人にお金を払わないといけなくなります。

f:id:iinuma0710:20191024171114p:plain

ワーカーの選択

ラベリングツールの設定

最後にラベリングツールの設定をします。一番上のテキストボックスには、アノテーションの簡単な説明を追加します。「ラベル」のカラムには、セグメンテーションの対象となるラベルを追加します。

f:id:iinuma0710:20191024171303j:plain

ラベリングツールの設定

これで「作成」をクリックすればジョブの作成は完了です。

 

ラベリングツールでアノテーションする

ここまででラベリングジョブの作成はできたので、実際にラベリングしていきます。個人のラベリングのページにログインすると、先ほど作成したジョブがラベリングタスクのリストに追加されています。

トップページからジョブを選択して "Start Working" をクリックします。ラベリングツールが表示されたら Brush とか Eraser とかを使いながらラベルを貼りたいピクセルを塗りつぶしていきます。

Polygon:ぽちぽちクリックして塗りつぶしたい部分を囲んでやると囲んだ部分の内側を塗りつぶしてくれるツールです。広範囲を塗りつぶす時に便利。

Brush:色鉛筆のように塗りつぶすツールです。細かい部分や最後の仕上げに使えます。

Eraser:はみ出した箇所を消すのに使う消しゴムツールです。

Dimmer:明るさの調整をします。デフォルトが100%で数字が小さくすると画像が暗くなります。ただ輝度調整なんかは実際の学習時にした方がいいと思うので、あまり使う機会はないような気がします。

Undo・Redo:言うまでもなく作業を戻したり進めたりします。

Zoom In・Zoom Out:画像の拡大縮小をします。画像上で上下にスクロールしても拡大縮小できます。

Move:ドラッグして画像を動かします。拡大表示しているときに使います。

Fit Image:拡大 or 縮小表示中の画像をオリジナルサイズに戻してくれます。

f:id:iinuma0710:20191024171455j:plain

アノテーション作業

 

右の"Labels"からアノテーション対象のラベルを選択して塗りつぶします。全部塗れたら右下の"Submit"を押して、その画像のアノテーションを完了します。もし、対象としているラベルが画像中になければ、"Nothing to label"にチェックを入れて"Submit"します。これを用意したデータセットの分だけやっていきます。

思った以上に大変な作業です。1枚あたり10分くらいはかかりそうな感じがします。今のところ115枚のデータがあるので、単純計算して19時間以上かかりますね・・・。

ANDPADにおけるVue.js活用事例だよ

はじめに

どうもー、工程管理チームの藤井です。先週とあるイベントで登壇予定だったのですが、台風の影響により無くなってしまいました・・。致し方ない。。

 

せっかく作った資料ですし、こちらで公開させて頂きます! 

 

資料だよ

弊社におけるVue.jsの活用事例を紹介したものです。10分ほどの予定でしたので、短めに作ってあります。

 

フロント側をVue.jsに置き換えていくプロジェクトが進んでおり、その中からいくつか事例をあげました。「カスタムディレクティブを利用した遅延読み込みとnew IntersectionObserverの組み合わせ」や「ガントチャートを作る時の工夫」などです。

 

スライドだけでは伝わりづらい部分もあるかと思いますが、気になる点等ありましたら、Twitterでも気軽にDMしてください。

さいごに

スライドの最後でも触れていますが、Vue.js使って開発したい!っていう人いたらぜひ、お気軽に遊びに来てくださいねー。

 

 

SwiftUI + CombineでMVVM

はじめに

こんにちは、Octでスマホアプリの開発をしているzigeninです。 SwiftUIとCombineによるMVVMの実装のポイントを解説します。 ログイン画面とログイン後の画面があるだけのサンプルアプリを題材とします。

前提

  • Apple公式のSwiftUIのチュートリアルは大体やり終えている
  • RxSwiftを触ったことがある
  • MVVMを知っている

サンプルアプリのソースコード

https://github.com/KamikazeZirou/SwiftUI-MVVM

サンプルアプリの動作環境

サンプルアプリの画面構成

f:id:zigenin:20191016023457p:plain

※ユーザIDが"foobar@example.com"、パスワードが"password"のときのみログインは成功します。

サンプルアプリのクラス構成

f:id:zigenin:20191017143043p:plain

ViewはSwiftUIとCombineを両方使用します。
ViewModelとModelはCombineのみ使用します。
AnyPublisher, FutureはRxSwiftのObservableやSingleに相当します。

View

CotentView (ルート画面)

struct CotentView: View {
    @EnvironmentObject var session: Session

    var body: some View {
        VStack {
            if self.session.isLogin {
                HomeView()
                    .environmentObject(self.session)
            } else {
                LoginView()
                    .environmentObject(self.session)
            }
        }
    }
}

ログイン状態によって画面を切り替える

ログインはLoginView、ログアウトはHomeViewで行っており、ログイン状態はこれらのView内で更新されます。このとき、ログイン状態の変化を検知して、ContentViewに表示を切り替えたいです。

そこで、@EnvironmentObjectを利用します。 @EnvironmentObjectをViewのメンバ変数に付けると、複数View間で値の共有ができます。 また、値が変化したときに、View#bodyのgetterを呼んで表示も更新してくれます。

この実装例では、ログイン状態を保持するSessioinクラスのオブジェクトに@EnvironmentObjectを付けることで、やりたかったことを実現しています。

LoginView (ログイン画面)

struct LoginView: View {
    @EnvironmentObject var session: Session
    @ObservedObject private var vm = LoginViewModel()

    var body: some View {
        VStack {
            Text("Learning SwifUI-MVVM")
                .font(.title)

            TextField("User ID", text: $vm.userId)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .autocapitalization(UITextAutocapitalizationType.none)

            SecureField("Password", text: $vm.password)
                .textFieldStyle(RoundedBorderTextFieldStyle())
            
            if (!vm.validationText.isEmpty) {
                Text(vm.validationText)
                    .font(.caption)
                    .foregroundColor(Color.red)
            }

            Button(action: {
                _ = self.vm.login()
                    .sink(receiveCompletion: { completion in
                        print("receiveCompletion:", completion)
                    }, receiveValue: { user in
                        print("userId:", user.id)
                        self.session.user = user
                        self.session.isLogin = true
                    })
            }) {
                Text("Login")
            }
            .disabled(!vm.canLogin)
            
        }.padding()
    }
}

ViewModelの状態が変わったら表示を更新する

@EnvironmentObject, @ObservedObjectがポイントです。

    @EnvironmentObject var session: Session
    @ObservedObject private var vm = LoginViewModel()

@EnvironmentObjectをメンバ変数sessionに付けているのは、 ログイン時(session#isLoginをtrueにした時)に、 ContentViewの表示をLoginViewからHomeViewに切り替えるためです。

LoginViewModelに@ObservedObjectを付けているのは、 LoginViewModelのメンバ変数が変化したときにLoginViewの表示更新するためです(LoginViewModel#bodyのgetterが呼ばれる)。

ログインボタンの有効/無効の制御

            TextField("User ID", text: $vm.userId)
                ...

            SecureField("Password", text: $vm.password)
                ...

            Button(action: {
                ...
            }) {
                Text("Login")
            }
            .disabled(!vm.canLogin)

処理の流れ

  1. TextField/SecureFieldの入力値を変更する
  2. LoginViewModelはuserId/passwordの値の変化を検知
  3. LoginViewModelはuserId/passwordの両方とも空でないならLoginViewModel#canLoginをtrueにする
  4. canLoginが変化した場合、LoginView#bodyのgetterが呼ばれる
  5. Button#disabled(LoginViewModel#canLogin)により、ログインボタンの有効/無効が変化する

ポイント
TextField/SecureFieldの第2引数はBindingとなっていますが、ユーザ入力値の変化はこの引数に伝わります。 ここでは、第2引数にLoginViewModelのuserIdとpasswordをPublisherに変換したもの(CombineのクラスでRxSwiftのObservableのようなもの)を指定しています。それにより、LoginViewModelはTextField/SecureFieldの値の変化を検知できます。

canLoginの変化後に、bodyのgetterが呼ばれるのは前述の@ObservableObjectのおかげです。

ログイン

            Button(action: {
                _ = self.vm.login()
                    .sink(receiveCompletion: { completion in
                        print("receiveCompletion:", completion)
                    }, receiveValue: { user in
                        print("userId:", user.id)
                        self.session.user = user
                        self.session.isLogin = true
                    })
            }) {
                Text("Login")
            }
            .disabled(!vm.canLogin)

処理の流れ

  1. Loginボタンを押す
  2. action内のclosureが呼ばれる
  3. LoginViewModel#login() (AnyPublisherを返す)
  4. ログイン成功時には、AnyPublisher#sinkのreceiveValueが呼ばれる
  5. ログインの成否によらず、AnyPublisher#sinkのreceiveCompletionが呼ばれる

ポイント
AnyPublisherとsinkが重要です。これらは、RxSwiftでいえば、Observableとsubscribeに相当します。

補足
ログイン成功時、AnyPublisher#sinkのreceiveValue内でsession.isLoginをtrueにしています。これによって、親のContentViewのbodyのgetterが呼ばれて、bodyがLoginViewからログイン後のViewであるHomeViewに切り替わります。結果、ログイン後の画面が表示されます。

ViewModel

LoginViewModel

final class LoginViewModel: ObservableObject {
    // MARK: Private
    private let authProvider: AuthProviderProtocol
    @Published private var isBusy: Bool = false
    
    // MARK: Input
    @Published var userId: String = ""
    @Published var password: String = ""
    
    // MARK: Output
    @Published private(set) var canLogin: Bool = false
    @Published private(set) var validationText: String = ""
    
    // MARK: Action
    func login() -> AnyPublisher<User, Error> {
        isBusy = true
        validationText = ""
        
        return authProvider.login(userId: userId, password: password)
            .receive(on: RunLoop.main)
            .handleEvents(receiveCompletion: { [weak self] completion in
                switch completion {
                case .finished:
                    self?.validationText = ""
                case .failure:
                    self?.validationText = "Incorrect ID or password"
                }
                
                self?.isBusy = false
            })
            .eraseToAnyPublisher()
    }
    
    init(authProvider: AuthProviderProtocol = AuthProvider()) {
        self.authProvider = authProvider
        
        _ = Publishers
            .CombineLatest3($userId, $password, $isBusy)
            .map { (userId, password, isBusy) in
                return !(userId.isEmpty || password.isEmpty || isBusy)
            }
            .receive(on: RunLoop.main)
            .assign(to: \.canLogin, on: self)
    }
}

ViewがViewModelの値の変化を監視できるようにする

final class LoginViewModel: ObservableObject {

ObservableObjectというprotocolを付けます。 これで、LoginViewModelのメンバ変数に@ObservableObjectを付けられるようになり、ViewからViewModelの状態を変化を監視できるようになります。

ViewModelのメンバ変数を監視できるようにする

    @Published private var isBusy: Bool = false
    
    // MARK: Input
    @Published var userId: String = ""
    @Published var password: String = ""
    
    // MARK: Output
    @Published private(set) var canLogin: Bool = false
    @Published private(set) var validationText: String = ""

メンバ変数に@Publishedを付けることで、Publisher(RxSwiftのObservableのようなもの)としてもアクセスできます。具体的には、$<メンバ変数名>とするとPublisherとしてアクセスできます。このPublisherを使えば、メンバ変数の値の変化を監視できます。

ユーザID/パスワードが両方とも空でなければ、ログイン可能にする

    init(authProvider: AuthProviderProtocol = AuthProvider()) {
        self.authProvider = authProvider
        
        _ = Publishers
            .CombineLatest3($userId, $password, $isBusy)
            .map { (userId, password, isBusy) in
                return !(userId.isEmpty || password.isEmpty || isBusy)
            }
            .receive(on: RunLoop.main)
            .assign(to: \.canLogin, on: self)
    }

userId/passwordの値の変化を監視して、両方とも空でなければ、ログイン可能にします(canLoginをtrueにする)。

userId/passwordは別々のAnyPublisherになっていますが、 それらをまとめて監視するために、Publishers#CombineLatest()を使います。考え方はRxSwiftと同じです。

mapもRxSwiftと同じです。mapは、Streamに流れてくる値を変換します。

recieve(on:)は、RxSwiftのobserveOnと同じで、それ以降のStreamのスレッドを指定します。 ViewModelのメンバ変数の更新や外部への通知は、メインスレッドにしたいので、 RunLoop.mainを指定しています。

assign(to:on:)は、 最終的にStreamの値を代入する先を指定します。 RxSwiftでいえば、以下と同等です。

subscribe(onNext: { canLogin in self.canLogin = canLogin })

ログイン

    func login() -> AnyPublisher<User, Error> {
        isBusy = true
        validationText = ""
        
        return authProvider.login(userId: userId, password: password)
            .receive(on: RunLoop.main)
            .handleEvents(receiveCompletion: { [weak self] completion in
                switch completion {
                case .finished:
                    self?.validationText = ""
                case .failure:
                    self?.validationText = "Incorrect ID or password"
                }
                
                self?.isBusy = false
            })
            .eraseToAnyPublisher()
    }

いくつかポイントを解説します。

ログイン処理の本体は、Model層のAuthProviderに委譲しています。

ログイン完了時、成否に応じて、エラーメッセージ(LoginViewModel#validationText)を更新したいです。 LoginViewModel#validationTextを更新するのは、副作用を伴う処理です。 RxSwiftであれば、副作用を伴う処理を行う時、do(onNext:onCompleted:)を使うかと思います。 Combineでは、AnyPublisher#handleEvents()で同じことができます。

最後のeraseToAnyPublisherは、型をAnyPublisher<User, Error>にするためです。 Combineだとメソッドをチェーンするごとに戻り値の型が変わってしまいます。 ここの例では、eraseToAnyPublisherを呼ぶ前は、Publishers.HandleEvents<Publishers.ReceiveOn<Future<User, Error>, RunLoop>>という型になっています。 これをそのまま返すと扱いづらいのでeraseToAnyPublisherを呼んで、シンプルなAnyPublisher<User, Error>に変換しておきます。

Model

AuthProvider(ログイン/ログアウトを実際に行うクラス)

final class AuthProvider: AuthProviderProtocol {
    func login(userId: String, password: String) -> Future<User, Error> {
        
        return Future<User, Error> { promise in
            // This closure is unexpectedly called synchronously.
            // Therefore, wrap it with DispatchQueue.global().async
            DispatchQueue.global().async {
                // Intended to network communicate
                Thread.sleep(forTimeInterval: 1.0)
                
                 if userId == "foobar@example.com" && password == "password" {
                     promise(.success(User(id: userId, name: "foobar")))
                 } else {
                     promise(.failure(AuthError.invalidIdOrPassword))
                 }
            }
        }
    }
    ...
}

非同期 かつ バックグラウンドでログイン処理を行う

こういうケースでは、RxSwiftではSingleを使うと思います。Combineにも類似のFutureがありますので、それを使います。 Future<Output, Failure>という形式で、Outputは成功時にStreamに流す値の型、Failureは失敗時のエラーの型 です。 RxSwiftのSingleと同じく、1回だけ値が流れます。

注意点は、Futureのコンストラクタに渡したclosureは、Futureのコンストラクタから同期的に呼ばれるということです。 ですので、Futureを使って通信などを行いたいなら、直接closure内で通信するのではなく、DispatchQueue.global().asyncなどを挟む必要があります。

最後に

当面、業務ではSwiftUIとCombineを使うことはなさそうです*1。しかし、Octが標準的に使っているStoryBoardやRxSwiftが時代遅れになる可能性がもあったので、SwiftUI + Combineを学んでみました。

SwiftUIは、StoryboardでUIを組み立てるとソースコードでUIを組み立てる方法の良い所取りだと思います。ソースコードでUIのレイアウトをかけるのは気持ちが良いですし、コードレビューもしやすいです。その一方で、画面のPreviewを表示できるので、UIの組み立てが快適です。

現在、アンテナが高い先人たちがいるものの、SwiftUIとCombineの情報はあまりない印象です。そういった状況ですが、この記事が多少は誰かの役に立てば幸いです。

参考ページ

Using Combine
Combineについて、Apple公式のドキュメントより分かりやすいです!

*1:OctはBtoBであり、iOS 12以前を使用しているお客様もいるためです。また、現状のSwiftUI/Combineはいろいろ足りていない感が強いです

ANDPADは新社屋に引っ越ししましたよ!

はじめに

ANDPAD工程管理チームの藤井です。

本日は記念すべきオフィス移転初日でした!

 

今回は、新オフィスのご紹介をしようと思います!

 

当日の朝

slackでも連絡があり、注意が促されていました。

f:id:yohei-fujii:20191007193832p:plain


やはり習慣というのは怖いもので、無意識に前のオフィスに行った人もいたようですw

 

駅から徒歩1分そして、ヨドバシカメラの横ということでとてもわかりやすく全然迷いなく到着することができました。

 

f:id:yohei-fujii:20191007184019p:plain

どーーーん!! 

大きいな・・・。そして入口へ行くと見つけました!

「お、うちの会社の名前だ!」

 

f:id:yohei-fujii:20191007183618p:plain

この前の8月に竣工した新築ビルということで、まだ一社しか入っていないみたいですね。

 

エントランスも広くて、ビルを貸し切っている気分・・w

場所はどこなの?

秋葉原です!ヨドバシカメラの北側です!

前のオフィスよりも駅近になり、とても便利・・。

買い物も色々便利そうです!

 

中はどんな感じ?

ひとまずエレベーターを降りてもらうと廊下があります。

f:id:yohei-fujii:20191007183601p:plain

そして受付に行ってもらうと、タブレットがあるのでそこから担当者を呼んで下さい。

f:id:yohei-fujii:20191007184915p:plain

 

執務室側から見るとこんな感じ↓↓

f:id:yohei-fujii:20191007183608p:plain

 

会議室は前は4つほどでしたが、こちらの新オフィスでは10まで増え、会議室難民はいなくなると思われます!

 

f:id:yohei-fujii:20191007183605p:plain

なかなかシャレとるのぉ。

 

ちなみにトイレもパシャり。

f:id:yohei-fujii:20191007183557p:plain

男子トイレもかなり綺麗ですね!

*女子トイレも撮影したかったのはやまやまなのですが、引っ越し初日にトラブルを起こすわけにはいかず、自粛しました。

 

そして執務室!広いな、おい。

f:id:yohei-fujii:20191007183614p:plain

営業部まで遠いぞw 開発部なのでそこまで向こうの場所に関係はありませんが、せっかくワンフロアになったので、ちょくちょく雑談やちょっかいをかけに・・・いや、仕事や打ち合わせのために行きたいと思います。

 

さいごに

今日は少しゆっくり出社したのですが、既に会社に来ていたメンバーは涼しい顔して仕事に取り組んでいました。

 

私だけでしょうか。ソワソワしていたのは。

 

急に広いオフィスだから違和感はハンパなかったですww いずれ慣れるとは思いますが、しばらくは広いオフィスにワクワクしながら仕事してみようと思います。

 

それでも、1年もしたら人も増えて狭く感じるのかなぁとも思ったり。

 

実は8階だけでなく、9階もイベントスペースとして借りているので、エンジニアイベントなど行えます! (後日別で紹介記事書きますねー)

 

エンジニア採用もしているので、ぜひぜひ遊びに来たりもしてください!

 

 

社内でテスト勉強会を始めてみた

はじめに

ANDPADのiOSアプリの開発を担当しているzigeninです。 機能追加や不具合修正のかたわら、モバイルアプリのテスト基盤の整備やテストコードの追加をしています。 今月から、社内でテスト勉強会を始めてみました。

経緯

最近、Octでは、サーバサイド、モバイルアプリで、 自動テストを充実させていく活動が活発になっています。

ただ、自動テストをするといっても、 自動テストは品質保証の銀の弾丸ではありませんし、 闇雲にテストコードを増やしても効果は小さいです(却って負債にもなりかねません)。

ですので、単に自動テストのことだけを学ぶのではなく、 自動テストの限界、様々なテスト技法も含めて学ぶことが重要と考えました。

テスト関連の技術顧問として来て頂いているDeNAのSWETの平田さんにお願いし、 勉強会を開催して頂くことにしました。

勉強会の当日について

初回は「テストの重要性」「テストとは」、といったそもそも論がテーマでしたので、 5〜6名くらしいか集まらないと思っていたのですが、意外にも20名以上集まりました。 エンジニアだけでなく、カスタマーサクセスの方なども参加して頂けたことも意外でした。

f:id:zigenin:20190910191850j:plain
テスト勉強会(1回目)の様子
※紛らわしいですが、画像の左側のディスプレイ手前の方が講師の平田さんです。

今回の内容で印象深かったのは、テストの分類です。

  • Verificaiton
    • 仕様書のとおりにソフトウェアが作成されているかを確認すること
  • Validation
    • ユーザーの要求どおりにソフトウェアが作成されているかを確認すること

QA系でないエンジニアにとってのテストは、VerificaitonであってValidationではないと思います。 ただ、仕様どおりに動けば品質が良いという訳ではありませんので、Validationの考えはとても重要だと思いました。

現状のOctでは、QA専門の社員がいないこともあって、 Validationという観点のテストはあまりできていない印象です。 (筆者がValidationという観点のテストをしていないだけで、他の方々はテストできているのかもしれません)

最後に

思っていたよりも社内で需要がありそうでしたので、 勉強会は隔週〜毎月感覚で継続して実施することにしました。

この勉強会によって、プロダクトの品質保証を支援できればと思っています。

後、上にも述べましたがOctではQA専門の社員がいません。 そのため、QAの領域が弱く感じることがあります。 0からQAの体制を立ち上げてみたいという方がいましたら、一緒に働けると嬉しいです。

施工管理チームも真のスクラムを目指して

はじめに

開発部で施工管理チームのフロントエンド 開発をしている藤井です。

施工管理チームは主に、新築施工・リフォーム施工において、着工から完工までの「実際の工事が完了するまで」を効率的に管理するための機能を担当しています。

具体的な機能には、工程表・報告・検査などがあります。

 

施工管理チームの開発フローは、これまでウォーターフォールに近いものでした。

ウォーターフォール的な開発が、決して悪い訳ではありませんが、直近の開発においてチーム内でズレが生じてしまったことをきっかけに、スクラムの本格的な導入を検討することになりました。

 

弊社の新規事業チームでは、すでにスクラムが導入されており、それに倣ってみようと考えたのです。

(新規事業チームとは、ANDPADでまだサービス化できていない建築業界の新たなニーズに対応する部隊です) 

 

 チームが抱えていた課題

私たちのチームには2つの課題がありました。

 

1つ目は、開発工数がうまく見積もれなかったことです。

その結果、ある機能のリリースを延ばす事態になってしまいました。お客様に直接影響が出ることはなかったのですが、ビジネスサイドやカスタマーサクセスメンバーに迷惑がかかってしまいました。

 

2つ目は、開発メンバー自身も負担がかかかり「上から降ってきたタスクを消化しているだけに感じてしまう」という声が上がったことです。これまでも部分的に朝会などを取り入れてコミュニケーションを図っていましたが、正直、形だけになっていた感が否めません。 

 

これを機会に、本当に良いチーム・開発とはなんなのか、チームみんなでスクラムを通して考えることにしました。

スクラムを導入してみて、施工管理チームがやったこと・良かった点などを紹介します。

 

まずチームでしたこと

開始当初、「スクラム」のことをなんとなく分かっていても、具体的にどうしたらよいか真に理解しているメンバーはいませんでした。

そこで、まずチーム内で認識を合わせたことが2つあります。

 

1つは、「誰か1人がスクラムに責任を持つのではなく、メンバー全員が理解して取り組む」ということです。

そしてもう1つは「最初から完璧に理解して行うのではなく、やりながら全員で勉強していこう」ということです。

 

スクラムマスター1人が頑張って啓蒙しても、メンバーが協力しなければ、その人が疲弊して終わるだけです。何も改善には繋がりません。

この「共通認識の構築」が非常に良い効果をもたらしました。

 

あるメンバーはスクラムの図解イラストを引っ張ってきて共有したり、またあるメンバーは本を読んできて内容を共有したりといった、学び合う雰囲気が自然とできていったのです。 

 

参考にしたもの

今回我々が参考に使ったものは、とても少なく、イラスト1枚と本1冊です。

いろんな参考資料や書籍がありますが、全てを読み込んでからやるのではなく、やりながら理解していくために、あれこれと手を出しませんでした。

イラスト

www.ryuzee.com

スクラムの全体像を再確認するために、上記に掲載のイラストを何度も見て、参考にさせて頂きました。プランニング、レビュー、レトロスペクティブをどのタイミングでやるのか、メンバー全員で都度チェックしました。 

 

書籍

スクラムに関する書籍はいくつかありますが、そんな中で今回こちらの本が参考書となりました。

SCRUM BOOT CAMP THE BOOK

SCRUM BOOT CAMP THE BOOK

この本が自然と選ばれた理由は、読みやすさにあります。

スクラムの基本的な解説とあわせて、実際の開発を想定したストーリーが漫画で描かれており、とても読みやすく参考になりました。大事なところは丁寧な説明も入っており、何度も振り返りながら使っていく上で、使いやすさも感じました。

分厚い本だと全員が読むまでに時間もかかってしまいますが、スクラムに対して強いモチベーションがないメンバーでも簡単に読むことができたと思います。

 

新たに使う物品

付箋

今までJira上で管理していたissueチケットですが、付箋も使うことにしました。

付箋のメリットとしては、今何をやっているか、何が残っているかひと目で見えるということにあります。

f:id:yohei-fujii:20190909185040p:plain

付箋には、Jira番号、タイトル、担当者、そしてストーリーポイントを書きました。Jiraとの併用は面倒ではないかとも懸念がありましたが、メンバーそれぞれが分担して管理すればそうでもないことに気づきました。

また、タスクを完了した際に、物理的に付箋を移動することで、「終わらせた」という感覚が強く持てることもメリットです。大したことではないかもしれませんが、意外とこれがゲーム感覚になって達成感をより感じれます。

 

ホワイトボード

これまでもホワイトボードはメモ書き等で使っていましたが、今では物理カンバンとして利用しています。

f:id:yohei-fujii:20190909185037p:plain

またプランニングの際は、裏側を使用して、機能追加や修正の際には、タスクを小分けにするため書き出したりもします。

f:id:yohei-fujii:20190907160609j:plain

(タスク書き出し中の写真)

 

プランニングポーカー

ストーリーポイントをつけるために、さっそくこちらを経費で購入してもらいました。

 

 f:id:yohei-fujii:20190909185043p:plain

https://www.amazon.co.jp/dp/B0746T9LXW/ref=cm_sw_r_tw_dp_U_x_Ct0DDb6D2MC1T

 

カードにはフィボナッチ数列が振ってあり、タスクに対して自分が思う工数を出し合って、見積もっていきます。

もちろん、全員が同じ数字にはなりません。しかし、これは良いコミュニケーションをもたらしました。

 

Aさんはある理由で「5」だと考えるが、Bさんは別の理由で「13」だと考える。その違いを話し合うことで、PM・デザイナーも含め、メンバーの認識を合わせていきます。

この作業が白熱し、一見カードでわいわい遊んでいるように見えますが、真剣にそのタスクについての議論ができ、懸念点や難易度などの理解を深めることができました。

 

f:id:yohei-fujii:20190907160628j:plain

(スプリントプランニング中の写真)

 

効果はどんな感じ?

まだ初めて1週間ちょっとですが、メリットをすでに感じています。

それは「コミュニケーションが増えて、相互理解が深まった」ことです。

(ここでいう相互理解とは、PMと開発メンバー、そしてデザイナー間でという意味です)

 

これまでお互いに「彼はこんなタスクがあるんだろうなぁ」となんとなく把握している曖昧な状況でした。

しかし、一緒にスプリントプランニングを行い、他のタスクも洗い出すことでそれぞれが持っているタスクが明確になったのです。また物理ボードを使うことで今誰が何をやっているのかが一目でわかるという状況になりました。不思議とお互いの信頼度も上がったように感じます。

 

朝会の進め方も変わってきました。それまでは、1人の疑問点から議論が発展して、関係者だけが話し続ける、といったことがあったのですが、「それに関係あるのはAさんとBさんだけだから、朝会後に話そう」と場を切り分けるようになりました。 

 

さいごに

スクラムが走り出してわずかですが、今後もチームメンバーで話し合いながら改善を進めていきます。

また別の記事で、デイリースクラムにおいて工夫していることなどをまとめたいと思います。