Среда выполнения Apps Script
Библиотека одна, но её функции работают не в одних и тех же местах. От того, по какую сторону черты стоит функция, зависит, получится ли покрыть её модульным тестом и стоит ли она обращения к сервису.
Два рода функций
Независимые от среды функции — это обычный JavaScript. Они принимают значения, возвращают значения и никогда не лезут в глобальные объекты. К этому роду относится всё в lang, net, json, html, time и exception, а также те части appsscript, которые лишь преобразуют строки и простые объекты: разборщики нотации A1, конвертеры букв столбцов и сравнения GridRange.
Привязанные к сервисам функции сами обращаются к глобальному сервису. Они работают только внутри скрипта с выданными правами и в Node не работают вовсе. Сегодня это весь список:
Функция | К какому глобальному объекту обращается |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Всё остальное в appsscript принимает объект сервиса аргументом: appendRows пишет в тот Sheet, который вы ему дали, а requireSheet разглядывает переданное значение, а не добывает его сам. Таким функциям живой объект сервиса нужен, чтобы принести пользу, но искать его они не идут — именно это и позволяет подменить их в тесте.
Пакетность на границе
Каждое обращение к сервису — это поход туда и обратно, и Apps Script засчитывает его в лимит времени выполнения. Помощники для записи существуют ровно поэтому: appendRows делает один вызов независимо от числа строк, а цикл с appendRow — по вызову на строку.
То же рассуждение верно и для проверок. Проверяйте форму данных до того, как они дойдут до сервиса, а не после:
Range.setValues() отвергает рваный массив, но сбой приходит от сервиса, не сообщая, какая строка виновата. Проверка заранее превращает это в ошибку, которой вы управляете.
Квоты
Apps Script следит за квотами на скрипт и на аккаунт: время выполнения, вызовы каждого сервиса, триггеры, запросы URL и прочее. Библиотека их не повышает и не считает — она лишь уменьшает число обращений. Два следствия, которые стоит держать в голове:
Функция, не зависящая от среды, не стоит ничего, кроме процессорного времени. Переносите проверки и преобразования на эту сторону черты везде, где можете.
Функция, привязанная к сервису, может упасть по причинам, не связанным с аргументами. Если
isAdminбросаетAdminDirectoryException, это значит, что расширенный сервис Admin SDK не включён в проекте, а не что пользователь не администратор.
Действующие лимиты опубликованы в Quotas for Google Services.
Тестирование
Набор тестов работает на Vitest в Node, где глобальных объектов Apps Script нет вовсе. Поэтому деление и важно:
Независимые от среды функции покрыты модульными тестами напрямую. Их большинство в
test/, импорт идёт через алиас@/.Привязанные к сервисам — нет. Вызов в Node падает с
ReferenceErrorнаSpreadsheetAppили другом глобальном объекте, поэтому их проверяют вручную на настоящем проекте скрипта. Функции, которые просто принимаютSheet, стоят посередине: их можно прогнать с объектом-заглушкой той же формы, что и класс сервиса.
Когда добавляете функцию, первый вопрос к её устройству такой: может ли она принимать объект сервиса параметром вместо того, чтобы добывать его? Если может — она становится тестируемой, а вызывающий код сохраняет контроль над числом обращений к сервису.
Как писать для обеих сторон
Библиотека рассчитана на среду V8. Две привычки держат код рабочим по обе стороны черты:
Принимайте объекты сервисов аргументами.
appendRows(sheet, rows)работает всюду, откуда бы ни пришёлSheet; функция, которая внутри зовётSpreadsheetApp.getActiveSpreadsheet(), работает только в привязанном скрипте.Отделяйте разбор от добычи.
parseA1Notation— чистая работа со строками, она запускается в Node и потому покрыта тестами;getSheetById— тот же поиск, но выраженный через активную таблицу, и тестами не покрыт. Явная передача таблицы —getSheetById(id, spreadsheet)— возвращает его обратно за черту.
Эта документация сгенерирована нейросетью (Claude) на основе исходного кода библиотеки.