%\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 !")? (, 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}