開発

エンドツーエンドテスト(E2Eテスト)をTestCafeとAWS CodeBuildでもっともっと手軽にする

ラクスルの@nnm_techです。

ラクスルの印刷サービスではこれまでにもE2Eテストがありましたが、この度より良いシステムを目指し再構築することになりました。 本記事ではTestCafeとAWS CodeBuildを用いたE2Eテスト刷新について紹介したいと思います。

ラクスルで運用している印刷サービスは裏側で複数のサービスが協調動作しています。 入稿サービス、発注サービス、決済サービス…外部サービスとの連携も含めるとかなりの数があります。
このような中、弊社では各サービスの変更時におけるユースケースの動作担保・リグレッション検知を目的にE2Eテストを実施しています。

続きを読む

GoのおすすめDIツール

はじめに

これまでラクスルではRubyを開発言語として採用することが多かったのですが、最近はコマンドラインツールやバッチ処理などでGoによる開発も増えています。
私が最近取り組んでいる印刷発注基盤の刷新プロジェクトでもGoを使ってWEBアプリケーションを開発しており、社内では先例がないこともあって色々と苦労しながらも楽しく開発を進めています。
GoによるWEBアプリケーション開発では、RubyにおけるRailsのようなデファクトスタンダードは存在しないため考慮すべき点がたくさんあります。
例えば、「パッケージ構成をどうするか」「WEBアプリケーションフレームワークを使うべきか」などですが今回はちょっと軽めのテーマとしてDIツールについて紹介します。

DIとは

DIとはDependency Injectionの略で「依存性の注入」と訳されます。
Goではインタフェース型の値をコンスタラクタの引数などで渡すことでDIを実現することが多いと思います。
具体的なコードを見てみましょう。

type DogUsecase struct {
	repo DogRepository
}

func NewDogUsecase(dogRepo DogRepository) DogUsecase {
	return DogUsecase{
		repo: dogRepo,
	}
}

func (u DogUsecase) All() []Dog {
	return u.repo.All()
}

type DogRepository interface {
	All() []Dog
}

type dogRepositoryImpl struct{}

func NewDogRepository() DogRepository {
	return dogRepositoryImpl{}
}

func (r dogRepositoryImpl) All() []Dog {
	// DBからDogを取得
}

func main() {
	// インターフェース型の値を生成
	repo := NewDogRepository()
	// コンストラクタの引数として値を渡す
	usecase := NewDogUsecase(repo)
	usecase.All()
}

上記ではDogUsecaseのAll()メソッド内でDogRepositoryを使っています。
そして、コンストラクタであるNewDogUsecaseの引数としてDogRepositoryを渡しています。
ポイントは下記の2点です。

  • DogUsecaseの内部でDogRepositoryを生成しているのではなく外から渡している
  • DogRepositoryをインターフェースにしている

DogUsecaseDogRepositoryというインターフェースにのみ依存しており、その具象型(dogRepositoryImpl)には依存していません。
そのため、DogUsecaseの単体テストを書く時にDogRepositoryの具象型をモックに変更することが可能です。
また、DogRepositoryの具象型の実装に変更(データの取得元をDBではなく外部APIに変えるなど)があった場合でも、インターフェースが変わらない限りはDogUsecaseのコードを修正する必要がありません。
このように、DIを使うことによってDBや外部APIといった実装の詳細を利用者側から隠蔽し、モジュール間を疎結合にすることで保守性やテスタビリティを高めることができます。

DIツール(dig)の紹介

上述したコードではmain関数の中でDIを行なっていました。
今回のようなシンプルなコードの場合はこれでも問題ないのですが、アプリケーションが大きくなっていくとDIのためのコードが肥大化しメンテナンスしづらくなります
そこで、これらのコードを自動化するためのDIツールが役に立ちます。
GoのDIツールはいくつかありますが、現在私たちのプロジェクトではuber-go/digを使っています。
digの特徴は何といってもシンプルで学習コストが低い点です。また、少し複雑なDIもやろうと思えばできる柔軟性も備えています。
先ほどのコードの中でDIしていた箇所をdigを使って書き直すとこのようになります。

func main() {
  container := dig.New()
  container.Provide(NewDogRepository)
  container.Provide(NewDogUsecase)
  container.Invoke(func(usecase DogUsecase) {
    usecase.All()
  })
}

dig.New()でDIコンテナを作成し、Provideでコンテナに型を登録します。
Provideの引数は、「返り値として登録したい型を返す関数」です。
例えば、NewDogRepositoryは返り値としてDogRepository型を返す関数なので、Provideの引数として渡すことによってDogRepository型をコンテナに登録することができます。
登録した型の値を取り出したい場合には、Invokeを使用します。Invokeの引数は、「利用したい型を引数にした関数」です。
ここでは、DogUsecase型を使ってAll()メソッドを実行したいので、DogUsecase型を引数とする関数がInvokeの引数になっています。
注目していただきたいのは、container.Provide(NewDogUsecase)という行です。
NewDogUsecaseは引数としてDogRepositoryを必要としますが、この行ではDogRepositoryを渡していません。
一つ前の行のcontainer.Provide(NewDogRepository)によってDogRepository型をコンテナに登録しているため、Invokeの実行時には自動でNewDogRepositoryの引数であるDogRepository型の値をコンテナから取得することができます。

一見するとDIツールを使わなかったコードの方が短くてシンプルに思えるかもしれませんが、アプリケーションが成長してモジュールが増え依存関係も複雑になっていくと、手動でDIをする必要がなくコンストラクタの引数によって自動的にDIできることのメリットは大きくなります。

digの応用的な使い方

digを使った場合、基本的には型をベースにしてDIが実行されますが、時には同じ型で別の値を使いたいというケースもあると思います。
例として、MasterとSlaveのDBを同じRepository内で使いたいというケースを考えてみます。

func NewMasterConnection() (*sql.DB, error)
func NewSlaveConnection() (*sql.DB, error)

type DBConnection struct {
  dig.In

  MasterConn *sql.DB `name:"master"`
  SlaveConn  *sql.DB `name:"slave"`
}

func NewDogRepository(c DBConnection) DogRepository {
  return dogRepositoryImpl{
    master: c.MasterConn,
    slave:  c.SlaveConn,
  }
}

func main() {
  container := dig.New()
  container.Provide(NewMasterConnection, dig.Name("master"))
  container.Provide(NewSlaveConnection, dig.Name("slave"))
  container.Provide(NewDogRepository)
}

上記では、NewMasterConnectionNewSlaveConnectionProvideの引数として渡す際にdig.Nameを使って名前付けしています。
そして、NewDogRepositoryではDBConnectionという構造体のフィールドの中で`name:”master”``name:”slave”`といったタグを付ける事で同じ型(*sql.DB)の中でdig.Nameに対応する値を取得することができます。
このように、digは少し複雑なDIをする場合も柔軟に対応できます。
今回紹介した機能の他にもオプショナルな型であったり、同じ型の異なる値をグループ化したりといったこともできますので、詳しくは公式ドキュメントをご参照ください。

まとめ

  • ラクスルでもGoで開発している!
  • DIはモジュール間の結合度を下げて保守性やテスタビリティを高める
  • 手動DIはアプリケーションが大きくなるとつらいのでDIツールを使うのがオススメ
  • uber-go/digというDIツールが便利

社内ハッカソンの意義 〜Raksul Hack Week #2 開催から見えてきたこと〜

こんにちは。ハコベル(物流サービス)チームのサーバサイドエンジニア 加藤です。

このブログでもお伝えしていた第2回社内ハッカソン Raksul Hack Week #2。6月第2週に開催しました。今回は開催のレポートをしつつ、そこから見えてきたものについて書いてみたいと思います。

開催概要と今回のテーマ

2回目となる今回も、Raksul Hack Week #1から基本的な開催概要は変えずに企画しました。

  • 参加者はエンジニア、プロダクトマネージャー、デザイナーとする
  • ハッカソンの期間は1週間とし、期間中はハッカソン100%コミット、普段の開発業務はやらない(※ ただし緊急対応系は最優先で)
  • チーム制とする
  • 最終日に各チームは成果発表する
  • ラクスルの事業、ステークホルダーに関わることであれば何に取り組んでも良い

結果として、4日間の作業期間+1日の発表会&表彰式という日程で、2〜5名の16チームが参加する大イベントとなりました。運営は各事業部から集まったエンジニア・PM・デザイナーの4人で行いました。

そして今回のテーマは、

 

みんなで創る次の世界

〜好きなことから始まる挑戦〜

課題 ✖️チーム✖️技術

 

第一回の開催の後、様々な背景を持ったメンバーが増えて続けているプロダクトチーム。普段は業務に集中していて、視野を広げたり、チームを超えてお互いを知り合う時間が十分にとれないのが現状です。今回は、普段の仕事の範囲を超えて、お客様や社内の課題を共有し、チームと技術の力を使って、次の世界を実現するきっかけにしようという気持ちを込めました。

開催までの取り組み

今年は、以下のように開催2ヶ月前から次ごとにテーマを区切り準備を進めてきました。

  • 4月:アイデア月間
  • 5月:チーミング月間
  • 6月:Raksul Hack Week #2

この期間に、各事業部のビジネスメンバーからお客様の課題、自分たちの業務の課題やこんなのがあれば嬉しいといった声を聞くランチイベント Hack Week Lunch や、チームビルディングイベント Hack Week Pre-event を開催しました。

詳しくは、今年も Raksul Hack Week を開催します! や いよいよRaksul Hack Weekが始まります!をご覧ください。

いよいよ本番

まずは、初日のキックオフ。毎週の全社朝会の後、スケジュールや注意事項の説明を聞き、くじで発表順を決めていざ開始!

キックオフの様子

初日は、それぞれのチームで、4日間をどんな風に進めるか、課題を整理したりプランニングしたりする様子が見受けられました。

ホワイトボードを前にチームでディスカッション

進め方を決めたあとはそれぞれで作業を進めたり、ラクスルでもすっかり定番になった、ペアプロやモブプログラミングを取り入れて進めるチームやユーザ調査に出かけるチームもありました。

モブプロで作業を進めるチーム

連日、いつもより遅くまで作業を続ける姿が見られました。すごいコミットメントですね。

バイキングデスクで仲良く作業する2人

ベトナムからは今回は2チームが参加。

作戦を練り直すベトナムチームのメンバー

発表会

発表会は、参加メンバーはもちろん、弁理士の先生や、ボードメンバー、ビジネスメンバーも参加して行われました。持ち時間は、各チーム7分。お昼を挟んでの2部構成で行いました。

ネットワークがつながりにくいというトラブルがある中、それぞれ、デモまで行って聞き応えのある発表となりました。1週間の熱量が発表にもこもります。

チームみんなで発表

技術的にも、AI、IoT、モバイルアプリやフロント技術を使ったものや、組み合わせ方に工夫を凝らしたものなど、様々な取り組みが発表されました。どのチームも課題設定が素晴らしく、ボードメンバーからも賞賛の声がありました。

発表を聞き入るCEO 松本とCTO 泉

投票&表彰式

前回は、シールをボードに貼る形式で行われましたが、今回は集計のことも考えて Google Form を利用。一人2チームを選ぶ形式で投票を行いました。また、どこが素晴らしいと思って投票したのか、投票コメントも記入してもらうようにし、翌週に全チームにフィードバックしました。

受賞したチームや受賞は逃したものの評価が高かったチームを3チームほどご紹介します。

技術負債の可視化ツールに取り組んだのは、以前からこのブログにも度々投稿している、エンジニアマネージャーの二串とチームメンバー。ラクスルは以前から技術負債解消に果敢に取り組んでいますが、その取り組みをダイエットに見立てて、楽しく可視化しようとしたアイデアがみんなの心を掴みました。

技術負債可視化チーム

そして今回一位は、古参の岡田と新卒からラクスルにジョインしている3人のメンバーのチームでした。

印刷マッチングプラットフォームであるラクスル。その重要なテーマの1つである、発注周りの改善提案に取り組みました。

ビジネスメンバーが思いつかなかった課題にテクノロジーを使って切り込み、見事な改善提案で、みんなをあっと言わせました。

リーダーシップをとったのは、3年目の岸野。「以前から個人的にやるべきだと思っていた課題。Hack Week という機会に取り組むことができました。」とコメント。ジョブローテーションをしながら、確実に成長している3人の今後がますます楽しみになりました。

CEO松本と1位のチーム

ちなみに私のチームは、CEO賞を受賞しました。ハコベル(物流サービス)の技術スタックを横展開し、他事業で数年前からの課題を解決するWebサービスとアプリを開発しました。運営しつつも、今回のテーマ「みんなで創る次の世界」を体現したいと思い、CTOの泉、アプリエンジニアのライアン、デザイナーの武末とともに、トライアル運用を目指して取り組みました。

未来を見つめるCEO賞受賞チーム

ここで紹介しきれませんが、本当にどのチームの成果も素晴らしく、今後どのようにするのかは継続議論がされることになりました。

懇親会で締めくくり

みんな全力投球したHack Weekを終えての懇親会は、それぞれ取り組んだテーマの話で盛り上がりました。また、期間中のエピソードもたくさん聞くことができました。

懇親会で挨拶するCTO

Raksul Hack Week #2 開催から見えてきたもの

Hack Week後のアンケートを通じて、参加メンバーからたくさんの声をいただきました。

「Hack Week Lunchもラクスル自体の理解度解像度が上がり良かった」

「普段あまり関わりの無いメンバーと開発でき、充実していた」

「ゼロからのプロダクトに少人数で考えて向かうという、実は普段あまりチャンスが無いようなことができたことにとても価値があったと思う」

「新しい技術にトライできて勉強になったし楽しかった」

「自分自身の課題も見えたり、メンバーとの交流できて、良い経験が得られたなと思った」

 

これらの声や運営している中での気づきから、ラクスルにおける社内ハッカソンの意義を私なりにまとめてみました。

  1. 学びの機会
    – 技術だけでなく、ビジネス課題に視野を広げる
    – 課題設定、プランニング、ゼロイチの開発を自分たちで行う力を養う(広義の開発力の向上)
  2. コミュニケーションの機会
    – 成長しているエンジニア組織でのチームを超えたコミュニケーションの促進
    – お互いを応援・賞賛しあう文化づくり
  3. 挑戦の機会
    – 新しい技術への挑戦
    – 結果が見えないテーマに挑戦(失敗できる機会)
  4. ビジョンの実現の加速の機会
    – 動くものを見せることで未着手のテーマに本格的に取組むきっかけにする

Hack Week はさながら冒険の旅のようでした。出発前の戸惑いあったり、チームで課題を乗り切る場面があったり。翌週出会ったみんなは、心なしか一回り力強くなったように感じました。

表彰式後の集合写真。何のポーズかな??

1週間業務を止めて行う社内ハッカソンの開催は、それなりの投資。決定するのにも勇気が要りますし、運営チームや参加者を含めて準備もあります。でも、上記にまとめたように、今回それ以上に意義のあるイベントだと思いました。

エンジニア組織の活性化をお考えの方は、社内ハッカソンに取り組んでみてはいかがでしょうか。

ラクスルでは事業をつくっていきたいエンジニアを絶賛募集しています!

自分の書くコードで、印刷・広告・物流といった大きな産業の課題を解決するようなインパクトを作り出してみませんか。技術・業界・業務など多角的に理解を深めながら、自らアイデアを出し、主体的に事業をつくっていく。そんな働き方をしたい方をお待ちしています!

ハコベルチームはHack Week後から五反田の新オフィスで勤務。新オフィスにもぜひ遊びに来てください!

gRPC Client Interceptor入門 with Ruby

こんにちは。サーバサイドエンジニアの三瓶です。印刷ECサイトの ラクスル の開発保守を担当するチームに所属しています。

ラクスルでは Raksul Platform Project (RPP) と称して技術負債の返済活動を継続的に行っているのですが、その流れの一環として印刷ECチームでは巨大化したアプリケーションから商品仕様に関わる部分を別サービスとして切り出す、という作業を最近行いました。

この切り出したサービスでは通信方式として gRPC を、言語としてはRubyを採用しています。

実際に導入してみて、gRPCはまだドキュメントも多くはないと感じたので、本記事ではgRPCが備える機能の1つである Interceptor についてチュートリアル形式でご紹介したいと思います。言語はもちろんRubyを使います。

※ gRPC導入に至った背景や経緯について興味のある方は弊社エンジニアの二串のスライドをご参照ください

Interceptorとは何か

Interceptorとは、RPCコールの前後に任意の処理を差し込める機能・レイヤーのことです。Railsアプリケーションに馴染みがある方は Rack Middleware のようなものを想像してもらうと理解しやすいかと思います。

Interceptorを使えば、認証やロギング、データのフィルタリングなど各通信で共通の処理をひとまとめにできます。
実際に、我々のプロジェクトではリクエストを一意に特定するためのIDをmetadataに付与するInterceptorやリクエストしたメソッド・パラメータ・処理時間をログに残すInterceptor、例外をCatchしてSentryに通知するInterceptor等を自作して、本番環境で稼働しています。

以下はClient → Server間の通信に2つのInterceptorが介在したときのイメージ図です。

作成するClient Interceptorの仕様を決める

ClientのInterceptorと一口に言っても、以下のパターンが考えられます。

  • リクエスト処理の「前」に差し込む。リクエストデータに何か手を入れたい場合など
  • リクエスト処理の「後」に差し込む。レスポンスデータに何か手を入れたい場合など
  • リクエスト処理の「前後」に差し込む。両方に手を入れたい場合や処理全体に何かをしたい場合など

このうち、今回はリクエストの「前」に差し込んでリクエストデータを大文字化する UpcaseInterceptor と、リクエストの「前後」に差し込んで処理時間を計測する PerformanceLoggingInterceptor の2つを作ってみることにします。

以下、それぞれのインターセプターの簡単な仕様です。

  • UpcaseInterceptor
    • リクエストの前に差し込む
    • リクエストオブジェクトに name というキー名のデータあればその値を大文字化する
  • PerformanceLoggingInterceptor
    • リクエストの前後に差し込む
    • リクエスト全体にかかった時間を計測し、標準出力へ出力する
    • 時間は小数点第3位以下を四捨五入する

また、動作の流れは次の図のようになります。赤い線の箇所でインターセプターの処理を挟みこむイメージです。

Client Interceptorを実装する

※ 注意:gRPCのRuby実装では、IntercptorのAPIインターフェースは EXPERIMENTAL という扱いのようです。そのため、今後のバージョンアップで今回紹介するものとはAPIインターフェースが変わっている、ということがありえますのでお気をつけください。

Interceptorを実装するのはとても簡単で、今回の仕様であれば数行で作れてしまいます。

UpcaseInterceptor

UpcaseInterceptorの実装は次のようになりました。

# upcase_interceptor.rb
class UpcaseInterceptor < GRPC::ClientInterceptor
  def request_response(request: nil, call: nil, method: nil, metadata: nil)
    request.name = request.name.upcase
    yield
  end
end

いくつかポイントとなる部分を説明します。

(1) GRPC::ClientInterceptor を継承する

インターセプタークラスは GRPC::ClientInterceptor クラスを継承する必要があります。
継承しない場合、GRPC::InterceptorRegistry::DescendantError 例外が発生します。

(2) 通信方式に合わせてメソッドをオーバーライドする

メソッドは利用する通信方式に合わせてオーバーライドします。gRPCには以下4つの通信方式があり、それぞれに対応するメソッドがあります。

  • Unary RPC : #request_response
  • Server streaming RPC : #server_streamer
  • Client streaming RPC : #client_streamer
  • Bidirectional streaming RPC : #bidi_streamer

今回は、1リクエストに対して1レスポンスが返ってくる一番シンプルな通信方式であるUnary RPCを使うため、request_response メソッドをオーバーライドしています。

※ 各通信方式の詳細が気になる方は https://grpc.io/docs/guides/concepts/ をご参照ください

(3) yieldで次の処理を呼び出す

次にメソッドの中ですが、yield を呼び出すことで次のインターセプターまたはリクエスト処理の本体を呼び出すことになります。
UpcaseInterceptorはリクエスト実行の前に呼び出したいので、yield をコールする前に変換処理をしています。

PerformanceLoggingInterceptor

同様に、処理時間を計測して出力するPerformanceLoggingInterceptorは次のようになりました。

# performance_logging_interceptor.rb
class PerformanceLoggingInterceptor < GRPC::ClientInterceptor
  def request_response(request: nil, call: nil, method: nil, metadata: nil)
    start_time = Time.now.to_f
    resp = yield
    request_time = Time.now.to_f - start_time

    puts "duration: #{request_time.round(3)} sec"
    resp
  end
end

PerformanceLoggingInterceptorはリクエスト処理の全体を計測したいため、リクエストの前後に処理を差し込みます。
前述のとおり、リクエスト処理は yield を呼び出すことで伝播されていくため、その前後を囲う形で時間を取れば良いということになります。

返り値は yield で受け取ったレスポンスオブジェクトをそのまま返します。

Interceptorを使う

それでは、作成した2つのInterceptorを実際に使ってみましょう。
gRPC公式のチュートリアル Ruby Quick Start にあるHelloWorldアプリケーションに、今回作成したインターセプターを組み込んで動かしてみます。

Client Interceptorは、Stub と呼ばれるクライアントオブジェクトを初期化するときに引数として渡します。

# greeter_client.rb
require 'grpc'
require 'helloworld_services_pb'

# ★ 作成したインターセプターをrequireする
require_relative './performance_logging_interceptor'
require_relative './upcase_interceptor'

def main
  stub = Helloworld::Greeter::Stub.new(
    'localhost:50051',
    :this_channel_is_insecure,
    interceptors: [UpcaseInterceptor.new, PerformanceLoggingInterceptor.new] # ★ ここでインターセプターを設定する
  )

  # 省略..
end

main

interceptors 引数に渡す順序でインターセプターの実行順序が決まります。
配列の末尾から先頭へと順に実行されるため、上の書き方の場合、PerformanceLoggingInterceptorUpcaseInterceptor の順で実行されることになります。当初のイメージ図の通りの順序ですね。

それでは実行してみましょう。
まず、比較対象としてインターセプターを使わなかった場合の結果を見てみます。

$ bundle exec ruby greeter_client.rb
"Greeting: Hello world"

チュートリアルの通りであれば、上のように “Greeting: Hello world” という結果が出力されると思います。

次にインターセプターを有効化した状態で実行してみます。

$ bundle exec ruby greeter_client.rb
duration: 0.002 sec
"Greeting: Hello WORLD"

UpcaseInterceptor によって “world” が大文字化されて、PerfomanceLoggingInterceptor によって実行にかかった時間が出力されました!

まとめ

以上、簡単ではありますが、RubyでのgRPC Client Interceptorの仕組みや使い方についてのご紹介でした。

アプリケーションを本格的に運用するにつれて様々な要求が発生してくるかと思いますが、Interceptorが役に立つ・解決手段となるケースもあるのではないかと思います。そのようなときにこの記事が参考になれば幸いです。

ラクスルテレビCMオンラインストアの初期開発の進め方

こんにちは。フロントエンドを担当している古谷です。

今回は、ラクスルテレビCMオンラインストアを開発開始からリリースするまでの進め方についてご紹介します。

こちらの記事は、以前行われた 【ラクスル×KCF】FrontendNight というイベントにて 本編には惜しくも入れられなかった部分を中心に構成しています。

イベントで発表したこちらの資料もあわせてご覧ください。

Storybookを使って安心しながら開発を進める ラクスルテレビCMオンラインストア開発

こちらの資料では、「進める際のリスクがなにかをしっかりと想定すること、そのための対応手段の選択肢を増やすために技術力をあげていくことが大事。」とまとめています。

この発表では主にツール、技術の選定にフォーカスして発表していました。
この記事では、進め方について、実際のPRの画面を交えながら紹介していきます。

速度を保ちつつ開発する進め方

こちらも、上記資料と同じくどんなことが起きたら開発がうまく回らないかを考えていました。

以下の不安があり、それらに対して対策を打っていました。

  • 技術選定はうまくいっても速度が出ないのではないか
  • 仕様や実装の変更による手戻りが多いのではないか

速度が出ない問題にはどう対処したか

基本的なことではありますが、PRの単位を小さく保つこと、そして丁寧にすることがあげられます。

こちらが実際のPRです。

(※ 小さくとかいてあるわりに、Files changed が15なのは、Snapshotで多くのコンポーネントに影響が出ているからです。)

PRのテンプレートを作った

おそらく多くのプロジェクトで行っていると思いますが、PULL_REQUEST_TEMPLATE.md の形式を整えます。 チケット番号、実装したものの概要は書くとして、それ以外になぜその実装にしたのかという理由を書くことが大切です。

また、(実装者が想定している)影響範囲を書いておくことで、全体把握をしているという信頼関係の構築がしやすくなると思っています。 リリース前だからこそできることですね。

## 概要

簡潔に何ができるようになるかを書きます。
また、その背景や対応するチケットがあれば貼ってください。

その実装にした理由をコミットメッセージに加えてこちらに書いてください。
その他、レビューを厚めにしてもらいたい点があれば書いてください。

## 影響範囲

Storybookのスクリーンショットなどわかりやすいものがあれば貼ってください。
Propsの変更など大きな変更があれば書いてください。

## TODO

* [ ] storybook
* [ ] このPRをmergeするために必要な残タスクがあれば列挙します

## このPRでやらないこと

このPRでは対象外にすることを書きます。
チケット、ISSUE、FIXMEを使って管理してください。

スクリーンショットを乗せるようにした

フロントエンドの開発では、スクリーンショットを乗せることでPRを中心にコミュニケーションが回ります。 スクリーンショットを乗せることで、仕様に沿った動きをしているかどうか、挙動の認識があっているかを一目でわかることが大切です。

レビューイはスクリーンショットを撮るために、当然手元で実装物を再現させる必要があります。

動作確認してからPRを出すのは当然ですが、複数パターン考えられるものを撮ったりするうちに、考慮漏れに気づいたり、モバイルサイズにした時に思わぬ挙動を見つけたりするのではないかと思います。私もこの段階で考慮漏れに気づくことがありました。

手元での動作確認は基本の所作ですが、 スクリーンショットを撮るということをフローに入れるだけで、うっかりは少なくなるはずです。

レビュアーも手元で再現させるのは当然ですが、PRで見るべき箇所がわかりやすいのでレビューしやすくなります。

やらないことを明記した

開発期に関しては、PRをためてしまい脳内バッファを消費することがないように、多少中途半端であってもmergeしてしまうことが大切と考えます。

また、完成度の高い物を作っても直前に変わる可能性もあるため、過度な作り込みは厳禁です。

とはいえ、中途半端なものがリリースされてしまっては問題なので、PRに明記することとチケットやコード内 FIXME などで残すようにしています。

ここで、想定している「ここでは積み残している事」を共有する事で、レビュー時に実装者の考慮漏れを見つけることができますし、積み残しを個人のタスクからチームのタスクに開放することが出来ます。

手戻りを発生させないようにどうしたか

手戻りの原因は沢山あると思いますが、一つはエンジニア以外とのコミュニケーション不足で起こるものがあると思っています。

これに関しては、PRにスクリーンショットを貼ることである程度対応ができました。

PRに貼るスクリーンショットをアニメーションGifにしておくと、連携したチャットツール(Slack)にPRに貼られた画像が流れます。Slackに動く画面が流れれば、エンジニア以外も実装している内容に関して興味が持てるようになります。

このPRの動くスクリーンショットと、Slackの絵文字コミュニケーションとあわされば、実装中のものに対して誰でもリアクションが出来る状態が保たれます。


※ 価格データなどはランダムなものを使っています
※ 実際のSlackの画像を編集しています

これによって成果物をベースとしたチーム全体の認識共有に繋がったと思います。 対面での進捗報告や、確認環境へのデプロイも大切ですが、もっと周囲がフォローしやすい形に持っていく事で、実装の認識の齟齬を発生しにくくすることができました。

まとめ

ラクスルテレビCMオンラインストアの初期開発では、PRをこまめに出すことと動くスクリーンショットを貼ることを心がけました。

まわりの関心を引きつけつつ、抜けもれなく開発を進めていました。

フェーズやチーム構成によって最適な進め方は違うと思いますが、参考になると幸いです。

ラクスルでは、新サービス開発をリードするフロントエンドエンジニアを募集しています。

 

ハコベルコネクトをリリースしました!

サーバサイドエンジニアの加藤です。
昨年8月に印刷から運送マッチングサービスのハコベルのチームに異動し、ハコベルコネクトの開発を進めてきました。

ハコベルコネクトとは

現在の物流業界における最大の課題はドライバー不足。ハコベルチームでは、現場の課題を調査し、その原因を探ってきました。見えてきたのは、複数の運送会社によって仕事が行われていることで、システム化が進まず紙・電話・FAXなどアナログで生産性を上げにくい現場環境でした。

ハコベルコネクトは、この情報断絶を解決すべく、大手物流荷主、一般貨物事業者など運送会社を自由度高くつなぐ物流プラットフォームを目指しています。

先月 1月24日に記者発表をし、日経新聞やTechCrunchにも取り上げられました。弊社CTOの泉がスマホのGoogle Chromeの新規タブで出てきたとのことで、社内でもちょっとした話題になりました。

今までも提供してきたサービスは、ハコベルカーゴ※に名称変更。これからもラストワンマイル向けのマッチングサービスを提供していきます。

仕事をする人に心地よい体験を

ハコベルコネクトは、PMやデザイナー・エンジニアが現場に足を運び、MVPを開発、実証実験を重ねて、徐々にその姿を明らかにしてできてきたプロダクトです。いくつもの運送会社の事業所に出向き、たくさんの方々にインタビューさせていただいたり、トラックに同乗させていただいたり。仕事をする人がどんな課題と向き合っているのか。様々なプロセスを経て、方向性が定まってきました。

ドライバーアプリイメージ

 

ハコベルコネクトという名前もその中で生まれてきました。SNSのように、会社と会社がつながりスムーズに情報のやりとりができることで、快適に仕事が出来るようにという想いが込められています。

ハコベルコネクトイメージ

技術負債の芽を最低限に ー今しかないでしょ!ー

さて、プロダクトとしてのリリースに向け、エンジニアとしてはどんなチャレンジがあったでしょうか。

  • 実証実験向けに急いで作られた側面があったため、既に技術負債気味なところがある
  • 業界独自の業務をモデリングしているため、キャッチアップしにくい
  • よくある機能でも複雑度が高く、要件の不整合が起きて開発が進みにくい

ハコベルチームには、私を含めラクスルの開発に関わってきたメンバーもいます。

「そうだ、ラクスルを作り直そう!」という投稿や「生まれ変わらNight -技術的負債からの一発逆転-」というイベントでご紹介させて頂いた通り、ラクスルは技術負債の課題に向き合ってきました。

(ちなみにまだまだ先はありますが、経営陣の理解もあり、ラクスルの技術負債の解消は着々と進んでいます。)

その教訓を活かして、ハコベルコネクトでは、リリース前に負債の芽の解消にも時間を使ってきました。リリース前ならば、ユーザー影響を気にせずにあらかじめアーキテクチャの整備を進めることが出来ます。「今しかないでしょ!」を合言葉に、気なっていたところはどんどん書き直しました。

ただし、特定業界向けのSaaSというサービスの成長特性を考えて、最初からドメイン分割やマイクロサービスを採用することはしていません。ラクスルで蓄積してきているベストプラクティスを取り入れ、まずはRails アプリケーションとしての Rail にきれいに乗るというところに集中しました。

フロントは、ラクスルの他のプロダクトでも採用しているVue.jsを使い、実証実験後にUIをフルリニューアル。Web APIが必要だったため、コントローラやサービスは約80%を書き換えました。

Web APIの開発中には、GraphQLが話題になったり、gRPC-Webが正式リリースになったりしたタイミングで、チーム内でGraphQLのPOCも行なったりしていたのですが、開発の状況を考慮して今回はオーソドックスにRESTful APIで開発していくことにしました。

フロントメンバーとのコミュニケーションやAPI エンドポイントの可視化のため、API仕様はSwaggerで記述することにしています。

チームでプロダクトを開発する ーペア&モブプロ、デザインスタジオー

先ほども書いた通り、ハコベルコネクトは、業界独自の業務をモデリングしているためキャッチアップしにくくチームの生産性がなかなか上がりにくい状態でした。

6ヶ月間でチームも倍増。新メンバーのキャッチアップスピードをあげたり、モチベーションを保ちつつ機能開発スピードを上げるのが重要課題になっていました。

そのため、複数人でコーディングするモブプログラミングやペアプログラミングを取り入れて、難しいドメイン知識を共有しつつ開発を進めたり、モチベーションが上がりにくい機能開発をわいわいと議論しながら進めました。

モブプログラミング用のスペースも導入され、今ではハコベルだけでなく他のチームで取り入れています。

モブプログラミングの様子

また、ラクスルの他のチームで行なっていた、コラボレーティブデザインの手法である「デザインスタジオ」も取り入れました。機能に対して色々なアイデアが出るのはもちろん、デザイナーも含めてこれから作る機能のデータ構造を共有したり、機能の背景を理解するのに役に立っています。

ハコベルチームには、運送業界出身のメンバーもいます。要件がわからない時はすぐに話を聞けるのも、チームで開発を進める上ですごく助けになりました。

新メンバー募集中です

さて、ハコベルコネクトはリリースしましたが、私たちの毎日を支えてくれている物流の現場には課題がたくさんあります。課題を解決すべく、ハコベルコネクトも成長させていきたいと思っています。

「仕組みを変えれば、世界はもっとよくなる」

エンジニアリングの力で世界を変えたいフロントエンドエンジニア、サーバサイドエンジニアを絶賛募集中です。興味を持たれた方は是非一度オフィスに遊びにきてください!

※ 2019/3/4 ハコベルマッチングからハコベルカーゴに再名称変更したためブランド画像更新しました。

MeetUpシールがとことん簡単に作れるプロダクトの話

突然ですがシールの作り方、わからなくないですか!?

ラクスルなら誰でも簡単にデザインできるサービス担当の武政です。

弊社でもミートアップや勉強会がよく行われていますが
企画にステッカーがあると一体感がでてテンションも上がりますよね。

でも忙しい中でデザイナーさんにお願いするのは申し訳ない…外注だと予算もかかる…
そんなお悩みの声に応えて、パパッとステッカーを制作できるプロダクトをリリースしました!

オンラインデザインのシール・ステッカー・ラベルです。

  • 円形、正方形、長方形が合計9サイズ
  • シール・ステッカーのテンプレートも約300点公開(2018/11現在)

MeetUp用のシールを作ろう!

私もオンラインデザインで、リリース記念のシールを作ってみました

MeetUp用シールの無料デザインテンプレートも公開!

ミートアップですぐ使えるテンプレートも用意しました!
ぜひロゴやテキスト入れて遊んでみてください。
※画像をクリックするとオンラインデザインに遷移します

①ミートアップ:おしゃれ  (円形 直径50mm)
円形はPCに貼るには定番ですね

テンプレート編集はこちらから>>

②ミートアップ:シンプル (正方形 50*50mm)
ロゴを置いて、背景色をロゴ内の色に変えるだけでそれらしくなります

テンプレート編集はこちらから>>

シールでこんな遊び方も!?

少年時代に熱中したキラキラ風シールも作れちゃいます。
③キラキラ風シール:※ホログラム加工ではありません (正方形 50*50mm)

テンプレート編集はこちらから>>

シールになった弊社CTOもニッコリ。

シール制作プロセスの何をシステムで解決したのか?

シールのデータ制作はコンテンツとしては驚くほど簡単ですが
制作データの構造が難しいという不思議な商品です。

  • 具体的に難しいポイントは「カットパス」データの作成
  • カットパスとはシールの形に切り抜くためのパスデータのこと
  • カットパスと聞いた瞬間に印刷業界以外の方は「んんん?」となりますよね

デザイナーさん以外にはこの「カットパスの理解と制作」が難しい。
これがシール作成における「できる/できない」を決定づける障壁です。


システムが解決したシール制作者の課題は「カットパスをつくる必要がない」という部分です。
これには制作が簡単になることに加え、制作作業の大幅な短縮効果があります。

このようにしてシールが簡単に作ることができるプロダクトができました。
ぜひお試しください!
https://raksul.com/online-design/sticker/

ラクスルではエンジニアを積極採用しています

ラクスルは画像処理や印刷技術、SVGに関わる開発ができるレアな環境です。
興味を持たれた方は是非一度オフィスに遊びにいらしてください!

1週間やる、楽しい社内ハッカソンの作り方。

RakSul Hack Week Logo

はじめに

こんにちは。ラクスルプラットフォームチームのエンジニアの二串です。

先日、ラクスルではRaksul Hack Week #1という1週間通しでやる全員参加型の社内ハッカソンを開催しました。その運営を担当しまして、本記事では1週間やる楽しい社内ハッカソンの作り方をご紹介します。

開催の背景

もともとラクスルのエンジニア向けの制度としてHack-It Dayという月に1日、自由なテーマで開発することができる日がありました。もちろん活用はされていたものの、1日では満足いくものが作れなかったり、日々のリリースを優先してしまったり…といった課題も見えてきていました。そこで今回「Raksul Hack Week」に名称を改め、皆で1週間集中して面白いプロダクト・サービスを作ったり、新しい技術チャレンジをしたりする機会をつくろうと考えました。

第1回目となる今回のテーマは「HACK THE SYSTEM, HACK THE WORLD. (仕組みをハックすれば、世界はもっとカッコよくなる)」です。1週間通しでハッカソンをやる、というのは個人的に(そして他のエンジニア達も)初めての体験でありましたが、結果として自分たちの設定したテーマに全力を注ぐことができてよかったと思います。取り組んだ内容は記事後半の方で紹介します。

イベント概要

どんな感じのイベントだったのか?

  • 参加者はエンジニア、プロダクトマネージャー、デザイナーとする
  • ハッカソンの期間は1週間とし、期間中はハッカソン100%コミット、普段の開発業務はやらない(※ ただし緊急対応系は最優先で)
  • チーム制とする
  • 最終日に各チームは成果発表する
  • ラクスルの事業、ステークホルダーに関わることであれば何に取り組んでも良い

開催週の月曜日が祝日のため実際には前3日間開発、4日目成果発表会となりました。開催場所は企画段階で社外のコワーキングスペース等を借りるという案も出ましたが、初回ということで慣れたオフィス内開催としました。

準備

参加者の自発性を重視する形で、チーム構成は自由、また取り組む内容もチーム毎に自由に決定可能としました。また、イベント開催1ヶ月以上前から、各自で取り組みたいことを考えてもらってアイデアを膨らませてねーとお願いし、少しつづ Hack Week を盛り上げることにしました。

今回やったチームを決めるまでの流れは次の通りです。

  1. まず、社内(非参加者方面)からネタ(お困り毎など)を募集する
  2. 1を参加者に展開してアイデアの種にしてもらう
  3. 参加者は各自やりたいことをシェアする(専用confluenceページに書いてもらう)
  4. 出たやりたいことに賛同した人たちでチームを組む、もしくは3の起票者がメンバをリクルーティングしたりして組んでもらう
  5. チームを決めかねてる人たちはシャッフルで決定。取り組み内容は3を参考に決めてもらう
  6. Google Formでチームエントリ

全チーム構成が決まったのが開催の1週間前でした。この時点で大体のチームがやることもざっくり決めれている状態でした。

普段の業務に近いメンバーと組んだチームもありましたが、結構普段組まないメンツでの構成のチームもあり、結果的には何が出来上がるかな〜と期待が膨らんでくるチーム編成となりました。

ステッカー作りました(もちろんラクスルで作りました)

ハッカソン当日

ハッカソンが始まってしまえばあとは各チームもくもくと作業するだけです。社内の様々な場所で開発が行われました。

ちなみに、私は運営ではありましたがチームの一員としても参加しており期間中はがっつりと開発してイベントを楽しむこともできました。事前の準備が大事! 普段はサーバサイドの開発がメインですがHack Weekでは違う技術をということでTypeScriptNuxt.jsでフロントエンド中心のプロジェクトをやりました。難しかったですが勉強になりました!

ちなみに、1日の終わりに各チーム進捗報告をSlack #hack_week チャネルにしてもらうようにしました。進捗サマリとともに開発風景やスクショ等を共有してもらうことで、チーム間でもコミュニケーションが生まれたり、ビジネスメンバ等も様子を垣間見れたりして良かったとおもいます。

CTO泉率いるチームの開発風景。なんか楽しそうです。

 

ベトナムの開発拠点もリモート参加

取り組み内容紹介

一部ではありますがアウトプットを紹介します!

Fax2Web

アナログとデジタルとのブリッジになるための技術開発、ということでFAXとwebをつなぐチャレンジ。FAXを受信するとシステム連携されwebの注文ステータスが変更されたり、文字認識により自動入力されたりする仕組みのPoCを取り組みました。

メンテゲーム

サイトのメンテ中に遊べるゲームの実装にトライ。システム構成は Vue CLI 3 を使った静的サイト。

発表会・打ち上げの様子

発表会は1チームづつ順に発表。ベトナムの開発拠点とも接続して中継しました。

発表会の様子

 

1週間のハッカソンを振り返って話題が尽きない打ち上げとなりました

その他細かい運営のこと

参加必須のため参加者が50人弱ということでそれなりに入念に準備しました。まず、各チームの開発繁忙期と被らないようにスケジュール調整し日程を決めました。その後は運営コアメンバを招集してのキックオフ(これが開催3ヶ月前)、あとは週1回定例会を開きまして、少しづつ準備進めていきました。

なお、企画にあたっては以下の記事を参考にさせていただきました。有益な情報ありがとうございました。

まとめ

Raksul Hack Week #1 という社内ハッカソンを開催しました。

初めての長期社内ハッカソンということで、開催前には本当に盛り上がるのだろうか、普段1週間開発を止めてまでやるほどの価値が出るだろうか、といった不安も運営的にはありましたが、結果的には開催後のアンケートや、また経営陣へのイベント後の振り返り報告でも好評で良いイベントになりました。

普段の業務では要件通りに仕事をするということは大切ではありますが、今回のようにエンジニアやデザイナーの自発的でクリエイティブな発想で仕事をするというのも価値があることだなと感じました。また、使ったことのない技術スタックを試す機会としても良かったとおもっていて、実際今回使った新しい技術を業務に取り入れることにしたチームもありました。

今後もラクスルのテックカルチャーの1つとして開催していければいいなとおもいます。

宛名印刷ができる年賀状・喪中はがきをリリースしました

はじめに

ラクスルでエンジニアをしている藤田です。
先月、「年賀状・喪中はがき」という商品をリリースしました。
ラクスルで扱っている印刷サービスは基本的に、PDF等でアップロードされた印刷データを対象の商品(チラシや名刺など)に印刷してお客様へお届けするというものです。
今回リリースした商品は宛名印刷という機能があり、印刷データと一緒に宛名リストをアップロードしてもらい、印刷データと共にそれぞれの宛名をはがきに印刷します。他の商品では全く同じ印刷物が注文された部数だけ出来上がるのに対して、宛名印刷では一つ一つの印刷物が宛名の箇所だけ異なる内容になる点が特徴です。
一見するとシンプルに思われるこの機能ですが、実際に開発してみると意外と奥が深く面白かったので紹介したいと思います。

宛名印刷の難しさ

宛名印刷の難しさは「どうやって宛名を違和感なく宛名面にマッピングするか」にあります。組版処理とも呼ばれます。
下の画像を見ていただくと伝わるかと思うのですが、役職・部署の有無や文字数などで宛名面のどこに印刷するかが大きく変わります。
そのため、様々なパターンを考慮する必要があり必然的に開発も難しくなります。

今回はこのよう組版処理を実装するにあたって、開発スピード等を考慮して既成の組版ソフトを利用する方針としました。このソフトはWindows上でしか動作しないため、別途windowsサーバーをたてる必要がありました。

システム概要

少し簡略化していますが、組版処理に関連するシステムの概要図は上記のようになっており、次の通り処理されていきます。上述の組版ソフトは⑥で使用しており、SQSからキューを取得したりS3にアップロードしたりといった周辺の処理はWindowsとも相性が良いGolangで開発しました。

1. ユーザーが印刷データと宛名リスト(CSV)をアップロード
2. 印刷データと宛名リストをS3に保存
3. SQSに組版処理をエンキュー
4. SQSからキューを取得
5. S3から印刷データと宛名リストを取得
6. 組版処理を実施
7. 処理結果ファイルをS3に保存

Webアプリケーションと組版ソフトが動作するWindows Serverを繋げる方法として、Amazon SQSを利用しました。Windows Server上にAPIサーバーをたてる方法もありますが、やや開発コストが増える点と、宛名リストの件数によっては組版ソフトの処理に時間がかかるためキューイングが必要だったこともあり、SQSを採用しました。

マニアックな機能: CIDチェック

今回、開発した機能の中で面白かったものがCIDチェックです。CIDとはアドビ社が定めている文字ごとに一意に振られる番号のことで、フォントの文字を識別するために用いられます。例えば、フォントでは異体字の字形を区別する必要がありますが、Unicode等の一般的な文字コードでは同じコードが割り当てられていることがあるため、CIDが利用されます。
宛名印刷では、宛名に用いられるフォントとして正楷書CB1、リュウミンなど4種類から選択することができます。これらのフォントはカバーしているCIDの範囲以外の文字には使用することができないため、宛名リストで入力された文字が範囲内に含まれているかどうかをチェックすることが必要です。
CIDチェックの実装方法ですが、

1. 入力された文字に紐づくCIDを取得する
2. 取得したCIDが範囲内に含まれるかチェック

という順序でチェックをしていきます。
私が探した範囲ではRubyのライブラリ(CIDチェックはRailsアプリ内で行います)で文字列からCIDを取得するようなものはなかったため、UTF-8のコードとCIDをマッピングしたファイルを作成してUTF-8からCIDを引けるようにしました。
幸い、アドビ社が提供しているcid2code.txtというファイルにCIDと主な文字コードとの対応表がありましたので、このファイルを利用することでマッピングファイルを作成することができました。
細かい話になりますが、最終的に実装は下記のようになりました。

# app/validators/adobe_japan13_validator.rb

class AdobeJapan13Validator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.nil?
    chars = invalid_chars(value)
    record.errors.add(attribute, :invalid_character, invalid_chars: chars.uniq.join) if chars.present?
  end

  class << self
    def valid_strings_map
      return @valid_strings_map if @valid_strings_map
      @valid_strings_map = {}
      File.open(Rails.root.join('config', 'adobe_japan13.dat'), 'rb') do |f|
        f.each_line do |line|
          line.chomp!
          _cid, utf8 = line.split("\t")
          @valid_strings_map[utf8] = true
        end
      end
      @valid_strings_map
    end
  end

  private

  def invalid_chars(target_string)
    target_string.each_char.reject do |c|
      utf8 = c.unpack1('H*')
      self.class.valid_strings_map.key?(utf8)
    end
  end
end

バリデーターの中で読み込んでいる adobe_japan13.dat がマッピングファイルになっており、次のような内容になっています。左側の数字がCID、右側がUTF-8に対応しています。

# config/adobe_japan13.dat
1	20
2	21
3	22
4	23
5	24
6	25
7	26
8	27
9	28
10	29
11	2a
12	2b
13	2c
14	e28091
# 以下略

おわりに

この記事では宛名印刷という開発案件について紹介しました。
組版処理やCIDチェックといったものは印刷サービスならではで、他の会社ではあまり経験できないラクスルらしいテーマを紹介できたかなと思っています。

そうだ スピードチェック入稿のテストを自動化しよう

ラクスルのサーバサイドエンジニア 加藤です。

今回はスピードチェック入稿(テスト対象が印刷用ファイルの場合)の自動テストについて書いてみたいと思います。

再びスピードチェック入稿とは

チラシや名刺などの印刷をインターネットで注文したことのある方はいらっしゃいますか。

印刷したいものを選んで購入し、自分で用意した印刷データをアップロードして待つと、チラシや名刺などが届きます。通常の通販サイトと違うのは、自分で印刷したいデータを用意し、アップロードする作業です。さらに入稿データはラクスルでチェックし、印刷に適した形式に変換されます。

ラクスルでは、昨年8月末にそれまでオペレータが手動で行ってきたこの工程を自動化、スピードチェック入稿をリリースしました。それまで、1日以上かかってしまうこともあったデータチェックが、その場で完了。印刷物のお届けまでの日数が想定よりかかってしまうということもなくなり、多くのお客様に使われるようになりました。

リリースまでの過程は、スピードチェック入稿リリース秘話でご紹介させていただきました。

スピードチェック入稿のその後

さて、スピードチェック入稿は、その後もどんどん進化してきました。

1. 対応商品の拡大
最初は、チラシ・フライヤーと名刺だけだった対応商品に、ポスター/ポストカード/カード/チラシ折加工/折パンフレットが加わりました。

2. 入稿ファイル形式の追加
入稿ファイル形式も最初はPDFのみでしたが、ai/jpg/png/tiffでも入稿できるようになりました。また、ラクスルで提供しているWEBブラウザから利用できる無料のデザインソフト「オンラインデザイン」で作成していただいたデータも入稿することができます。

3. UXの改善
面裏の入稿原稿向き、折の開き向きなどを実際の印刷物により近い形でご確認していただくために、仕上がりプレビューが生まれました。実際に、ひっくり返したり、開いたり閉じたりして仕上がりを確認することができます。

折加工商品の仕上がりプレビュー

さて、様々な機能追加があると、大変なのがテストです。

ファイルの変換が入るために、テストを自動化しようにも、通常のWebテストのフレームワークだけでは対応できません。悩んでいるうちにも開発は進み、リリースのたびにプロジェクトメンバーがかなりの時間をかけて、データを手動でチェックしてテストをしていました。また、テストの実施もれが発生してしまうことがありました。これでは、チームとして自信を持って開発・リリースすることができません。

テストを自動化するぞ!

なんとかしなきゃとディスカッションしていたところ、「正常リリースで生成された印刷データと変換方法に変更を加えた後に生成した印刷データを、画像に変換して比較したらいいのではないか」というアイデアと、実際にImageMagicのcompareコマンドで検証結果がチーム内でシェアされました。

印刷データ画像

差分なし

差分あり

変換方法を変えて差分ができた場合は、差分箇所が赤く表示されます。これなら、一目で問題に気づけそうです!

このアイデアをとっかかりに、さらに印刷データのファイルに関してテストしたい項目を整理してみました。

1. ファイルの規格、各BOX(*)のサイズ、カラーが注文情報と比較して正しいこと
2. 問題のあるファイルに対して正しくエラーが返されていること
3. 変更前後の印刷データの画像差分がないこと、もしくは差分が妥当であると確認できること

* ドキュメントの用紙サイズ、印刷用の裁ち落としや仕上がりサイズの定義

そして、実際にテストを開発に組み込んでいくために、非エンジニアでも気軽にテストを実施したいと考えました。それぞれテストファイルが30個だとしても、この3項目に対してテストすると3倍の90項目。テストが大変だという理由で改善が進まないのは残念です。

なるべく早く問題に気づけるようにする

早速、設計に取りかかりました。

自動テストの場合は、社内ツールでユーザは自分も含め同じプロジェクトのメンバーです。要件はユーザ向けの機能と違って自由に決められますが、今回は開発がスムーズに進むようにするためなので「開発のなるべく早い段階で問題に気づけるような仕組みを作る」を指針にしました。

スピードチェック入稿のシステムは大きく3つのレイヤーに分かれています。

・フロント:データチェック用のAPIを呼び出し結果を描画
・データチェック用のAPI: フロントから受け取ったリクエストを受けてジョブをキューイング
・バックエンドジョブ:データチェックファイル変換を実施

シンプルにするために、今回の3項目のテストの対象はバックエンドジョブに絞りました。

システム構成

先ほどの指針にしたがって、1. ファイルの規格のテストに関しては、QA環境でスピードデータチェック入稿が使われると必ずジョブ内で自動で実行され、問題が発見されればslackに通知されるようにしました。

 

ファイルアップロード後の処理の流れ

上記の処理の流れの中で、サムネイルの生成が終わったらステータスは終了にして、フロントが描画できるような状態にしつつ、データチェックエンジンのチェック用のスクリプトが走るような作りです。

2. エラーのテスト3. 画像比較テスト に関しては、実行するとしばらくQA環境のジョブを占有してしまうので、実行タイミングをコントロールできるように、手動でslackから実行できるようにしました。

実行するとテストケースのYAMLから、上記の処理のファイル検証〜サムネイル生成を行うジョブをまとめて生成、結果チェックまたは画像比較が順次実行されます。画像比較テストの場合はテストケースが何十個もあるため、期待画像を一括に生成するコマンドも用意。テストケースの更新の手間も軽減しました。

テストの結果も、既存ツールに表示しようかと思いましたが、もう少し要件があったため、以下の画像のように、Datacheck APIの管理画面に表示しています。

画像比較テストの結果一覧(差分がない場合)

 

画像比較テストの結果一覧(差分がある場合)

これで1日〜2日かかっていたテスト作業が、30分以内で実施できるようになりました!
さらにミスもなくなり自信を持ってリリースすることができるように。ファイルの処理で処理フローに変更が必要になって試行錯誤することがあっても、すぐに確認できると開発効率は上がりますし、様々な改善のアイデアも湧いてきます。

自動テストの開発後、スピードチェック入稿当初からやりたかった複雑な処理変更や細かい改善を無事にリリースすることができました。

自動テストを使い続ける

自動テストは、テスト自体のメンテナンスも必要ですね。

今回は同じリポジトリにテストケースの定義を含めたため、通常のWebテストフレームワークと同じように機能修正のPull Requestに含めてテストの変更、追加を行なっています。

自動テストに関するREADMEも用意しメンバーにシェア。エンジニアだけでなく、データチェックのエンジンの設定メンバーも、テストケースの変更・追加を行なって、テストを最新の状態に保っています。

まとめ

スピードチェック入稿の印刷用ファイルを対象にテストを自動化しました。

「開発のなるべく早い段階で問題に気づけるような仕組みを作る」を指針に3つの項目のテストが短時間に実施可能に。これにより、すぐにミスに気づけるようになり、開発効率も向上しました。

テストの自動化は後回しになってきましたが、スピードチェック入稿が進化していく上で重要な施策だったと思っていますし、早速その効果を実感することができました。

ラクスルでは、絶賛エンジニア募集中です。

「仕組みを変えれば世界はもっとよくなる」

ユーザ価値をさらに向上させつつ、より社内外のサービスと繋がっていくプラットホームを開発していきたいエンジニアを募集しています。是非一度オフィスに遊びにきてください!