如何在Google App Script中测试触发器函数?

59
Google Apps Script支持触发器,这些触发器会将事件传递给触发函数。不幸的是,开发环境不允许您测试没有参数传递的函数,因此您无法通过这种方式模拟事件。如果尝试,则会出现以下错误之一:

ReferenceError:'e'未定义。

或者

TypeError:无法从未定义的*...*读取属性

(其中e未定义)

可以将事件视为可选参数,并使用有更好的方法在JavaScript中处理可选函数参数吗?中的任何技术将默认值插入到触发函数中。但这会带来风险,即懒惰的程序员(如果是您,请举手!)会留下该代码,并产生意想不到的副作用。

肯定有更好的方法吧?


始终查看所有答案并找到最新的更新,而不仅仅是已接受的答案。当前(2020年)的更新在这里 - TheMaster
4个回答

95

您可以编写一个测试函数,向触发器函数传递模拟事件。以下是测试一个 onEdit() 触发器函数的示例。它会传递一个事件对象,其中包含“电子表格编辑事件”中描述的所有信息,请参见了解事件

要使用它,请在目标 onEdit 函数中设置断点,选择函数 test_onEdit ,然后点击 Debug

/**
 * Test function for onEdit. Passes an event object to simulate an edit to
 * a cell in a spreadsheet.
 *
 * Check for updates: https://dev59.com/7mQo5IYBdhLWcg3wZelg#16089067
 *
 * See https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
 */
function test_onEdit() {
  onEdit({
    user : Session.getActiveUser().getEmail(),
    source : SpreadsheetApp.getActiveSpreadsheet(),
    range : SpreadsheetApp.getActiveSpreadsheet().getActiveCell(),
    value : SpreadsheetApp.getActiveSpreadsheet().getActiveCell().getValue(),
    authMode : "LIMITED"
  });
}

如果您好奇,这是为了测试Google表格三个单元格条件格式的onEdit函数而编写的。

下面是用于电子表格表单提交事件的测试函数。它通过读取表单提交数据来构建模拟事件。这最初是为如何修复onFormSubmit触发器中的TypeError错误?而编写的。

/**
 * Test function for Spreadsheet Form Submit trigger functions.
 * Loops through content of sheet, creating simulated Form Submit Events.
 *
 * Check for updates: https://dev59.com/7mQo5IYBdhLWcg3wZelg#16089067
 *
 * See https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
 */
function test_onFormSubmit() {
  var dataRange = SpreadsheetApp.getActiveSheet().getDataRange();
  var data = dataRange.getValues();
  var headers = data[0];
  // Start at row 1, skipping headers in row 0
  for (var row=1; row < data.length; row++) {
    var e = {};
    e.values = data[row].filter(Boolean);  // filter: https://dev59.com/yGIj5IYBdhLWcg3wx34x#19888749
    e.range = dataRange.offset(row,0,1,data[0].length);
    e.namedValues = {};
    // Loop through headers to create namedValues object
    // NOTE: all namedValues are arrays.
    for (var col=0; col<headers.length; col++) {
      e.namedValues[headers[col]] = [data[row][col]];
    }
    // Pass the simulated event to onFormSubmit
    onFormSubmit(e);
  }
}

提示

在模拟事件时,请尽可能准确地匹配文档中记录的事件对象。

  • 如果您希望验证文档,可以记录从触发函数接收到的事件。

Logger.log( JSON.stringify( e , null, 2 ) );
  • 在电子表格表单提交事件中:

    • 所有namedValues的值都是数组。
    • 时间戳是字符串,并且它们的格式将本地化为表单的语言环境。如果从具有默认格式的电子表格中读取,则它们是日期对象。如果您的触发器函数依赖于时间戳的字符串格式(这是个坏主意),请注意确保适当地模拟该值。
    • 如果您的电子表格中有未包含在表单中的列,则此脚本中的技术将模拟一个包括这些附加值的“事件”,而这不是您将从表单提交中接收到的内容。
    • Issue 4335所述,“新表单”和“新工作表”中的空白答案将被跳过,因此values数组会跳过空白答案。使用filter(Boolean)方法模拟此行为。

  • *格式设置为“纯文本”的单元格将保留日期作为字符串,并且这不是一个好主意。


    1
    @Sergeinsas - 我敢肯定那些值以前不是数组,也许它们随着新的表格而改变了?或者我以前使用它们的方式适用于数组,例如indexOf()搜索。无论如何,最好弄清楚。 - Mogsdad
    我使用的表单提交模拟在没有数组的情况下运作得非常完美......即使使用了数组,也没有明显的差异。我检查了另一个脚本,在那里我写了一个真实表格的日志记录器结果,但我没有看到数组括号,所以有两种可能性: 1 我没有戴眼镜 - 2 没有括号......我不能确定哪一个是真的;-) - Serge insas
    勘误:我发现了一条2013年12月写的日志,内容如下:« /* {"namedValues":{"user name":["test name"],"a comment":["gxfgxdfhdgfhjfgjhfgjxfgj"],"Timestamp":["1/2/2014 22:17:24"]},"values":["1/2/2014 22:17:24","test name","gxfgxdfhdgfhjfgjhfgjxfgj"],"source":{},"range":{"rowStart":2,"rowEnd":2,"columnEnd":3,"columnStart":1}} */  » 括号在那里!!!所以它并不是很新...摘自:「Google Apps Script for Beginners [eBook].epub.」iBooks. - Serge insas
    2
    test_onEdit在从GScript IDE运行/调试时会出现错误:“请先选择一个活动工作表”。我尝试在test_onEdit中打开电子表格并设置活动工作表,但是当onEdit被调用时,相同的错误会阻止执行。我是否漏掉了某个前置步骤? - Baker
    1
    JSON.stringify(e) = 太棒了! - Jonathan
    显示剩余2条评论

    16

    更新2020-2021:

    无需像之前的答案建议的那样使用任何种类的模拟事件。

    如问题所述,如果您直接在脚本编辑器中“运行”函数,则会抛出诸如

    TypeError: 无法从未定义的对象读取属性 ...

    这些不是真正的错误。这个错误只是因为您在没有事件的情况下运行了函数。如果您的函数表现不如预期,您需要找出实际的错误:

    要测试触发器函数,请执行以下操作:

    1. 手动触发相应的事件:即,要测试onEdit,请编辑工作表中的单元格;要测试onFormSubmit,请提交虚拟表单响应;要测试doGet,请将浏览器导航到已发布的webapp /exec url。

    2. 如果有任何错误,则记录到stackdriver中。要查看这些日志,请执行以下操作:

      • 在脚本编辑器中,单击左侧栏上的执行图标(旧版编辑器:查看 > 执行)。

      • 或者,点击此处 > 点击您感兴趣的项目 > 单击左侧栏上的“执行”图标(第四个)

  • 在执行页面中,您会找到执行列表。请确保清除顶部左侧的任何筛选器(如“Ran as:Me”),以显示所有执行项。单击您感兴趣的执行项,它将显示导致触发器失败的错误信息(以红色显示)。

  • 注意:有时由于错误,日志无法显示。特别是对于由匿名用户运行的Web应用程序而言更为常见。在这种情况下,建议切换默认的Google Cloud项目到标准的Google Cloud项目,并直接使用“查看> Stackdriver日志记录”。有关详细信息,请参见此处

  • 若需要进一步调试,您可以编辑代码,在任何您感兴趣的代码行后添加console.log(/*您感兴趣的对象*/)以查看该对象的详细信息。强烈建议您将要查找的对象转换为字符串:console.log(JSON.stringify(e)),因为日志查看器具有某些特殊性质。添加console.log()后,请重复从第1步开始进行调试,直到缩小问题范围。
  • 恭喜!您已成功找出了问题并跨过了第一个障碍。


    1
    这个解决方案不适用于插件开发测试,但更多的是插件开发问题本身。 - Kos

    10

    2017更新: 使用Stackdriver Logging为Google应用脚本调试事件对象。在脚本编辑器的菜单栏中,转到:查看> Stackdriver日志记录以查看或流式传输日志。

    console.log()将写入DEBUG级别的消息

    onEdit()使用示例:

    function onEdit (e) {
      var debug_e = {
        authMode:  e.authMode,  
        range:  e.range.getA1Notation(),    
        source:  e.source.getId(),
        user:  e.user,   
        value:  e.value,
        oldValue: e. oldValue
      }
    
      console.log({message: 'onEdit() Event Object', eventObject: debug_e});
    }
    

    关于onFormSubmit()的示例:

    function onFormSubmit (e) {
      var debug_e = {
        authMode:  e.authMode,  
        namedValues: e.namedValues,
        range:  e.range.getA1Notation(),
        value:  e.value
      }
    
      console.log({message: 'onFormSubmit() Event Object', eventObject: debug_e});
    }
    

    示例onChange()

    function onChange (e) {
      var debug_e = {
        authMode:  e.authMode,  
        changeType: changeType,
        user:  e.user
      }
    
      console.log({message: 'onChange() Event Object', eventObject: debug_e});
    }
    

    然后检查在Stackdriver UI中标记为message字符串的日志,以查看输出。


    0
    作为上述方法(2020更新)的补充,以下是我用来跟踪触发代码并已经节省了很多时间的小例程。此外,我打开了两个窗口:一个带有堆栈驱动程序(执行),另一个带有代码(大多数驻留在库中),因此我可以轻松地找到罪魁祸首。

    /**
     *
     * like Logger.log %s in text is replaced by subsequent (stringified) elements in array A
     * @param {string | object} text %s in text is replaced by elements of A[], if text is not a string, it is stringified and A is ignored
     * @param {object[]} A array of objects to insert in text, replaces %s
     * @returns {string} text with objects from A inserted 
     */
    function Stringify(text, A) {
      var i = 0 ;
      return (typeof text == 'string') ? 
          text.replace(
            /%s/g, 
            function(m) { 
              if( i >= A.length) return m ;
              var a = A[i++] ;
              return (typeof a == 'string') ? a :  JSON.stringify(a) ;
            } ) 
          : (typeof text == 'object') ? JSON.stringify(text) : text ;
    }
    
    /* use Logger (or console) to display text and variables. */
    function T(text) {
      Logger.log.apply(Logger, arguments) ;
      var Content = Stringify( text, Array.prototype.slice.call(arguments,1) ) ;
      return Content ;
    }
    
    /**** EXAMPLE OF USE ***/
    function onSubmitForm(e) {
      T("responses:\n%s" , e.response.getItemResponses().map(r => r.getResponse()) ;
    }


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接