diff --git a/.gitignore b/.gitignore index 8906b1a..ecc5e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ venv *.nav *.out *.pdf +*.pyg *.snm *.synctex.gz *.toc diff --git a/slides/img/note-500.png b/slides/img/note-500.png new file mode 100644 index 0000000..4fa3a68 Binary files /dev/null and b/slides/img/note-500.png differ diff --git a/slides/img/re2o-500.png b/slides/img/re2o-500.png new file mode 100644 index 0000000..3a83b26 Binary files /dev/null and b/slides/img/re2o-500.png differ diff --git a/slides/img/youtube-500.jpg b/slides/img/youtube-500.jpg new file mode 100644 index 0000000..4a7d658 Binary files /dev/null and b/slides/img/youtube-500.jpg differ diff --git a/slides/seminaire-ci.tex b/slides/seminaire-ci.tex index ec03f8e..b2b34f9 100644 --- a/slides/seminaire-ci.tex +++ b/slides/seminaire-ci.tex @@ -1,10 +1,58 @@ -\documentclass[handout,aspectratio=169]{beamer} +%\documentclass[handout,aspectratio=169]{beamer} +\documentclass[aspectratio=169]{beamer} \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage{lmodern} -\usepackage[francais]{babel} +\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} @@ -19,50 +67,581 @@ \maketitle \end{frame} -\begin{frame} - \tableofcontents[subsubsectionstyle=hide] -\end{frame} +\section{Introduction} -\section{Example} +\subsection{Discussion avec un chatbot} -\subsection{Block} - -\begin{frame} - \begin{block}{Title block} - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - \end{block} - \begin{block}{} - No title Block \\ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - \end{block} - \begin{exampleblock}{Example Block} - Example text (ugly) +\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} - \begin{alertblock}{Alert Block} - Alert Text - \end{alertblock} \end{frame} -\subsection{Lists} +\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 Item 1 - \item Item 2 - \begin{itemize} - \item Subitem 1 - \item Subitem 2 - \end{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} - \begin{enumerate} - \item item 1 - \begin{enumerate} - \item item 1.1 - \end{enumerate} - \item - \item - \item - \end{enumerate} - \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 !")? +(, 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 commit \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 + 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 Tout est configurable, y compris pour ajouter des exceptions + \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}