Ранее, когда у нас не было своего корпоративного блога, я писал о том, как мы используем Microsoft TFS (Visual Studio Team Servives on Premises) для управления жизненным циклом разработки ПО и для автоматизации тестирования. В частности мы собрали большой набор автотестов по разным системам в один пакет, который запускаем каждый день. Подробнее об этом я рассказывал на конференции DevOpsDaysMoscow ( презентация, видео выступления )В ходе внедрения мы столкнулись с несколькими проблемами:
- Последовательный запуск автотестов в один поток занимает слишком много времени
- Часть падений автотестов не фиксируются
-
Сборки из дженкинса не публикуют результаты тестов в VSTS
все эти проблемы были успешно решены с помощью собственных расширений VSTS:
- Параллельные Сборки
- Автоматические дефекты
-
Доработка стандартной задачи запуска Jenkins job
Подробнее о проблемах и как мы искали решения — в моей прошлой статье. А сегодня я хочу рассказать о том как делать эти расширения и как мы можем помочь разработчикам расширений.
Как ?
Практически с самого начала мы поняли, что самый простой способ решить наши проблемы — это собственные задачи (tasks) или шаги сборки в TFS. Задачи можно писать либо на powershell, либо на typescript. Мы выбрали typescript, и вот почему — фактически это javascript, но с поддержкой типизации. О преимуществах typescript и его использовании существует множество статей, в том числе и на хабре. Для нас же основными преимуществами было следующее:
- IntelliSense and static checks
- Работает на linux агентах
- возможность использования любых npm модулей. Это означает, что не нужно изобретать велосипеды — нужна генерация UUID — она есть, генерация результатов тестов в формате Visual Studio (trx файлов) — она есть, работа с XML — тоже есть.
- Поддержка асинхронности на уровне языка — async/await. Позволяет избавиться от callback hell. Асинхронные методы и вызовы выглядят как синхронные.
-
Для работы c TFS Microsoft создала и опубликовала 2 npm модуля, что опять же избавило от необходимости изобретать велосипед.
Также большой помощью оказалось то, что многие задачи от Microsoft написаны и typescript и опубликованы под открытой лицензией. Кроме того, что это позволяет использовать их как примеры, это дало возможность сделать собственный форк этого репозитория и сделать свой "бутсрап" набор для быстрого создания задач и автоматизации сборки и упаковки расширений.
Что ?
Что представляет собой задача для VSTS? Это набор обязательных компонентов:
- определение задачи
- основной скрипт задачи
- package.json — определение зависимостей
-
иконка задачи
Определение задачи
Определение задачи содержит несколько блоков.
блок идентификации:
{ "id": "b5525419-bae1-44de-a479-d6de6a3ccb2f", "name": "TestTask", "friendlyName": "TestTask", "description": "TestTask", "helpMarkDown": "", "category": "Build", "author": "authorName", "version": { "Major": 1, "Minor": 0, "Patch": 0 }, "instanceNameFormat": "TestTask $(testparam)" }
В этом блоке описывается уникальный id задачи, ее имя, категория и версия. При создании задачи необходимо указывать все эти поля. Поле instanceNameFormat определяет как будет выглядеть имя задачи в сборке VSTS по умолчанию. В нем могут быть указаны параметры из блока параметров в виде $(имя параметра)
блок параметров
В блоке параметров указываются входные параметры задачи, их имена, описания и типы. Параметры могут группироваться для удобства представления на странице настройки задачи. Ниже блок параметром задачи AutoDefects:
{
"groups": [
{
"name": "authentication",
"displayName": "Authentication",
"isExpanded": false
}
],
"inputs": [
{
"name": "Assignees",
"type": "filePath",
"label": "Assignees list file",
"defaultValue": "assignees.json",
"required": false,
"helpMarkDown": "Bug assignees list in json format. Format: {"testrunname":"username"}"
},
{
"name": "authtype",
"type": "pickList",
"label": "Authentication type",
"defaultValue": "oauth",
"required": false,
"helpMarkDown": "Authentication type to access the tfs rest api",
"options": {
"oauth": "OAuth",
"NTLM": "NTLM",
"Basic": "Basic"
},
"groupName" : "authentication"
},
{
"name": "Username",
"type": "string",
"label": "Username",
"defaultValue": "",
"required": false,
"helpMarkDown": "Username to access tfs rest api (NTLM and Basic types)",
"groupName" : "authentication",
"visibilityRule" : "authtype != OAuth"
},
{
"name": "Password",
"type": "string",
"label": "Password",
"defaultValue": "",
"required": false,
"helpMarkDown": "Password to access tfs rest api (NTLM and Basic types)",
"groupName" : "authentication",
"visibilityRule" : "authtype != OAuth"
}
]
}
Параметры, определяющие схему аутентификации вынесены в отдельную группу, которая свернута по умолчанию.
В качестве типов параметров чаще всего используются:
- string — обычная строка.
- pickList — поле выбора с ограниченным списком значений
- filePath — выбор файла внутри репозитория сборки.
блок выполнения
содержит ссылку на главный исполняемый файл задачи
{
"execution": {
"Node": {
"target": "testtask.ts"
}
}
}
блок локализации
{
"messages": {
"taskSucceeded": "All done",
"taskFailed": "Task Failed"
}
}
содержит набор локализованных строк для протоколирования работы задачи в лог файле сборки. Используется реже, чем блоки выше. Сообщения для текущих локальных настроек можно получить вызовом task.loc("messagename");
Главный исполняемый файл
Главный исполняемый файл — это скрипт, который выполняет VSTS при старте задачи. Как минимум должен содержать код для импорта необходимых модулей для работы задачи и обработку ошибок. Например:
import tl = require('vsts-task-lib/task');
import trm = require('vsts-task-lib/toolrunner');
import path = require('path');
import fs = require('fs');
import Q = require("q");
import * as vm from 'vso-node-api';
import * as bi from 'vso-node-api/interfaces/BuildInterfaces';
import * as ci from 'vso-node-api/interfaces/CoreInterfaces';
import * as ti from 'vso-node-api/interfaces/TestInterfaces';
import * as wi from 'vso-node-api/interfaces/WorkItemTrackingInterfaces';
async function run() {
tl.setResourcePath(path.join(__dirname, 'task.json'));
let projId = tl.getVariable("System.TeamProjectId");
try {
} catch(err) {
console.log(err);
console.log(err.stack);
throw err;
}
}
run()
.then(r => tl.setResult(tl.TaskResult.Succeeded,tl.loc("taskSucceeded")))
.catch(r => tl.setResult(tl.TaskResult.Failed,tl.loc("taskFailed")))
Как видно задача представляет собой набор стандартных компонентов, которые мало меняются от задачи к задаче. Поэтому, когда я создавал третью задачу, появилась идея автоматизировать создание задач. Так появился наш "бутстрап", который значительно облегчает жизнь разработику расширений для VSTS.
Как быстрее?
Что нужно сделать когда создаешь задачу для VSTS, кроме собственно написание кода самой задачи? Шаги обычно одни и те же:
- Создать скелет задачи
- Собрать задачу в изолированный компонент
- Упаковать задачу в vsix для публикации в VSTS
Все эти шаги могут быть автоматизированы для ускорения разработки и устранения ненужного ручного труда. Для автоматизации этих шагов и можно применять наш "бутстрап". Схема работы нашего сборщика аналогична сборщику задач от Microsoft — задачи для сборки перечисляются в файле make-options.json в корне проекта:
{
"tasks": [
"AutoDefects",
"ChainBuildsAwaiter",
"ChainBuildsStarter",
"TestTask"
],
...
}
Prerequisite
для создания расширений Вам понадобятся следущее ПО:
- Локальная машина JavaScript — nodejs
- Интерпретатор typescript —
npm install -g typescript
- Сборщик gulp —
npm install -g gulp
- Консольная утилита по работе с VSTS tfx-cli —
npm install -g tfx-cli
Cоздание задачи
Задача TaskName создается командой:
gulp generate –-name TaskName
В результае выполениея команды происходит следующее:
- Задача добавляется в проектный список задач для сборки
- Создание каталога задачи и «скелетных» файлов – taskname.ts, task.json, package.json, typings.json, icon.png
Скелетные файлы содержат минимально необходимый набор данных и кода.
Сборка задач проекта
Сборка задач проекта осуществляется комадной gulp
При этом для всех задач, перечисленных в make-options.json происходит следующее:
- Трансляция .ts в .js
- Установка node_modules в каталог задачи
- Генерация языковых файлов
Упаковка задач
Упаковка задач осуществляется командой gulp mkext [--all] [--exts ext1,ext2]
По умолчанию каждая задача упаковывается в отдельный vsix файл, если указан параметр --all, то все задачи собираются в один большой vsix файл.
По умолчанию упаковываются все задачи, перечисленные в make-options.json, если указан параметр --exts, то упаковываются только перечисленные в параметре расширения.
У нас открыто
Бутстрап опубликован на GitHub — форки, feature requests, pull requests приветствуются.
Очень надеюсь, что эта статья вызовет интерес к Miscosoft VSTS, который на мой взгляд является отличным инструментом групповой работы не только для больших компаний, но и для небольших гибких команд.
Константин Нерадовский,
начальник отдела автоматизации тестирования,
банк "Открытие"
Автор: Открытие