Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
7b99fba63e | |||
143aae2e48 | |||
d0becd4063 | |||
560e9ee6db | |||
db413e4a35 | |||
e8fc6d0816 | |||
be7efbbfa7 | |||
b8161eef93 | |||
bcfd13442f | |||
f3dd9157c7 | |||
40076ad3c1 | |||
60e09751da |
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,3 +1,16 @@
|
||||
*.pyc
|
||||
|
||||
.pytest_cache
|
||||
.coverage
|
||||
|
||||
venv
|
||||
|
||||
*.aux
|
||||
*.log
|
||||
*.nav
|
||||
*.out
|
||||
*.pdf
|
||||
*.pyg
|
||||
*.snm
|
||||
*.synctex.gz
|
||||
*.toc
|
||||
|
@ -1,6 +1,7 @@
|
||||
stages:
|
||||
- linting
|
||||
- test
|
||||
- compile_slides
|
||||
|
||||
|
||||
flake8:
|
||||
@ -25,5 +26,24 @@ test:
|
||||
stage: test
|
||||
image: python:3-alpine
|
||||
before_script:
|
||||
- pip install pytest --no-cache-dir
|
||||
script: pytest --showlocals .
|
||||
- pip install pytest pytest-cov --no-cache-dir
|
||||
script: pytest --showlocals --cov=main --cov=main_test --cov-report=term-missing .
|
||||
|
||||
slides:
|
||||
stage: compile_slides
|
||||
image: aergus/latex
|
||||
before_script:
|
||||
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$SSH_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||
- mkdir -p ~/.ssh
|
||||
- chmod 0700 ~/.ssh
|
||||
- ssh-keyscan ynerant.fr >> ~/.ssh/known_hosts
|
||||
- chmod 0644 ~/.ssh/known_hosts
|
||||
script:
|
||||
- latexmk -cd -pdf slides/seminaire-ci.tex
|
||||
- scp slides/seminaire-ci.pdf gitlab-ci@ynerant.fr:gitlab-ftp/seminaire-ci.pdf
|
||||
artifacts:
|
||||
paths:
|
||||
- slides/seminaire-ci.pdf
|
||||
expire_in: 1 mo
|
||||
|
6
README.md
Normal file
6
README.md
Normal file
@ -0,0 +1,6 @@
|
||||
[](https://gitlab.crans.org/ynerant/seminaire-ci/-/commits/master)
|
||||
[](https://gitlab.crans.org/ynerant/seminaire-ci/-/commits/master)
|
||||
|
||||
# Séminaire intégration continue
|
||||
|
||||
Ce dépôt contient le script utilisé lors du séminaire Crans du 11 février 2021, ainsi que les slides.
|
48
main.py
48
main.py
@ -14,7 +14,7 @@ from random import choice
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
def main(): # pragma: nocover
|
||||
"""
|
||||
Fonction principale, qui va attendre les instructions de l'utilisateur.
|
||||
"""
|
||||
@ -51,16 +51,16 @@ def seminaire():
|
||||
"""
|
||||
Affiche les slides du séminaire.
|
||||
"""
|
||||
print("\x4c\x61\x69\x73\x73\x65\x20\x74\x6f\x6d\x62\x65"
|
||||
"\x72\x2c\x20\x74\x6f\x6e\x20\x74\x72\x75\x63\x20"
|
||||
"\x65\x73\x74\x20\x74\x72\x6f\x70\x20\x62\x65\x75"
|
||||
"\x67\x75\xe9\x2c\x20\x74\x75\x20\x64\x65\x76\x72"
|
||||
"\x61\x69\x73\x20\x70\x61\x73\x73\x65\x72\x20\x64"
|
||||
"\x75\x20\x74\x65\x6d\x70\x73\x20\xe0\x20\xe9\x63"
|
||||
"\x72\x69\x72\x65\x20\x64\x65\x73\x20\x74\x65\x73"
|
||||
"\x74\x73\x20\x65\x74\x20\x66\x61\x69\x72\x65\x20"
|
||||
"\x64\xe9\x66\x69\x6c\x65\x72\x20\x74\x65\x73\x20"
|
||||
"\x73\x6c\x69\x64\x65\x73")
|
||||
return "\x4c\x61\x69\x73\x73\x65\x20\x74\x6f\x6d\x62\x65" \
|
||||
"\x72\x2c\x20\x74\x6f\x6e\x20\x74\x72\x75\x63\x20" \
|
||||
"\x65\x73\x74\x20\x74\x72\x6f\x70\x20\x62\x65\x75" \
|
||||
"\x67\x75\xe9\x2c\x20\x74\x75\x20\x64\x65\x76\x72" \
|
||||
"\x61\x69\x73\x20\x70\x61\x73\x73\x65\x72\x20\x64" \
|
||||
"\x75\x20\x74\x65\x6d\x70\x73\x20\xe0\x20\xe9\x63" \
|
||||
"\x72\x69\x72\x65\x20\x64\x65\x73\x20\x74\x65\x73" \
|
||||
"\x74\x73\x20\x65\x74\x20\x66\x61\x69\x72\x65\x20" \
|
||||
"\x64\xe9\x66\x69\x6c\x65\x72\x20\x74\x65\x73\x20" \
|
||||
"\x73\x6c\x69\x64\x65\x73"
|
||||
|
||||
|
||||
def blague():
|
||||
@ -68,10 +68,10 @@ def blague():
|
||||
Renvoie une blague aléatoire.
|
||||
"""
|
||||
return choice(
|
||||
["Je vais vous raconter une blague sur, mais vous "
|
||||
["Je vais vous raconter une blague sur UDP, mais vous "
|
||||
"ne l'aurez peut-être pas.",
|
||||
"Connaissez-vous la différence entre la théroie "
|
||||
"et la pratique ? Il n'y en left pas, en théorie."])
|
||||
"Connaissez-vous la différence entre la théorie "
|
||||
"et la pratique ? Il n'y en a pas, en théorie."])
|
||||
|
||||
|
||||
def calcul(left: int, right: int, res: int, operation='+'):
|
||||
@ -79,19 +79,23 @@ def calcul(left: int, right: int, res: int, operation='+'):
|
||||
Vérifie si left operation b == c, où a, b et c sont des entiers.
|
||||
L'opération peut être +, -, *, /, &, |, ^, % ou l'un de ses alias anglais.
|
||||
"""
|
||||
left = int(left)
|
||||
right = int(right)
|
||||
res = int(res)
|
||||
|
||||
if operation in ['+', 'add', 'sum']:
|
||||
result = left + right
|
||||
if operation in ['-', 'sub']:
|
||||
elif operation in ['-', 'sub']:
|
||||
result = left - right
|
||||
if operation in ['*', 'mul', 'prod']:
|
||||
elif operation in ['*', 'mul', 'prod']:
|
||||
result = left * right
|
||||
if operation in ['/', 'div']:
|
||||
elif operation in ['/', 'div']:
|
||||
result = left / right
|
||||
if operation in ['&', 'and']:
|
||||
elif operation in ['&', 'and']:
|
||||
result = left & right
|
||||
if operation in ['|', 'or']:
|
||||
elif operation in ['|', 'or']:
|
||||
result = left | right
|
||||
if operation in ['^', 'xor']:
|
||||
elif operation in ['^', 'xor']:
|
||||
result = left ^ right
|
||||
else:
|
||||
result = left % right
|
||||
@ -102,7 +106,7 @@ def tri(*args):
|
||||
"""
|
||||
Trie les éléments donnés en argument.
|
||||
"""
|
||||
return sorted(args)
|
||||
return sorted(int(number) for number in args)
|
||||
|
||||
|
||||
def stop(exit_code: int = 0):
|
||||
@ -113,5 +117,5 @@ def stop(exit_code: int = 0):
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__': # pragma: nocover
|
||||
main()
|
||||
|
62
main_test.py
62
main_test.py
@ -4,6 +4,7 @@
|
||||
Exécution des tests du script.
|
||||
"""
|
||||
|
||||
from random import randint, seed, shuffle
|
||||
import unittest
|
||||
|
||||
import main
|
||||
@ -16,7 +17,8 @@ class TestMain(unittest.TestCase):
|
||||
|
||||
def test_aide(self):
|
||||
"""
|
||||
On essaie d'afficher l'aide, et on vérifie si ça affiche la bonne chose.
|
||||
On essaie d'afficher l'aide,
|
||||
et on vérifie si ça affiche la bonne chose.
|
||||
"""
|
||||
res = main.commande("aide")
|
||||
lines = res.split("\n")
|
||||
@ -27,3 +29,61 @@ class TestMain(unittest.TestCase):
|
||||
self.assertTrue(lines[3].startswith("calcul"))
|
||||
self.assertTrue(lines[4].startswith("tri"))
|
||||
self.assertTrue(lines[5].startswith("stop"))
|
||||
|
||||
def test_seminaire(self):
|
||||
"""
|
||||
Test de l'affichage du "séminaire".
|
||||
"""
|
||||
res = main.commande("seminaire")
|
||||
self.assertEqual(len(res), 114)
|
||||
|
||||
def test_blague(self):
|
||||
"""
|
||||
On teste les blagues, et on vérifie si
|
||||
elles fonctionnent bien.
|
||||
"""
|
||||
seed(1)
|
||||
res = main.commande("blague")
|
||||
self.assertEqual(res, "Je vais vous raconter une blague sur UDP, "
|
||||
"mais vous ne l'aurez peut-être pas.")
|
||||
|
||||
seed(5)
|
||||
res = main.commande("blague")
|
||||
self.assertEqual(res, "Connaissez-vous la différence entre la "
|
||||
"théorie et la pratique ? "
|
||||
"Il n'y en a pas, en théorie.")
|
||||
|
||||
def test_calcul(self):
|
||||
"""
|
||||
On vérifie que certains calculs basiques, puis aléatoires,
|
||||
marchent bien.
|
||||
"""
|
||||
self.assertTrue(main.commande("calcul", 1, 2, 3))
|
||||
self.assertFalse(main.commande("calcul", 1, 2, 2, '+'))
|
||||
self.assertTrue(main.commande("calcul", 1, 2, 2, '*'))
|
||||
self.assertTrue(main.commande("calcul", 42, 3, 14, 'div'))
|
||||
self.assertTrue(main.commande("calcul", 16777215, 42, 42, 'and'))
|
||||
self.assertTrue(main.commande("calcul", 16777213, 42, 16777215, 'or'))
|
||||
self.assertFalse(main.commande("calcul", 15, 20, 35, 'xor'))
|
||||
self.assertTrue(main.commande("calcul", 15, 20, 27, '^'))
|
||||
self.assertTrue(main.commande("calcul", 100, 76, 24, 'unknown'))
|
||||
|
||||
left, right = randint(0, 0x7FFFFFFF), randint(0, 0x7FFFFFFF)
|
||||
self.assertTrue(main.commande("calcul", left, right,
|
||||
left - right, "-"))
|
||||
|
||||
def test_tri(self):
|
||||
"""
|
||||
À partir d'une liste donnée, on trie la liste
|
||||
et on vérifie qu'elle est bien triée.
|
||||
"""
|
||||
my_list = ["23", "16", "234567", "-4"]
|
||||
shuffle(my_list)
|
||||
sorted_list = [-4, 16, 23, 234567]
|
||||
self.assertEqual(main.commande("tri", *my_list), sorted_list)
|
||||
|
||||
def test_stop(self):
|
||||
"""
|
||||
On vérifie que le programme s'arrête bien.
|
||||
"""
|
||||
self.assertRaises(SystemExit, main.commande, "stop", 0)
|
||||
|
132
slides/beamerthemecrans.sty
Normal file
132
slides/beamerthemecrans.sty
Normal file
@ -0,0 +1,132 @@
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%%%%%%%% BEAMER THEME FOR CRANS %%%%%%%%%%%%%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% place in same folder as your .tex
|
||||
% \usetheme{ens} in preamble
|
||||
% also need crans-logo.png
|
||||
|
||||
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
|
||||
\ProvidesPackage{ensbeamer}[2019/09/04 Beamer Theme]
|
||||
\RequirePackage{tikz}
|
||||
\RequirePackage{xcolor}
|
||||
|
||||
\makeatletter
|
||||
\newif\if@section\@sectionfalse
|
||||
\DeclareOption{sectiontitle}{
|
||||
\@sectiontrue
|
||||
}
|
||||
|
||||
\ProcessOptions\relax
|
||||
|
||||
\definecolor{rougecrans}{RGB}{207,14,34} % rougecrans !
|
||||
|
||||
\setbeamercolor{structure}{fg=rougecrans}
|
||||
%\setbeamercolor{sidebar}{fg=rougecrans,bg=white}
|
||||
\setbeamercolor{sidebar}{bg=white,fg=rougecrans}
|
||||
%\setbeamercolor{title in sidebar}{fg=white}
|
||||
%\setbeamercolor{section in sidebar}{fg=white}
|
||||
\setbeamercolor{section in sidebar shaded}{fg=rougecrans!40}
|
||||
\setbeamercolor{subsection in sidebar}{fg=rougecrans}
|
||||
\setbeamercolor{subsection in sidebar shaded}{fg=rougecrans!60}
|
||||
\setbeamercolor{frametitle}{fg=rougecrans,bg=white}
|
||||
|
||||
\def\swidth{2cm}
|
||||
|
||||
%
|
||||
% -----------------------------
|
||||
% Title Page
|
||||
|
||||
\setbeamerfont{title}{size=\huge}
|
||||
\setbeamerfont{author}{size=\large}
|
||||
\setbeamerfont{institut}{size=\Large}
|
||||
\setbeamerfont{subtitle}{size=\Large}
|
||||
\setbeamerfont{date}{size=\Large}
|
||||
\setbeamertemplate{title page}{%
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
\fill[white]
|
||||
(current page.north west) rectangle (current page.south east);
|
||||
\node
|
||||
at ([yshift=+.15\textheight]current page.center) (title)
|
||||
{\usebeamerfont{title}\textcolor{rougecrans}{\inserttitle}};
|
||||
|
||||
\node[below=2em]
|
||||
at(title) (subtitle)
|
||||
{\usebeamerfont{subtitle}\textcolor{rougecrans}{\insertsubtitle}};
|
||||
|
||||
\node
|
||||
at ([yshift=-70pt]current page.center) (institute)
|
||||
{\usebeamerfont{institute}\textcolor{rougecrans}{\insertdate}};
|
||||
|
||||
\node
|
||||
at ([yshift=-50pt]current page.center) (author)
|
||||
{\usebeamerfont{author}\textcolor{rougecrans}{\insertauthor}};
|
||||
|
||||
\node [opacity=.15] at (current page.center) {\includegraphics[height=0.9\textheight]{crans-logo} };
|
||||
|
||||
\end{tikzpicture}
|
||||
}
|
||||
|
||||
|
||||
|
||||
%
|
||||
% --------------------------------------
|
||||
% Sidebar
|
||||
|
||||
\useoutertheme[height=0pt,width=\swidth, hideothersubsections]{sidebar}
|
||||
|
||||
\setbeamertemplate{sidebar left}
|
||||
{
|
||||
{\vspace{0.9em}
|
||||
\hspace{-0.4em}
|
||||
\begin{minipage}{\swidth}
|
||||
\centering
|
||||
\insertlogo
|
||||
\end{minipage}
|
||||
\usebeamerfont{title in sidebar}%
|
||||
\vskip1em%
|
||||
\usebeamercolor[fg]{title in sidebar}%
|
||||
\insertshorttitle[width=\swidth,center,respectlinebreaks]\par%
|
||||
\vskip.5em%
|
||||
}%
|
||||
\insertverticalnavigation{\swidth}%
|
||||
\vfill
|
||||
\hbox to2cm{\hskip0.6cm\usebeamerfont{section in sidebar} \strut\usebeamercolor[fg]{section in sidebar}\insertframenumber/\inserttotalframenumber\hfill}%
|
||||
\vskip3pt%
|
||||
}%
|
||||
|
||||
|
||||
% --------------------------------------------------------------------------
|
||||
% Section as frame title
|
||||
\if@section
|
||||
\addtobeamertemplate{frametitle}{
|
||||
\let\insertframetitle\insertsectionhead}{}
|
||||
\addtobeamertemplate{frametitle}{
|
||||
\let\insertframesubtitle\insertsubsectionhead}{}
|
||||
|
||||
\makeatletter
|
||||
\CheckCommand*\beamer@checkframetitle{\@ifnextchar\bgroup\beamer@inlineframetitle{}}
|
||||
\renewcommand*\beamer@checkframetitle{\global\let\beamer@frametitle\relax\@ifnextchar\bgroup\beamer@inlineframetitle{}}
|
||||
|
||||
\fi
|
||||
|
||||
\setbeamertemplate{caption}{\raggedright\insertcaption\par}
|
||||
\setbeamertemplate{navigation symbols}[horizontal]
|
||||
|
||||
% ---------------------------------------------------
|
||||
% BLOC
|
||||
|
||||
\setbeamertemplate{blocks}[rounded][shadow=false]
|
||||
\setbeamercolor{block body}{fg=black,bg=rougecrans!20}
|
||||
\setbeamercolor{block title}{fg=black,bg=rougecrans!40}
|
||||
|
||||
\setbeamercolor{block body example}{fg=black,bg=gray!40}
|
||||
\setbeamercolor{block title example}{fg=black,bg=gray}
|
||||
|
||||
\setbeamercolor{block body alerted}{fg=black,bg=blue!40}
|
||||
\setbeamercolor{block title alerted}{fg=black,bg=blue}
|
||||
|
||||
\setbeamercolor{subitem}{fg=rougecrans!20}
|
||||
|
||||
\makeatother
|
||||
% LOGO :
|
||||
\logo{\includegraphics[width=\swidth]{crans-logo.png}}
|
BIN
slides/crans-logo.png
Normal file
BIN
slides/crans-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
slides/img/note-500.png
Normal file
BIN
slides/img/note-500.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
BIN
slides/img/re2o-500.png
Normal file
BIN
slides/img/re2o-500.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 593 KiB |
BIN
slides/img/youtube-500.jpg
Normal file
BIN
slides/img/youtube-500.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
648
slides/seminaire-ci.tex
Normal file
648
slides/seminaire-ci.tex
Normal file
@ -0,0 +1,648 @@
|
||||
%\documentclass[handout,aspectratio=169]{beamer}
|
||||
\documentclass[aspectratio=169]{beamer}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[french]{babel}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{ulem}
|
||||
\usepackage{listings}
|
||||
\lstset{
|
||||
inputencoding=utf8,
|
||||
mathescape,
|
||||
basicstyle=\tiny\ttfamily,
|
||||
numberstyle=\scriptsize\ttfamily\color{gray},
|
||||
showstringspaces=false,
|
||||
tabsize=2,
|
||||
numbers=left,
|
||||
xleftmargin=8mm,
|
||||
frame=lines,
|
||||
framexleftmargin=8mm,
|
||||
framerule=1pt,
|
||||
rulecolor=\color{green!50!black},
|
||||
backgroundcolor=\color{green!5},
|
||||
commentstyle=\color{gray},
|
||||
morecomment=[l][commentstyle]{//},
|
||||
classoffset=0,
|
||||
keywords={Pour, allant, Si, alors, Sinon, Fin},
|
||||
keywordstyle=\color{blue!75},
|
||||
classoffset=1,
|
||||
morekeywords={Afficher},
|
||||
keywordstyle=\color{green!50!black},
|
||||
classoffset=0,
|
||||
morestring=[b]",
|
||||
stringstyle=\color{red!75},
|
||||
literate=
|
||||
{é}{{\'e}}{1}%
|
||||
{è}{{\`e}}{1}%
|
||||
{à}{{\`a}}{1}%
|
||||
{â}{{\^a}}{1}%%%
|
||||
{ç}{{\c{c}}}{1}%
|
||||
{œ}{{\oe}}{1}%
|
||||
{ù}{{\`u}}{1}%
|
||||
{É}{{\'E}}{1}%
|
||||
{È}{{\`E}}{1}%
|
||||
{À}{{\`A}}{1}%
|
||||
{Ç}{{\c{C}}}{1}%
|
||||
{Œ}{{\OE}}{1}%
|
||||
{Ê}{{\^E}}{1}%
|
||||
{ê}{{\^e}}{1}%
|
||||
{î}{{\^i}}{1}%
|
||||
{ï}{{\"i}}{1}%%%
|
||||
{ô}{{\^o}}{1}%
|
||||
{û}{{\^u}}{1}%
|
||||
}
|
||||
|
||||
\usetheme[sectiontitle]{crans}
|
||||
|
||||
\title{Automatisez vos tâches avec une intégration continue}
|
||||
\subtitle{Séminaire CRANS}
|
||||
\author[]{\textsc{Yohann D'ANELLO}}
|
||||
\date{11 février 2021}
|
||||
|
||||
|
||||
\begin{document}
|
||||
\begin{frame}
|
||||
\maketitle
|
||||
\end{frame}
|
||||
|
||||
\section{Introduction}
|
||||
|
||||
\subsection{Discussion avec un chatbot}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Introduction avec un petit chatbot}
|
||||
\begin{lstlisting}[language=bash]
|
||||
git clone https://gitlab.crans.org/ynerant/seminaire-ci.git
|
||||
cd seminaire-ci
|
||||
git checkout v1
|
||||
python3 main.py
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Des erreurs}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Le script ne semble pas fonctionner}
|
||||
\begin{lstlisting}[language=Python]
|
||||
File "/tmp/seminaire-ci/main.py", line 7
|
||||
print "Bienvenue dans le chatbot du séminaire de l'intégration continue !"
|
||||
^
|
||||
SyntaxError: Missing parentheses in call to 'print'.
|
||||
Did you mean print("Bienvenue dans le chatbot du séminaire de l'intégration continue !")?
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}
|
||||
\includegraphics[width=180px]{img/re2o-500.png} \includegraphics[width=180px]{img/note-500.png}
|
||||
\includegraphics[width=180px]{img/youtube-500.jpg}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Trois solutions}
|
||||
Il existe différents types de développeurs
|
||||
\begin{itemize}
|
||||
\item Celui/celle qui fait confiance à son code
|
||||
\pause
|
||||
\item Celui/celle qui ne fait pas confiance à son code et qui teste tout
|
||||
en permance
|
||||
\pause
|
||||
\item Celui/celle qui ne fait pas confiance à son code et qui embauche un
|
||||
esclave pour tout tester en permanence
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\pause
|
||||
|
||||
\og{} Le code prouve que ça marche \fg{}
|
||||
|
||||
\og{} De toute façon ce que j'ai écrit peut rien péter \fg{}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Comment éviter les erreurs ?}
|
||||
|
||||
\begin{frame}
|
||||
\begin{itemize}
|
||||
\item Relire 100 fois son code
|
||||
\pause
|
||||
\item Réfléchir avant d'écrire, prendre du recul
|
||||
\pause
|
||||
\item Tester son code après chaque modification
|
||||
\pause
|
||||
\item Utiliser un analyseur syntaxique
|
||||
\pause
|
||||
\item Tester automatiquement et régulièrement le code
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\section{Correction des erreurs}
|
||||
|
||||
\subsection{Analyseur syntaxique}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Rôle d'un analyseur syntaxique}
|
||||
\begin{itemize}
|
||||
\item Lit le code sans l'exécuter ni le compiler
|
||||
\item Vérifie que la syntaxe est cohérente
|
||||
\item Repère les erreurs les plus courantes et suggère des modifications
|
||||
\item S'assure de l'uniformité du code et du respect de certaines
|
||||
conventions
|
||||
\item Existe dans la quasi-totalité des langages
|
||||
\item En Python : Flake8 et Pylint
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Installation des analyseurs}
|
||||
\begin{lstlisting}[language=bash]
|
||||
(sudo apt install python3-virtualenv / sudo pacman -S python-virtualenv)
|
||||
(python3 -m venv venv)
|
||||
(source venv/bin/activate)
|
||||
pip3 install pylint flake8
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
|
||||
\begin{exampleblock}{Analyse du code}
|
||||
En lancant \texttt{pylint main.py} :
|
||||
\begin{lstlisting}[language=Python]
|
||||
************* Module main
|
||||
main.py:7:11: E0001: Missing parentheses in call to 'print'.
|
||||
Did you mean print("Bienvenue dans le chatbot du séminaire de l'intégration continue !")?
|
||||
(<unknown>, line 7) (syntax-error)
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
|
||||
$\implies$ On n'a pas eu besoin d'exécuter le code
|
||||
\end{frame}
|
||||
|
||||
\subsection{Intégration continue}
|
||||
|
||||
\begin{frame}
|
||||
\begin{itemize}
|
||||
\item Appeler ce script à la main $\to$ laborieux
|
||||
\item Solution : utiliser \sout{un esclave} de l'intégration continue
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Qu'est-ce qu'une intégration continue ?}
|
||||
\begin{itemize}
|
||||
\item Script appelé après commit envoyé au serveur distant
|
||||
\item Utile pour analyser et tester le code
|
||||
\item Mais également pour déployer le projet automatiquement
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Analyse syntaxique automatique}
|
||||
\texttt{git checkout v2}
|
||||
|
||||
Dans le fichier \texttt{.gitlab-ci.yml} :
|
||||
\begin{lstlisting}[]
|
||||
flake8:
|
||||
image: python:3-alpine
|
||||
before_script:
|
||||
- pip install flake8 --no-cache-dir
|
||||
script: flake8 main.py
|
||||
|
||||
pylint:
|
||||
image: python:3-alpine
|
||||
before_script:
|
||||
- pip install pylint --no-cache-dir
|
||||
script: pylint main.py
|
||||
\end{lstlisting}
|
||||
|
||||
Résultat : \url{https://gitlab.crans.org/ynerant/seminaire-ci/-/pipelines/5595}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Nettoyer son code}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Utilité d'un analyseur syntaxique}
|
||||
\begin{itemize}
|
||||
\item \texttt{git checkout v2.5}
|
||||
\item Le script peut être lancé, mais le code n'est pas propre
|
||||
\item Un code bien écrit est un code qui peut être lu et maintenu
|
||||
\item Quelques règles standards :
|
||||
\begin{itemize}
|
||||
\item Lignes ne faisant pas plus de 79 caractères
|
||||
\item Code bien indenté
|
||||
\item Pas de variable inutile
|
||||
\item Code bien aéré (mais pas trop)
|
||||
\item Code documenté et commenté
|
||||
\item Imports triés
|
||||
\item Beaucoup d'options ...
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Analyse du code}
|
||||
En lancant \texttt{pylint main.py} sur le tag \texttt{v2.5} :
|
||||
\begin{lstlisting}[language=Python]
|
||||
************* Module main
|
||||
main.py:23:0: C0301: Line too long (104/100) (line-too-long)
|
||||
main.py:32:0: C0301: Line too long (469/100) (line-too-long)
|
||||
main.py:39:0: C0301: Line too long (192/100) (line-too-long)
|
||||
main.py:16:0: W0622: Redefining built-in 'help' (redefined-builtin)
|
||||
main.py:91:0: W0622: Redefining built-in 'quit' (redefined-builtin)
|
||||
main.py:1:0: C0114: Missing module docstring (missing-module-docstring)
|
||||
main.py:6:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||||
main.py:42:0: C0103: Argument name "a" does not conform to snake_case naming style
|
||||
(invalid-name)
|
||||
main.py:42:0: C0103: Argument name "b" does not conform to snake_case naming style
|
||||
(invalid-name)
|
||||
main.py:42:0: C0103: Argument name "c" does not conform to snake_case naming style
|
||||
(invalid-name)
|
||||
main.py:42:0: R0912: Too many branches (17/12) (too-many-branches)
|
||||
main.py:96:4: R1722: Consider using sys.exit() (consider-using-sys-exit)
|
||||
|
||||
------------------------------------------------------------------
|
||||
Your code has been rated at 8.03/10 (previous run: 8.03/10, +0.00)
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Faire marcher son code}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Un code bien écrit n'est pas un code qui marche}
|
||||
\texttt{git checkout v5}
|
||||
|
||||
En lançant \texttt{python3 main.py} :
|
||||
\begin{lstlisting}
|
||||
Bienvenue dans le chatbot du séminaire de l'intégration continue !
|
||||
Veuillez taper une commande. Tapez "aide" pour afficher l'aide.
|
||||
> calcul 1 2 3
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/seminaire-ci/main.py", line 110, in <module>
|
||||
main()
|
||||
File "/tmp/seminaire-ci/main.py", line 27, in main
|
||||
print(globals()[args[0]](*args[1:]))
|
||||
File "/tmp/seminaire-ci/main.py", line 90, in calcul
|
||||
result = left % right
|
||||
TypeError: not all arguments converted during string formatting
|
||||
\end{lstlisting}
|
||||
|
||||
$\implies$ le code est bien écrit, mais ne fait pas ce qu'on veut
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Utilisation de l'intégration continue}
|
||||
\texttt{git checkout v6}
|
||||
|
||||
On ajoute au fichier \texttt{.gitlab-ci.yml} :
|
||||
\begin{lstlisting}
|
||||
test:
|
||||
stage: test
|
||||
image: python:3-alpine
|
||||
script: python main.py
|
||||
\end{lstlisting}
|
||||
|
||||
Le code est bien exécuté :
|
||||
\url{https://gitlab.crans.org/ynerant/seminaire-ci/-/jobs/13865}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Environnement de tests}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Utiliser un environnement de test}
|
||||
\begin{itemize}
|
||||
\item Impossible d'exécuter le script pas d'entrée à fournir
|
||||
\item Impossible de choisir quoi tester et de vérifier la bonne
|
||||
exécution du programme
|
||||
\pause
|
||||
\item Solution : utiliser un environnement adapté et des tests unitaires
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Avec Pytest}
|
||||
\texttt{git checkout v8} \\
|
||||
\texttt{pip3 install pytest} \\
|
||||
Dans le fichier \texttt{main\_test.py} :
|
||||
\begin{lstlisting}[language=Python]
|
||||
import unittest
|
||||
|
||||
import main
|
||||
|
||||
|
||||
def test_aide():
|
||||
res = main.commande("aide")
|
||||
lines = res.split("\n")
|
||||
assert len(lines) == 6
|
||||
assert lines[0].startswith("aide")
|
||||
assert lines[1].startswith("seminaire")
|
||||
assert lines[2].startswith("blague")
|
||||
assert lines[3].startswith("calcul")
|
||||
assert lines[4].startswith("tri")
|
||||
assert lines[5].startswith("stop")
|
||||
\end{lstlisting}
|
||||
On a ajouté une fonction \texttt{commande}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Avec Pytest}
|
||||
\texttt{pytest .}
|
||||
\begin{lstlisting}[language=Python]
|
||||
============================= test session starts =============================
|
||||
platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
|
||||
rootdir: /tmp/seminaire-ci
|
||||
collected 1 item
|
||||
main_test.py F [100%]
|
||||
================================== FAILURES ===================================
|
||||
__________________________________ test_aide __________________________________
|
||||
|
||||
def test_aide():
|
||||
res = main.commande("aide")
|
||||
> lines = res.split("\n")
|
||||
E AttributeError: 'NoneType' object has no attribute 'split'
|
||||
|
||||
main_test.py:17: AttributeError
|
||||
---------------------------- Captured stdout call -----------------------------
|
||||
aide Affiche l aide
|
||||
[...]
|
||||
=========================== short test summary info ===========================
|
||||
FAILED main_test.py::test_aide - AttributeError: 'NoneType' object has no at...
|
||||
============================== 1 failed in 0.03s ==============================
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Avec Pytest, après réparation de l'erreur}
|
||||
\texttt{git checkout v9} \\
|
||||
\texttt{pytest .}
|
||||
\begin{lstlisting}[language=Python]
|
||||
============================= test session starts =============================
|
||||
platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
|
||||
rootdir: /tmp/seminaire-ci
|
||||
collected 1 item
|
||||
|
||||
main_test.py . [100%]
|
||||
|
||||
============================== 1 passed in 0.01s ==============================
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Un meilleur environnement de tests}
|
||||
\texttt{git checkout v10} \\
|
||||
On utilise une classe pour mieux construire son environnement
|
||||
\begin{lstlisting}[language=Python]
|
||||
import unittest
|
||||
|
||||
import main
|
||||
|
||||
|
||||
class TestMain(unittest.TestCase):
|
||||
def test_aide(self):
|
||||
res = main.commande("aide")
|
||||
lines = res.split("\n")
|
||||
self.assertEqual(len(lines), 6)
|
||||
self.assertTrue(lines[0].startswith("aide"))
|
||||
self.assertTrue(lines[1].startswith("seminaire"))
|
||||
self.assertTrue(lines[2].startswith("blague"))
|
||||
self.assertTrue(lines[3].startswith("calcul"))
|
||||
self.assertTrue(lines[4].startswith("tri"))
|
||||
self.assertTrue(lines[5].startswith("stop"))
|
||||
\end{lstlisting}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Qu'est-ce qu'un bon test unitaire ?}
|
||||
\begin{itemize}
|
||||
\item \og{} Unitaire \fg{} : on ne teste qu'une seule fonctionnalité à la
|
||||
fois
|
||||
\item Clair
|
||||
\item Pertinent
|
||||
\item Non-trivial
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Couverture}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Couverture}
|
||||
\begin{itemize}
|
||||
\item Les tests unitaires exécutent une partie du code
|
||||
\item Il est possible de mesurer quelle partie du code est exécutée et
|
||||
testée
|
||||
\item On parle de \textit{couverture} du code (\textit{coverage} en anglais)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\begin{exampleblock}{Calcul de la couverture du code}
|
||||
\begin{itemize}
|
||||
\item \texttt{git checkout v11}
|
||||
\item \texttt{pip install pytest-cov}
|
||||
\item \texttt{pytest --showlocals --cov=main --cov=main\_test --cov-report=term-missing .}
|
||||
\end{itemize}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{exampleblock}{Calcul de couverture}
|
||||
\texttt{pytest --showlocals --cov=main --cov=main\_test --cov-report=term-missing .}
|
||||
\begin{lstlisting}
|
||||
============================= test session starts =============================
|
||||
platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
|
||||
rootdir: /tmp/seminaire-ci
|
||||
plugins: cov-2.11.1
|
||||
collected 1 item
|
||||
main_test.py . [100%]
|
||||
----------- coverage: platform linux, python 3.9.1-final-0 -----------
|
||||
Name Stmts Miss Cover Missing
|
||||
--------------------------------------------
|
||||
main.py 41 27 34% 21-27, 54, 70, 82-98, 105, 112-113, 117
|
||||
main_test.py 14 0 100%
|
||||
--------------------------------------------
|
||||
TOTAL 55 27 51%
|
||||
|
||||
============================== 1 passed in 0.05s ==============================
|
||||
\end{lstlisting}
|
||||
Exercice pour le plaisir : couvrir et tester correctement la totalité du code
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Lutter contre le nazisme}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Configuration de l'analyse et des tests}
|
||||
\begin{itemize}
|
||||
\item \og{} Ohh vraiment je dois faire ça \fg{}, \og{} 79 caractères c'est
|
||||
pas assez \fg{}, \og{} Je ne veux/peux pas couvrir cette ligne \fg{}
|
||||
\item Couvrir et tester sont des très bonnes pratiques en règle générale
|
||||
\item Vous pouvez adapter la configuration selon vos propres critères,
|
||||
plus ou moins souples
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\section{Déploiement}
|
||||
|
||||
\subsection{Artefacts}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Rôle d'une intégration continue}
|
||||
Une intégration continue permet :
|
||||
\begin{itemize}
|
||||
\item d'analyser automatiquement la structure du code
|
||||
\item de tester automatiquement le projet
|
||||
\item \textit{a fortiori} de donner un indicateur aux autres personnes de
|
||||
la qualité du projet via des badges
|
||||
\pause
|
||||
\item De construire et de déployer automatiquement le projet
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Construction du projet}
|
||||
\texttt{git checkout v16} \\
|
||||
Un script d'intégration ne se limite pas aux tests : il peut compiler des
|
||||
projets
|
||||
|
||||
Exemple avec la compilation automatique \LaTeX{} de ces slides :
|
||||
\begin{lstlisting}
|
||||
slides:
|
||||
image: aergus/latex
|
||||
script:
|
||||
- latexmk -cd -pdf slides/seminaire-ci.tex
|
||||
\end{lstlisting}
|
||||
L'intégration continue génère un fichier \texttt{seminaire-ci.pdf} dans son
|
||||
script
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Construction du projet}
|
||||
\texttt{git checkout v16} \\
|
||||
Il est possible d'exporter certains fichiers compilés :
|
||||
\begin{lstlisting}
|
||||
slides:
|
||||
image: aergus/latex
|
||||
script:
|
||||
- latexmk -cd -pdf slides/seminaire-ci.tex
|
||||
artifacts:
|
||||
paths:
|
||||
- slides/seminaire-ci.pdf
|
||||
expire_in: 1 mo
|
||||
\end{lstlisting}
|
||||
Ici, les slides sont compilées et le fichier PDF généré est exporté et
|
||||
stocké pendant 1 mois :
|
||||
\url{https://gitlab.crans.org/ynerant/seminaire-ci/-/jobs/13826/artifacts/browse}
|
||||
(à des fins d'exemple, la limitation d'un mois à été retirée)
|
||||
|
||||
Particulièrement utile pour générer des binaires à partir du code source
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Déploiement automatisé}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Déployer son projet}
|
||||
\begin{itemize}
|
||||
\item Une fois le projet dans une version stable et bien testé, le projet
|
||||
peut être installé
|
||||
\item Si on a suffisamment confiance en ses tests, possibilité de le faire
|
||||
automatiquement
|
||||
\item Deux solutions principales :
|
||||
\begin{itemize}
|
||||
\item Envoyer un signal (\textit{webhook}) aux services concernés
|
||||
\item Directement déployer le serveur ce qu'il faut $\to$ nécessite
|
||||
d'être simple à déployer et d'être peu distribué (projet développé
|
||||
dans un but interne : site, latex, ...)
|
||||
\end{itemize}
|
||||
\item Pour des gros projets, on privilégie la première option
|
||||
\item Attention aux droits donnés à Gitlab
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[containsverbatim]
|
||||
\begin{block}{Déployer son projet}
|
||||
Les slides de ce séminaire sont automatiquement déployées sur
|
||||
\url{https://ynerant.fr/gitlab/seminaire-ci.pdf} :
|
||||
\begin{lstlisting}
|
||||
slides:
|
||||
stage: compile_slides
|
||||
image: aergus/latex
|
||||
before_script:
|
||||
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
- eval $$(ssh-agent -s)
|
||||
- echo "$$SSH_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||
- mkdir -p ~/.ssh
|
||||
- chmod 0700 ~/.ssh
|
||||
- ssh-keyscan ynerant.fr >> ~/.ssh/known_hosts
|
||||
- chmod 0644 ~/.ssh/known_hosts
|
||||
script:
|
||||
- latexmk -cd -pdf slides/seminaire-ci.tex
|
||||
- scp slides/seminaire-ci.pdf gitlab-ci@ynerant.fr:gitlab-ftp/seminaire-ci.pdf
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
Autre exemple : \url{https://gitlab.crans.org/nounous/homepage} déploie
|
||||
automatiquement \url{https://crans.org/}
|
||||
\end{frame}
|
||||
|
||||
\subsection{Webhooks}
|
||||
|
||||
\begin{frame}
|
||||
\begin{block}{Webhooks}
|
||||
Un \textit{webhook} est un événement envoyé à un service externe pour
|
||||
signaler un changement d'état.
|
||||
|
||||
Exemples de webhooks :
|
||||
|
||||
\begin{itemize}
|
||||
\item Message sur une plateforme de communication (IRC, Matrix, Discord,
|
||||
\ldots)
|
||||
\item Compilation automatique de documentation
|
||||
\item Traduction
|
||||
\item Compilation et déploiement du projet sur un service externe (Docker,
|
||||
PyPI ou autre gestionnaire de paquets, \ldots)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\section{Conclusion}
|
||||
|
||||
\begin{frame}
|
||||
\begin{exampleblock}{En résumé}
|
||||
\begin{itemize}
|
||||
\item Analyser statiquement son code permet d'assurer qu'il est
|
||||
syntaxiquement correct et qu'il est surtout lisible
|
||||
\item Tester efficacement son projet assure son bon fonctionnement et
|
||||
évite des erreurs imprévues
|
||||
\item Ces tâches peuvent se faire automatiquement après chaque commit par
|
||||
le biais d'une intégration continue
|
||||
\item Il est enfin possible de construire et déployer son projet
|
||||
automatiquement
|
||||
\item Une intégration continue efficace est souvent signe d'un projet bien
|
||||
géré
|
||||
\end{itemize}
|
||||
\end{exampleblock}
|
||||
|
||||
\pause
|
||||
|
||||
\begin{block}{}
|
||||
\textit{Gérer} un projet n'est pas uniquement \textit{coder} un projet.
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\end{document}
|
Loading…
x
Reference in New Issue
Block a user