[GAS]オブジェクト指向によるGAS開発のススメ #3内部設計・コーディング・テスト

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは、2021年9月から始まりました「ノンプロ研GAS中級講座6期」について、全7回でお届けします。最終回の7回目を、5回に渡ってお送りしています。それって、全12回じゃないのでしょうか。。。

前回のおさらい

前回は、「外部設計」をお届けしました。

[GAS]オブジェクト指向によるGAS開発のススメ #2外部設計
どうも。つじけ(tsujikenzo)です。このシリーズでは、2021年9月から始まりました「ノンプロ研GAS中級講座6期」について、全7回でお届けします。最終回の7回目を、5回に渡ってお送りしています。前回のおさらい前回...

今回は、3回目で「内部設計・コーディング・テスト」です。

「テスト」という工程は、ノンプログラマーにはあまり馴染みがありません。

今日は、「テスト駆動開発(っぽいもの)」という手法を取り入れながら、プログラミングしていきます。

今日のアジェンダ

  • プロジェクトを準備する
  • ファイル内構成
  • テスト駆動開発
  • コンストラクタとメソッドの実装

プロジェクトを準備する

まず、プロジェクトを用意します。実際に使用するスプレッドシートの、コンテナバインドスクリプトでいいでしょう。 

クラスごとのスクリプトファイルを用意する

クラス図を見ながら、1クラスにつき1スクリプトファイルを作成します。

スクリプトファイル名は「class_○○」で統一します。

onOpen()には、01_のような番号を振ると分かりやすいでしょう。 

さっそく、スクリプトファイルの中身を書いていきます。

順番はどれからでもかまいません。チームで開発するばあいは、クラスの担当を分けて、GitHubなどにPUSHしていくのもいいでしょう。

ファイル内構成

スクリプトファイル内は、このような構成になります。

クラス系ファイル

クラス系ファイルは、かならず上にクラスを書き、下にクラスをテストする関数を書きます。

//class クラス{ }

//function クラスのテストをする関数(){ }

onOpenなどの実行用関数系

同様に、onOpenなどの実行用関数系ファイルも、下にfunctionをテストする関数を書きます。

//グローバル領域

//function onOpen(){ }

//function onOpenで呼ばれる関数1(){ }

//function onOpenで呼ばれる関数2(){ }

//function スクリプトファイル内のテストをする関数(){ }

まずは、これらのコメントを、スクリプトファイルにコピペしておいてもいいでしょう。 

テスト駆動開発

クラスや、実行用関数を書くときは、まず、呼び出し側のテスト関数から書き始めます。

テスト関数の名前は、かならず「testクラス名」で統一します。

たとえば、DataSheetクラスのテストをする関数なら、「testDataSheet」という具合です。

まだ、中身はなにもありませんが、このような手順で書いてみましょう。

/** Dataシートクラス */
class DataSheet {

}

/** TEST関数 */
function testDataSheet() {

}

日本語でテスト関数を書く

テスト関数は、DataSheetクラスがちゃんと動くかどうか、テストをするための関数です。

なので、DataSheetクラスがもつ、状態や振舞いを、1つ1つ確認します。

たとえば、DataSheetクラスは、なにかしらのプロパティをもつはずですので、インスタンスが生成されたら、インスタンスとその中身を確認するテスト関数を 「日本語で」 書きます。

/** Dataシートクラス */
class DataSheet {

}


/** TEST関数 */
function testDataSheet() {

  //インスタンスの確認

  //シートがつかめているか確認

  //シート名の確認

}

メソッドも同様に、DataSheetクラスの振舞いを確認するものなので、1つ1つ確認します。

テスト関数のコツは、「メソッドでやることは1つ」という原則を守ることです。

メソッドはとても小さな処理になります。それでかまいません。

クラスがやる処理をすべて書き起こして、日本語でテスト関数を書きます

/** Dataシートクラス */
class DataSheet {

}



/** TEST関数 */
function testDataSheet() {

  //インスタンスの確認

  //シートがつかめているか確認

  //シート名の確認

  //全てのRecordsをobjArrayで取得するメソッド

  //Recordsのlengthを確認するメソッド

  //Dataシートの最終行を返すメソッド

  //Dataシートの最終列を返すメソッド

  //受け取ったobjArrayを貼り付けるメソッド

}

ある程度、テスト関数を日本語で書き終えたら、テスト関数のコードを書きます。

テスト関数では、ログ出力をもってコードが動いている、という確認をします。

なので、すべてのテスト関数に console.log() を書きます。

マルチカーソル機能(Alt + クリック) を使って、コーディングを効率化していきましょう。コピペより早いです。 

console.log()の中に、確認するプロパティやメソッドを書いていきます。

チームで開発するさいは、テスト関数を書いて、次の人にパスしてあげると、次の人も作業がしやすいですね。

ということで、すべてのクラス(onOpenなども含む)の、テスト関数を日本語で書くことからはじめましょう。

/** Dataシートクラス */
class DataSheet {

}



/** TEST関数 */
function testDataSheet() {

  //インスタンスの確認
  const d = new DataSheet();
  console.log(d);

  //シートがつかめているか確認
  console.log(d.sheet);

  //シート名の確認
  console.log(d.sheet.getName());

  //全てのRecordsをobjArrayで取得するメソッド
  console.log(d.getDataSheetValues());

  //Recordsのlengthを確認するメソッド
  console.log(d.dataSheetRecordsLength());

  //Dataシートの最終行を返すメソッド
  console.log(d.getCustomLastRow());

  //Dataシートの最終列を返すメソッド
  console.log(d.getCustomLastColumn());

  //受け取ったobjArrayを貼り付けるメソッド
  console.log(d.setCustomValues());

}

コンストラクタとメソッドの実装

テスト関数ができたら、クラスの中身を書いていきます。

コンストラクタになにをもたせて、インスタンスを生成するかは、非常にむずかしいポイントです。

絶対これが正しい、という正解はありませんで、まずは、自分のイメージと合うコンストラクタを書いていいと思います。

「この方が便利!かっこいい!」と思うものがあれば、修正しながら、でいいと思います。

/** Dataシートクラス */
class DataSheet {

  /** 
    * @constructor
    */
  constructor() {
    this.id = SHEET_ID; //onOpen.gsのグローバル領域に定義しています。
    this.sheetName = `Data`;
    this.sheet = SpreadsheetApp.openById(this.id).getSheetByName(this.sheetName);
  }

}

メソッドは、短く、純粋関数(戻り値を返すだけの関数)で書きます。

set系のメソッドには、テキストメッセージを持たせ、メソッドはかならず戻り値を持つようにします。

手順としては、まず、テスト関数をそのままクラス内に貼り付けます。

/** Dataシートクラス */
class DataSheet {

  /** 
    * @constructor
    */
  constructor() {
    this.id = SHEET_ID; //onOpen.gsのグローバル領域に定義しています。
    this.sheetName = `Data`;
    this.sheet = SpreadsheetApp.openById(this.id).getSheetByName(this.sheetName);
  }

  //シート名の確認
  console.log(d.sheet.getName());

  //全てのRecordsをobjArrayで取得するメソッド
  console.log(d.getDataSheetValues());

  //Recordsのlengthを確認するメソッド
  console.log(d.dataSheetRecordsLength());

  //Dataシートの最終行を返すメソッド
  console.log(d.getCustomLastRow());

  //Dataシートの最終列を返すメソッド
  console.log(d.getCustomLastColumn());

  //受け取ったobjArrayを貼り付けるメソッド
  console.log(d.setCustomValues());

}

マルチカーソル機能(とくにCtrl+矢印キーが大活躍します)を使って、メソッドに仕上げていきます。 

メソッドは、かならず戻り値を持たせますので、すべてのメソッドにreturnを持たせます

return文には、変数を持たせるように統一します。

returnで返す変数、ということは、変数宣言するはずですので、変数宣言も用意しておきます。

変数名はとりあえずvalueでいいでしょう。

これだけ準備しておくだけでも、コーディングがものすごく早くなります。(コーディング時に悩みが少なくなります)

/** Dataシートクラス */
class DataSheet {

  /** 
    * @constructor
    */
  constructor() {
    this.id = SHEET_ID; //onOpen.gsのグローバル領域に定義しています。
    this.sheetName = `Data`;
    this.sheet = SpreadsheetApp.openById(this.id).getSheetByName(this.sheetName);
  }


  /** 全てのRecordsをobjArrayで取得するメソッド
    * @return {Object} value
    */
  getDataSheetValues() {
    const value = '';
    return value;
  }

  /** Recordsのlengthを確認するメソッド
    * @return {Object} value
    */
  dataSheetRecordsLength() {
    const value = '';
    return value;
  }

  /** Dataシートの最終行を返すメソッド
    * @return {Object} value
    */
  getCustomLastRow() {
    const value = '';
    return value;
  }

  /** Dataシートの最終列を返すメソッド
    * @return {Object} value
    */
  getCustomLastColumn() {
    const value = '';
    return value;
  }

  /** 受け取ったobjArrayを貼り付けるメソッド
    * @return {Object} value
    */
  setCustomValues() {
    const value = '';
    return value;
  }

}

そして、すべてコーディングが終わったら、テスト関数 testDataSheet() を実行します。

ここで発生するエラー(タイプミスや浮動小数点など)のことを、バグと呼びます。

テスト関数を実行しながら、バグを1つずつつぶしていきます。 

「バグに対処するために、もうすこし早めに、コードが動くかどうかテストしたい」という声もあると思います。

しかし安心してください。バグの必要な修正箇所は、比較的見つかりやすいです。

それは、メソッドが小さいからです。メソッドとメソッドは複雑に絡み合っていません。

このような書き方を「疎結合(そけつごう)」と言います。

メソッドを書くときは、疎結合を意識していきましょう。

まとめ

以上で、「内部設計・コーディング・テスト」をお送りしました。

チームで開発をするときなど、他人のコードが、コーディングガイドラインに違反した書き方だと、ついつい口を出してしまうものです。

しかし、まずはテスト関数が動いているなら中身(書き方)は問わない、という安心感を提供することで、チームの士気を高めます。

リファクタリングもしかりで、まずはコードが動くことを確認しながらコーディングするテクニックを、取り入れてみましょう。

テスト駆動開発、おススメです。

次回は、 「クラス設計のコツ」 をお届けします。

このシリーズの目次

[GAS]オブジェクト指向によるGAS開発のススメ

  1. 要件定義
  2. 外部設計
  3. 内部設計・コーディング・テスト
  4. クラス設計のコツ
  5. シートクラス
タイトルとURLをコピーしました