Процесс разработки не начинается и не заканчивается на написании кода программного продукта. Мы пишем документацию, придумываем, как это всё оттестировать, и заботимся о том, чтобы доступность приложения была на высоком уровне.
Мы все делаем привычные вещи привычным для нас способом. Порой выполняя много ручной и неэффективной работы. Но что, если есть другой, радикальный подход. Можно ли формализовать свою деятельность и переложить её в код? Какие практики и инструменты для этого использовать?
В докладе будет представлен личный опыт автора по автоматизации различных элементов разработки ПО.
13. Что такое код?
● Коллекция инструкций
● Человекочитаемый формат
○ plain text
● Может быть понят и “проигран”
○ после компиляции
○ интерпретатором
13
29. # checkout repositories from git
- include: checkout.yml
# configure your local environment
- include: configure.yml
# add useful mappings to your hosts file
- include: hosts.yml
# add gradle support
- include: gradle.yml
# clean and build all projects
- include: build.yml
# delpoy all services to dev
- include: deploy.yml
29
Алгоритм
36. {% if services is not none %}{% for service in services %}
if (project.name == '{{ service.name }}') {
{% if 'sourceDir' in service %}
main.java.srcDir '{{ service.sourceDir }}'
{% endif %}{% if 'testDir' in service %}
test.java.srcDir '{{ service.testDir }}'
{% endif %}
}
Условные операторы
36
37. {% if services is not none %}{% for service in services %}
if (project.name == '{{ service.name }}') {
{% if 'sourceDir' in service %}
main.java.srcDir '{{ service.sourceDir }}'
{% endif %}{% if 'testDir' in service %}
test.java.srcDir '{{ service.testDir }}'
{% endif %}
}
Опять циклы
37
38. {% if services is not none %}{% for service in services %}
if (project.name == '{{ service.name }}') {
{% if 'sourceDir' in service %}
main.java.srcDir '{{ service.sourceDir }}'
{% endif %}{% if 'testDir' in service %}
test.java.srcDir '{{ service.testDir }}'
{% endif %}
}
Переменные
38
40. Install apps Code Checkout
Configure
workspace
Multiplie Use
40
Петля обратной связи
git
41. Что получили
● Автоконфигурация рабочего
пространства
○ повторяемая
○ немутабельная
● Можно дать скрипт новичку
● Можно и нужно пользоваться этим
каждый день
41
85. attacker := vegeta.NewAttacker()
var metrics vegeta.Metrics
for res := range attacker.Attack(targeter, rate, duration) {
metrics.Add(res)
}
metrics.Close()
if metrics.Success*100 < test.SLA.SuccessRate {
os.Exit(1)
}
if metrics.Latencies.P99() > SLA.Latency*time.Millisecond.Nanoseconds() {
os.Exit(1)
}
Немного go-кода
85
86. attacker := vegeta.NewAttacker()
var metrics vegeta.Metrics
for res := range attacker.Attack( targeter, rate, duration) {
metrics.Add(res)
}
metrics.Close()
if metrics.Success*100 < test.SLA.SuccessRate {
os.Exit(1)
}
if metrics.Latencies.P99() > SLA.Latency*time.Millisecond.Nanoseconds() {
os.Exit(1)
}
Майним метрики
86
87. attacker := vegeta.NewAttacker()
var metrics vegeta.Metrics
for res := range attacker.Attack(targeter, rate, duration) {
metrics.Add(res)
}
metrics.Close()
if metrics.Success*100 < test.SLA.SuccessRate {
os.Exit(1)
}
if metrics.Latencies.P99() > SLA.Latency*time.Millisecond.Nanoseconds() {
os.Exit(1)
}
Не слишком ли много ошибок?
87
88. attacker := vegeta.NewAttacker()
var metrics vegeta.Metrics
for res := range attacker.Attack(targeter, rate, duration) {
metrics.Add(res)
}
metrics.Close()
if metrics.Success*100 < test.SLA.SuccessRate {
os.Exit(1)
}
if metrics.Latencies.P99() > SLA.Latency*time.Millisecond.Nanoseconds() {
os.Exit(1)
}
Уложились ли по времени?
88
89. > cat config.json | docker run -i apistress -config=stdin
> echo $?
0
89
Код возврата
90. Requests [total, rate] 200, 10.05
Duration [total, attack, wait] 23.04784s, 19.899754743s, 3.148093811s
Latencies [mean, 50, 95, 99, max] 3.023677499s, ..., 11.832287083s
Bytes In [total, mean] 6874, 34.37
Bytes Out [total, mean] 1349, 6.75
Success [ratio] 35.50%
Status Codes [code:count] 0:129 200:71
Error Set:
Get http://host:9000/rent-service/rent: EOF
Get http://host:9000/rent-service/rent: http: server closed idle connection
...
Test#2
90
91. > cat config.json | docker run -i apistress -config=stdin
> echo $?
1
91
Код возврата
92. Unit tests API tests Stress tests UI tests
Selenium,
Selenide
92
93. Что получили
● Все тестовые сценарии в коде
● Можно встроить в процесс сборки
или доставки ПО
● Если хотите, то можно
генерировать отчёты для разбора
полётов
93
98. ISupplier<String, Ctx> srcsetSupplier = (ctx) -> {
if (configuration. isDoubleDensityAvatarsEnabled(user.getModel())) {
String link = imageSrc.getModel();
return linkBuilder.createSourceSetLink(link);
}
return "";
};
Очень простой эксперимент
98
99. Code Branches
One server or
partition
99
//by partition
app.photos.doubleDensityAvatarsEnabled: 0
Step#1
100. Code Branches
One server or
partition
Part of servers
or partitions
100
//by partition
app.photos.doubleDensityAvatarsEnabled: 0,4,7-9
Step#1 Step#2
101. Code Branches
One server or
partition
Part of servers
or partitions
All servers or
partitions
101
//by partition
app.photos.doubleDensityAvatarsEnabled: ALL
Step#1 Step#2 Step#3
115. Что получили
● Эксперименты хранятся в git-
репозитории
● Можно применить любой шаг в
любой момент времени на любой
среде
● Можно встроить в пайплайн
доставки
115
122. = Hippo Rent Service
This is documentation for Open API of our hippo renting service
== Methods
=== Rent
==== Request specification
===== Headers
//тут опишем http-заголовки
===== Example
//а здесь будут примеры вызова
==== Response specification
===== Response fields
//здесь описание полей ответа
===== Example
//ну и пример того, что вообще ожидать в ответе
Вот такой документ
122
130. = Hippo Rent Service
This is documentation for Open API of our hippo renting service
== Methods
=== Rent
==== Request specification
===== Headers
include::{snippets}/rent-hippo/request-headers.adoc[]
===== Example
include::{snippets}/rent-hippo/http-request.adoc[]
==== Response specification
===== Response fields
include::{snippets}/rent-hippo/response-fields.adoc[]
===== Example
include::{snippets}/rent-hippo/http-response.adoc[]
Вставляем их в документ
130
131. = Hippo Rent Service
This is documentation for Open API of our hippo renting
service
== Methods
=== Rent
==== Request specification
===== Headers
include::{snippets}/rent-hippo/request-headers.adoc[]
===== Example
include::{snippets}/rent-hippo/http-request.adoc[]
==== Response specification
===== Response fields
include::{snippets}/rent-hippo/response-fields.adoc[]
===== Example
include::{snippets}/rent-hippo/http-response.adoc[]
131
133. Что получили?
● Документация как код
○ лежит в репозитории с кодом
○ встроена в цикл сборки
○ рендерится в html, pdf и т.д.
○ почти всегда актуальна
● Синергия с тестами
133
194. Позитивные выводы
● Почти любой процесс можно
формализовать, представить в
виде кода и автоматизировать
● Мы все пишем код, хотя можем
думать, что это не так
● Рано или поздно всё превращается
в код
194
195. Trade-offs
● Необходимы как разовые
“капиталовложения”, так и
постоянные затраты ресурсов
● Могут потребоваться изменения в
архитектуре
● Требует дисциплины и более
высокой квалификации
специалистов
195