Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

graphql-codegenで簡単3分!TypeScript型定義。

こんにちわ。
私の記憶もブロックチェーンにしてしまいたいと思う今日この頃。
(報酬は私の特製おにぎりで勘弁)
AIR Design for Apps事業部、社内システムチームの高橋です。

私の事業部では案件管理を
独自の社内システムを開発し運用しています。

その社内システムのフロントエンドではフレームワークにReact
GraphQLクライアントにApollo Client
開発言語はTypeScriptという構成で開発を行なっています。
(バックエンドではWeb-APIとしてGraphQL)

システムが本番リリースしてからフロントエンド開発に携わったため
普段、開発に用いていたライブラリの利便性について
あまり考えることはありませんでした。
が、改めて調べてみるとこりゃ良いなと思った
graphql-codegen というライブラリについて今回はご紹介したいとます。

graphql-codegenとは

公式リポジトリには下記の説明があります。

GraphQL Code Generatorは、GraphQLスキーマからコードを生成するツールです。

参照 github.com
フロントエンド側でGraphQLを呼び出す時に
クエリに渡すパラメータやモデル、レスポンス内容といった
TypeScript型定義をGraphQLスキーマから定義してくれます。

バックエンド側のスキーマに変更があった場合も
コマンド一つで既存の型定義を更新してくれます。

初期設定を変更すればApollo Clientのカスタムフックも定義してくれて超便利。
いわゆる開発を効率化してれるっていうツールです。

GraphQLスキーマとは

簡単に言うとGraphQLの型です。

GraphQLの詳細は今回は割愛しますが
GraphQLではカスタムスカラー型からモデル型
各クエリ型の定義まで様々なものを型定義します。

この明確な定義によって単一のエンドポイントから特定のクエリを呼び出したり
パラメータや各フィールドのバリデーションをGraphQLサーバー側で行うことが出来ます。
またGraphQLの特徴にもつながる大事な構成要素となっています。

使ってみた

どんなツールかざっくりと分かったところで
使用しないとどんな実装になって、使用するとどんな実装になってメリットを享受できるか
具体的にApollo Client公式ドキュメントより
コードを拝借して見ていきたいと思います。
Apollo Clientを使用し実装する前提で解説していきます、Apollo Clientの設定は省いています。

変更前

import React from 'react';
import { useQuery, gql } from '@apollo/client';

// モデル型
interface RocketInventory {
  id: number;
  model: string;
  year: number;
  stock: number;
}

// クエリ型
interface RocketInventoryData {
  rocketInventory: RocketInventory[];
}

// パラメータ型
interface RocketInventoryVars {
  year: number;
}

// クエリドキュメント
const GET_ROCKET_INVENTORY = gql`
  query GetRocketInventory($year: Int!) {
    rocketInventory(year: $year) {
      id
      model
      year
      stock
    }
  }
`;

export function RocketInventoryList() {
  // Apollo Clientを使用しクエリをリクエストしてレスポンスを取得
  const { loading, data } = useQuery<RocketInventoryData, RocketInventoryVars>(
    GET_ROCKET_INVENTORY,
    { variables: { year: 2019 } }
  );
  return (
    <div>
      <h3>Available Inventory</h3>
      {loading ? (
        <p>Loading ...</p>
      ) : (
        <table>
          <thead>
            <tr>
              <th>Model</th>
              <th>Stock</th>
            </tr>
          </thead>
          <tbody>
            {data && data.rocketInventory.map(inventory => (
              <tr>
                <td>{inventory.model}</td>
                <td>{inventory.stock}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

上記のコードがですね。。

変更後

import React from 'react';
import { useGetRocketInventoryQuery } from 'src/gen/graphql';

export function RocketInventoryList() {
  // Apollo Clientのカスタムフック関数でクエリをリクエストしてレスポンスを取得
  const { loading, data } = useGetRocketInventoryQuery({
    variables: { year: 2019 }
  });
  return (
    <div>
      <h3>Available Inventory</h3>
      {loading ? (
        <p>Loading ...</p>
      ) : (
        <table>
          <thead>
            <tr>
              <th>Model</th>
              <th>Stock</th>
            </tr>
          </thead>
          <tbody>
            {data && data.rocketInventory.map(inventory => (
              <tr>
                <td>{inventory.model}</td>
                <td>{inventory.stock}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

コード量を少なく実装できるようになりました!
パッと見た感じ各型定義の記述が無くなり
API呼び出しフックも短くなって見やすいですね。
次にどこがどうなったか見ていきたいと思います。

と、その前にgraphql-codegenを利用する下準備を説明します。
まず今回使用するライブラリをインストール ※Apollo Clientを使用していない場合、@graphql-codegen/typescript-react-apolloは不要です。

npm install @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

次に下記、codegen.ymlを下記の設定で用意する

schema: http://localhost:3000/graphql //エンドポイント先URL
documents: "src/**/*.graphql" // クエリ内容の保存先フォルダ
generates:
  src/gen/graphql.ts: //型定義ファイルのパス
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"

package.jsonに下記スクリプトを追加

"scripts": {
  "generate": "graphql-codegen --config codegen.yml"
}

これでgraphql-codegenを利用できる準備が整いました。
次に変更後のコードを記述するために必要な手順を説明します。

codegen.ymlで指定したdocumentsにGetRocketInventory.graphqlという
下記クエリドキュメントを置く

query GetRocketInventory($year: Int!) {
  rocketInventory(year: $year) {
    id
    model
    year
    stock
  }
}

最後に下記コマンドを実行する

npm run generate

此処までを行うと変更前のコードで定義されていた
各定義(モデル、パラメータなど)がgraphql.tsへ出力されます。
下記が出力された定義一覧。

export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
};

export type Query = {
  __typename?: 'Query';
  /** 出力されたクエリ型 */
  rocketInventory: Array<RocketInventory>;
};

// 出力されたモデル型
export type RocketInventory = {
  __typename?: 'RocketInventory';
  id: Scalars['ID'];
  model: Scalars['String'];
  stock: Scalars['Int'];
  year: Scalars['Int'];
};

// 出力されたパラメータ型
export type GetRocketInventoryQueryVariables = Exact<{
  year: Scalars['Int'];
}>;

// 出力されたレスポンス内容の型
export type GetRocketInventoryQuery = { __typename?: 'Query', rocketInventory: Array<{ __typename?: 'RocketInventory', id: string, model: string, year: number, stock: number }> };

// 出力されたクエリドキュメント
export const GetRocketInventoryDocument = gql`
    query GetRocketInventory($year: Int!) {
  rocketInventory(year: $year) {
    id
    model
    year
    stock
  }
}
    `;

/**
 * __useGetRocketInventoryQuery__
 *
 * To run a query within a React component, call `useGetRocketInventoryQuery` and pass it any options that fit your needs.
 * When your component renders, `useGetRocketInventoryQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useGetRocketInventoryQuery({
 *   variables: {
 *      year: // value for 'year'
 *   },
 * });
 */
// 出力されたApollo Clientのカスタムフック
export function useGetRocketInventoryQuery(baseOptions: Apollo.QueryHookOptions<GetRocketInventoryQuery, GetRocketInventoryQueryVariables>) {
        const options = {...defaultOptions, ...baseOptions}
        return Apollo.useQuery<GetRocketInventoryQuery, GetRocketInventoryQueryVariables>(GetRocketInventoryDocument, options);
      }
export function useGetRocketInventoryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetRocketInventoryQuery, GetRocketInventoryQueryVariables>) {
          const options = {...defaultOptions, ...baseOptions}
          return Apollo.useLazyQuery<GetRocketInventoryQuery, GetRocketInventoryQueryVariables>(GetRocketInventoryDocument, options);
        }
export type GetRocketInventoryQueryHookResult = ReturnType<typeof useGetRocketInventoryQuery>;
export type GetRocketInventoryLazyQueryHookResult = ReturnType<typeof useGetRocketInventoryLazyQuery>;
export type GetRocketInventoryQueryResult = Apollo.QueryResult<GetRocketInventoryQuery, GetRocketInventoryQueryVariables>;

codegen.ymlで設定したエンドポイント先URLを指定することで
GraphQLサーバー側で定義されているGraphQLスキーマを基に
上記の各型定義がフロントエンド側で自動で定義されます。

また作成したクエリドキュメントを基にクエリドキュメントと
@graphql-codegen/typescript-react-apolloによってApollo Clientのカスタムフックもgraphql.tsへ出力されます。

これによって変更前コードで定義していたGraphQL API呼び出しの記述が全てgraphql.tsに定義され
それらをラップしたApollo Clientのカスタムフックが実装されることで
変更後のコードでは簡単な記述でAPIが呼び出せるようになっています。

今回は定義していないですがオリジナルスカラー型を定義した場合も
GraphQLサーバー側のスキーマを基に自動で型定義してくれます。
いやー本当に便利。
どうですか?便利ですよね。いつ使うの?。。。い。。

まとめ

毎回サーバー側のスキーマ定義と
フロントエンド側のスキーマ型定義を合わせる作業が無くなった。
(npm run generateで自動でサーバー側スキーマとクライアント側の型定義を合わせるため)
自動で定義されることでフロントエンド側の型定義の質が担保され
更にTypeScriptの型定義を行う作業が減り、他の開発に専念できてスピードも上がるとても便利なツールです。
是非、導入されていない方は導入してみてください。

参照

qiita.com

www.apollographql.com

www.oreilly.co.jp

ソフトウェアテストの基礎

こんにちは、AIR Design for Apps事業部 Android チームの堀口です

今回のブログは基礎から学ぶソフトウェアテストを読んだまとめを書いていこうと思います。


経緯

弊社ではアプリの開発後、QA チームによるテストが行われます。

エンジニアとしてはできるだけ不具合を出さないようにしたいので、 テストの段階でどれだけ不具合を減らすことができるのか?と考え、ソフトウェアテストについて基礎から学ぼうと思い立ちました。


まとめ

全章を読みましたが、ソフトウェアテストビギナーにとって全てを理解するのは難解でした。

その点については本書「第6章 ソフトウェアテスト運用の基本-テスト成功の方程式-」の冒頭にも書かれており、「読者がビギナーレベルのテスト担当者であれば、本書の1章と2章を理解すれば十分です。(引用)」とあるので本ブログでは 1章、 2章についてまとめました。

本書のブログ

juichi.blog.ss-blog.jp

第1章 はじめに

1.1 ~ 1.3 章

どんなソフトウェアにもバグはある(引用)」という話でした。

具体例として、南米フランス領で打ち上げられた ESA のロケット (Arianne 5) が墜落した話、NASA の火星探査機が墜落した話など挙がっていました。

1.4 章

バグを全て見つけるのは無理。しかし、ソフトウェアのどの部分にバグが出やすいのかを理解し、適切なテスト手法を適用すれば十分な品質を得ることが可能」という話でした。

根拠として、 実際に行われた調査報告の中に、ソフトウェアに含まれる 47%のバグがプログラムの4%の部分に偏在しているというデータがあることが述べられていました。 参考文献はこちら

1.5 章

全くバグのないソフトウェアを作ることはできないが、十分な品質を持ったソフトウェア製品を開発するためのテスト手法を紹介する」という話でした。

インプットされた2つの整数(1以上999以下)を掛け算するプログラムですら100万通りのテストケースがあり、仮にこれらを網羅したとしても、コンパイラ や CPU のバグなどまで考慮してテストを行って初めて完全無欠なソフトウェアになるという話しがありました。

2つの整数の積を計算するだけの小さいプログラムが完璧に動作するかを確認するだけでこれだけの手間がかかるのであれば、Android アプリをテストし切るのは現実的に無理。。。


2章 ホワイトボックステストと具体的なテスト手法について

2.1 ~ 2.8章
ホワイトボックステスト

e-words.jp

本書の説明と大まかに合致していて、論理構造の正しさをテストするもの。ただし、ソフトウェアの仕様が間違っていることで発生するバグは発見できないという特徴を持ったテストのことです。

制御パステスト法

e-words.jp

本書の説明とも大まかに合致しています。(IT 用語辞典にはステートメントカバレッジの記載がないため)

制御パステスト法には以下の 2種類があり、

  1. ステートメントカバレッジ
  2. ブランチカバレッジ

例えば以下のコードで

    if (num == 0) {
        commonNum++ // ①
    } 
    if (num1 > 1) {
        commonNum *= 2 // ②
    }

①と②を一行ずつ実行して確認するのはステートメントカバレッジ。 処理の流れをフローチャートに描き起こして、分岐までカバーするのがブランチカバレッジです。

ステートメントカバレッジのメリットは記載なし、デメリットはバグを見逃すこと。

ブランチカバレッジのメリットはバグの見逃しがステートメントカバレッジより少ないこと、デメリットはテストケースの数が多いこと。

結論としては、テストが大変だからという理由でブランチカバレッジを実行しないことは避けたほうが良いです。 特に、バグが頻発しているコードに対して実行すると品質向上に有効そうです。

カバレッジ基準

ソースコード全体を 100% とした時、そのうち何%がカバレッジテストをクリアしたか?という基準です。

一般的なソフトウェアであれば 60 ~ 90 % 程度で十分と言えるようです。

というのは、エラー処理、コメントアウトされたコードなどはテストしようがないからですね。

また、カバレッジで検出できないバグとして下記があります。

  1. ループ処理
  2. 仕様の間違い
  3. データに関するバグ
  4. タイミングに関するバグ(マルチタスク、割り込みなど) (引用)

エンジニアが主に関わる必要があるのは 1 , 3 。2 については仕様通り動くかをテストするべきで、4 については優れた研究はあるが実務で使うのは困難なようです。

2.9 章 ホワイトボックステスト(TDD)

アジャイル開発が流行っていることから、ブラックボックステストに代わってホワイトボックステストが復活したという話です。

TDD のテストを書く

実践的なことはここに書いてありました、TDD の基本は

  1. 小さい動作しないテストを書く
  2. テストを通るコードを書く
  3. 重複したコードを削除する(リファクタ)

(引用)

です。TDD の参考文献をネットを検索すると以下のような本が出てきますが、この本で書いてある基本をベースに読めばスッと入りそうな気がしています。 読んだらまたブログ書きます。

www.amazon.co.jp

以上です。


弊社ではいっしょに働く Android エンジニア、iOS エンジニアを募集しています!
ご興味のある方はぜひご応募いただけますと嬉しいです。

Androidエンジニアの方はこちらから: www.wantedly.com
iOSエンジニアの方はこちらから: www.wantedly.com

腕に覚えのある方はリードエンジニアの求人もありますので是非ご検討ください。AndroidiOSの両方の開発ができるエンジニアも大歓迎です! www.wantedly.com

AWS Lambda 非同期呼び出しのご紹介(aioboto3)

Python に入門しなきゃと思いつつ半年が経ってしまった AIR Design for Marketing 事業部バックエンドエンジニアの成田です。

AWS Lambda の呼び出しを aioboto3 を使って非同期化してみたところ、呼び出し完了までが 100 倍速 ほどになり大変捗ったのでご紹介します。

続きを読む