スプレッドシートからテスト用Googleフォームを作成するGAS(複数選択)

Googleスプレッドシートから採点等ができる複数選択問題のテスト用Googleフォームを作成するGASを紹介します。

概要

前回の記事では、スプレッドシート上の問題データから、ラジオボタン(選択問題)のテストフォームを作る方法をご紹介しました。
今回は、チェックボックス(複数選択)の問題も作成できるようにし、2種類の問題形式を利用できるGoogleフォームの自動生成スプレッドシートを作ります。

前回の記事で作成したスプレッドシートとGASに手を加えていきたいと思います。

スプレッドシートのへデータ入力

スプレッドシートには以下のようにデータを入力し、シート名はそのまま「シート1」としています。
前回のシートに「問3」の行を追加しています。

A列B列C列D列E列F列G列H列I列J列
フォームタイトル理解度テスト
フォームの説明文簡単な理解度チェックのテストです
作成したフォームのURL
問題のタイトル問題の説明配点問題形式正解の番号選択肢①選択肢②選択肢③選択肢④フィードバック(答えの解説など)
問1.GASは何の略称かGASは以下のうちどれの略称か答えよ。60ラジオボタン2Google Apple SystemGoogle Apps ScriptGood Admin ScriptGroup Apply System正解はGoogle Apps Scriptです。
問2.GASのベース言語GASのベースになっているプログラミング言語は以下のうちどれか。40ラジオボタン3PythonJavaJavaScriptGo正解はJavaScriptです。
問3.GoogleのサービスGoogleの提供しているサービスを選択せよ。50チェックボックス1,3SpreadsheetExcelDocumentWord正解はSpreadsheetとDocumentです。

GASの作成

コピペですぐに使いたい人向けに、今回の完成状態のGASを先に紹介しておきます。
この関数createForm()をスプレッドシート上に作成したボタンに割り当てるなどすれば使用できます。

function createForm() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');

  const formTitle = sheet.getRange('B1').getDisplayValue();
  const formDescription = sheet.getRange('B2').getDisplayValue();

  const firstRow = 5;
  const lastRow = sheet.getLastRow();

  const dataRows = lastRow - (firstRow - 1);

  var questionList = sheet.getRange(firstRow, 1, dataRows, 10).getDisplayValues();

  questionList = questionList.map(question => {
    return {
      title: question[0],
      helpText: question[1],
      point: question[2],
      type: question[3],
      answer: question[4],
      choices: [question[5], question[6], question[7], question[8]],
      feedback: question[9]
    }
  });

  const form = FormApp.create(formTitle);

  form.setDescription(formDescription)
    .setIsQuiz(true);


  questionList.forEach(question => {

    if (question.type == 'ラジオボタン') {
      var choiceItem = form.addMultipleChoiceItem();

      var choiceList = [];

      question.choices.forEach((choice, index) => {

        if (choice != '') {

          let isCorrect = question.answer == String(index + 1);

          let choiceObj = choiceItem.createChoice(choice, isCorrect);

          choiceList.push(choiceObj);
        }

      });

      const feedback = FormApp.createFeedback().setText(question.feedback).build()

      choiceItem.setTitle(question.title)
        .setHelpText(question.helpText)
        .setPoints(question.point)
        .setChoices(choiceList)
        .setFeedbackForCorrect(feedback)
        .setFeedbackForIncorrect(feedback);

    }
    else if (question.type == 'チェックボックス') {
      var checkboxItem = form.addCheckboxItem();

      var choiceList = [];
      var questions = question.answer.split(',');

      question.choices.forEach((choice, index) => {

        if (choice != '') {

          let isCorrect = questions.includes( String(index + 1) );

          let choiceObj = checkboxItem.createChoice(choice, isCorrect);

          choiceList.push(choiceObj);
        }

      });
      const feedback = FormApp.createFeedback().setText(question.feedback).build()
      
      checkboxItem.setTitle(question.title)
        .setHelpText(question.helpText)
        .setPoints(question.point)
        .setChoices(choiceList)
        .setFeedbackForCorrect(feedback)
        .setFeedbackForIncorrect(feedback);

    }
    else {
      Browser.msgBox('「'+ question.title +'」は作成できない問題形式です');
    }


  });

  sheet.getRange('B3').setValue(form.getEditUrl());
  Browser.msgBox('「' + formTitle + '」の作成が完了しました');
}

以下では詳しく理解したい人向けに、前回の記事からの変更点を解説したいと思います。

1. 正解の番号の受け取り方

スプレッドシート上のデータを受け取った後、map処理を用いてデータを整形する部分です。
基本的には前回の記事と同じですが、正解の番号の受け取り方を変更しています。

  // map処理で問題のデータを整形
  // 正解の番号は複数の場合があるので、そのまま受け取ります。
  // ※前回記事では「数値型」に変更していました。
  questionList = questionList.map(question => {
    return {
      title: question[0],
      helpText: question[1],
      point: question[2],
      type: question[3],
      answer: question[4],  // 変更前: answer: Number(question[4]),
      choices: [question[5], question[6], question[7], question[8]],
      feedback: question[9]
    }
  });

2. 問題形式による分岐

フォームの作成後、問題を追加していく部分です。
問題形式によって分岐し、それぞれに合ったオブジェクトを作成します。
※ラジオボタンの場合、MultipleChoiceItemというオブジェクト
※チェックボックスの場合、CheckboxItemというオブジェクト

questionList.forEach(question => {
  if (question.type == 'ラジオボタン') {
      
      // ラジオボタン(選択問題)の追加処理
      // 一部変更あり、次項で解説

  }
  else if (question.type == 'チェックボックス') {
    
    // チェックボックス(複数選択)の追加処理
    // 後の項で解説

  }
  else {

    // 問題の形式がラジオボタンでもチェックボックスでもなかった場合
    // メッセージボックスを表示、問題は作成しません。
    Browser.msgBox('「'+ question.title +'」は作成できない問題形式です');
  }
});

3. ラジオボタン(選択問題)の追加処理

項1でデータ整形時にデータ型をそのままにした為、question.answerには「文字列型データ」が格納されています。
正解の番号と問題番号との比較を行う際にデータ型が違うと、エラーとなってしまうのでデータ型を揃える処理を追加します。

// 混同を防ぐため変数名を変えています。
var choiceItem = form.addMultipleChoiceItem();  // 変更前: var item = form.addMultipleChoiceItem();

var choiceList = [];

question.choices.forEach((choice, index) => {

  if (choice != '') {

    // この選択肢が正解かどうかチェック
    // question.answerが「文字列型データ」なので、型を揃えて比較する
    let isCorrect = question.answer == String(index + 1);  // 変更前: let isCorrect = question.answer == (index + 1)

    let choiceObj = choiceItem.createChoice(choice, isCorrect);

    choiceList.push(choiceObj);
  }

});

const feedback = FormApp.createFeedback().setText(question.feedback).build()

choiceItem.setTitle(question.title)
  .setHelpText(question.helpText)
  .setPoints(question.point)
  .setChoices(choiceList)
  .setFeedbackForCorrect(feedback)
  .setFeedbackForIncorrect(feedback);

4. チェックボックス(複数選択)の追加処理

チェックボックス(複数選択)の問題を追加する際は.addCheckboxItem()を用います。
問題の追加手順はラジオボタンと基本同じですが、正解の番号が複数ある部分についてアレンジが必要です。

var checkboxItem = form.addCheckboxItem();

var choiceList = [];

// 正解の番号を配列に変換
// split処理を用いて「文字列型データ」から「文字列型データの配列」に分割します。
// 分割する際の区切り文字は「,(半角カンマ)」です。
var questions = question.answer.split(',');

question.choices.forEach((choice, index) => {

  if (choice != '') {

    // この選択肢が正解に含まれているかどうかチェック
    // questionsにこの選択肢が含まれているかinclueds処理を用いて確認します。  
    // index + 1は「数値型データ」なので「文字列型データ」に揃えてから確認します。
    let isCorrect = questions.includes( String(index + 1) );

    let choiceObj = checkboxItem.createChoice(choice, isCorrect);

    choiceList.push(choiceObj);
  }

});
const feedback = FormApp.createFeedback().setText(question.feedback).build()

checkboxItem.setTitle(question.title)
  .setHelpText(question.helpText)
  .setPoints(question.point)
  .setChoices(choiceList)
  .setFeedbackForCorrect(feedback)
  .setFeedbackForIncorrect(feedback);

完成

これでラジオボタンとチェックボックスの2種類の問題形式を利用できるフォームの自動生成スプレッドシートが完成しました。

function createForm() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');

  const formTitle = sheet.getRange('B1').getDisplayValue();
  const formDescription = sheet.getRange('B2').getDisplayValue();

  const firstRow = 5;
  const lastRow = sheet.getLastRow();

  const dataRows = lastRow - (firstRow - 1);

  var questionList = sheet.getRange(firstRow, 1, dataRows, 10).getDisplayValues();

  questionList = questionList.map(question => {
    return {
      title: question[0],
      helpText: question[1],
      point: question[2],
      type: question[3],
      answer: question[4],
      choices: [question[5], question[6], question[7], question[8]],
      feedback: question[9]
    }
  });

  const form = FormApp.create(formTitle);

  form.setDescription(formDescription)
    .setIsQuiz(true);


  questionList.forEach(question => {

    if (question.type == 'ラジオボタン') {
      var choiceItem = form.addMultipleChoiceItem();

      var choiceList = [];

      question.choices.forEach((choice, index) => {

        if (choice != '') {

          let isCorrect = question.answer == String(index + 1);

          let choiceObj = choiceItem.createChoice(choice, isCorrect);

          choiceList.push(choiceObj);
        }

      });

      const feedback = FormApp.createFeedback().setText(question.feedback).build()

      choiceItem.setTitle(question.title)
        .setHelpText(question.helpText)
        .setPoints(question.point)
        .setChoices(choiceList)
        .setFeedbackForCorrect(feedback)
        .setFeedbackForIncorrect(feedback);

    }
    else if (question.type == 'チェックボックス') {
      var checkboxItem = form.addCheckboxItem();

      var choiceList = [];
      var questions = question.answer.split(',');

      question.choices.forEach((choice, index) => {

        if (choice != '') {

          let isCorrect = questions.includes( String(index + 1) );

          let choiceObj = checkboxItem.createChoice(choice, isCorrect);

          choiceList.push(choiceObj);
        }

      });
      const feedback = FormApp.createFeedback().setText(question.feedback).build()
      
      checkboxItem.setTitle(question.title)
        .setHelpText(question.helpText)
        .setPoints(question.point)
        .setChoices(choiceList)
        .setFeedbackForCorrect(feedback)
        .setFeedbackForIncorrect(feedback);

    }
    else {
      Browser.msgBox('「'+ question.title +'」は作成できない問題形式です');
    }


  });

  sheet.getRange('B3').setValue(form.getEditUrl());
  Browser.msgBox('「' + formTitle + '」の作成が完了しました');
}

実行結果:
実行用ボタンから実行すると、スプレッドシート上にメッセージボックスが表示され、B3セルにフォームの編集用URLが書き込まれました。

フォームの編集用URLにアクセスすると、このように問題と正解が設定されていることが確認できます。

まとめ

前回の記事に引き続きスプレッドシートからテスト用Googleフォームを作成する方法をご紹介しました。

これで2種類の問題形式が扱えるテストを自動生成できるようになりました。
しかし、Googleフォームでは記述式の問題や画像付き問題も作成できます。
それらについて対応する方法も今後ご紹介する予定です。

テスト用Googleフォームは教育サービスのGoogle Classroomなどにうってつけのとても便利な機能です。
Google Classroomで活用できるテスト用Googleフォームの自動生成についてもいずれは紹介していけたらと思います。

Google Cloudの導入は当社にご相談ください

ITディストリビューターであるTD SYNNEXはGoogle Cloud™ Partner Award を受賞するなど、長年にわたりGoogle™のグローバル認定ディストリビューターとして、総合的な Googleソリューションを提供しています。お客様にとって最適なソリューションの提案や導入、活用をサポートします。

製品・サービスについてのお問合せ

情報収集中の方へ

導入事例やソリューションをまとめた資料をご提供しております。

資料ダウンロード
導入をご検討中の方へ

折り返し詳細のご案内を差し上げます。お問い合わせお待ちしております。

お問い合わせ