{"cells": [{"cell_type": "markdown", "id": "0bf6d757", "metadata": {}, "source": ["# Test unitaires\n", "\n", "Les tests unitaires sont l'\u00e9l\u00e9ment cl\u00e9 pour cr\u00e9er un programme fiable. Il est impensable de s'en passer. Un test unitaire est une fonction qui s'assure qu'une autre fonction retourne le r\u00e9sultat souhait\u00e9 pour les m\u00eames entr\u00e9es. Ils sont pr\u00e9sents dans tous les langages.\n", "\n", "Les modules python les plus utilis\u00e9s sont aussi les plus test\u00e9s, ils sont valid\u00e9s par des milliers de tests unitaires."]}, {"cell_type": "code", "execution_count": 1, "id": "31adf536", "metadata": {}, "outputs": [], "source": ["def unit_test():\n", " y = f(x)\n", " if y != valeur_attendue:\n", " raise AssertionError(f\"{y} != {valeur_attendue}\")"]}, {"cell_type": "code", "execution_count": 2, "id": "20bda5df", "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 3, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "id": "570adf0b", "metadata": {}, "source": ["## Un petit jeu\n", "\n", "On suppose qu'une tour est plac\u00e9e sur un \u00e9chiquier, on veut savoir combien de coups il faut pour atteindre une autre case."]}, {"cell_type": "code", "execution_count": 3, "id": "b3056ab6", "metadata": {}, "outputs": [], "source": ["def tour_prend_piece(x1, y1, x2, y2):\n", " # ...\n", " return 1 or 2"]}, {"cell_type": "markdown", "id": "8ba48742", "metadata": {}, "source": ["Si votre fonction est bien correct, la fonction suivante doit s'ex\u00e9cuter sans erreur."]}, {"cell_type": "code", "execution_count": 4, "id": "416b49bf", "metadata": {}, "outputs": [], "source": ["def test_tour_prend_piece():\n", " assert tour_prend_piece(0, 0, 0, 1) == 1\n", " assert tour_prend_piece(0, 0, 1, 0) == 1\n", " assert tour_prend_piece(1, 0, 0, 0) == 1\n", " assert tour_prend_piece(0, 1, 0, 0) == 1\n", " assert tour_prend_piece(0, 0, 1, 1) == 2\n", " assert tour_prend_piece(0, 2, 1, 1) == 2"]}, {"cell_type": "markdown", "id": "f9d20ecb", "metadata": {}, "source": ["## Une autre \u00e9criture\n", "\n", "La fonction pr\u00e9c\u00e9dente a quatre arguments. On souhaite les remplacer par deux tuple."]}, {"cell_type": "code", "execution_count": 5, "id": "47c8f27f", "metadata": {}, "outputs": [], "source": ["def tour_prend_piece_tuple(t1, t2):\n", " # ...\n", " return True or False\n", "\n", "def test_tour_prend_piece_tuple():\n", " def _tour_prend_piece(x1, y1, x2, y2):\n", " return tour_prend_piece_tuple((x1, y1), (x2, y2))\n", " assert _tour_prend_piece(0, 0, 0, 1) == 1\n", " assert _tour_prend_piece(0, 0, 1, 0) == 1\n", " assert _tour_prend_piece(1, 0, 0, 0) == 1\n", " assert _tour_prend_piece(0, 1, 0, 0) == 1\n", " assert _tour_prend_piece(0, 0, 1, 1) == 2\n", " assert _tour_prend_piece(0, 2, 1, 1) == 2"]}, {"cell_type": "markdown", "id": "dd9a5adf", "metadata": {}, "source": ["## Des obstacles...\n", "\n", "Ecrire une fonction qui prend en compte les obstacles : la tour ne peut pas traverser une case si une pi\u00e8ce est pr\u00e9sente. On pourra s'inspirer d'un algorithme de coloriage. Qu'en est-il des tests unitaires pr\u00e9c\u00e9dents ?\n", "\n", "L'id\u00e9e est colorier l'\u00e9chiquier avec le nombre de coups qu'il faut pour atteindre une case."]}, {"cell_type": "code", "execution_count": 6, "id": "78847424", "metadata": {"scrolled": false}, "outputs": [{"data": {"text/plain": ["4.0"]}, "execution_count": 7, "metadata": {}, "output_type": "execute_result"}], "source": ["import numpy\n", "\n", "def find_neighbour(echiquier, p):\n", " x, y = p\n", " if x > 0 and echiquier[x-1, y] == -1:\n", " return (x-1, y), (-1, 0)\n", " if x < echiquier.shape[0] - 1 and echiquier[x+1, y] == -1:\n", " return (x+1, y), (1, 0)\n", " if y > 0 and echiquier[x, y-1] == -1:\n", " return (x, y-1), (0, -1)\n", " if y < echiquier.shape[1] - 1 and echiquier[x, y+1] == -1:\n", " return (x, y+1), (0, 1)\n", " return None, None\n", "\n", "def coloring(t1, t2, obstacles):\n", " obstacles = set(obstacles) # pour aller plus vite\n", " echiquier = numpy.zeros((8, 8)) -1\n", " for obs in obstacles:\n", " echiquier[obs] = -2\n", " echiquier[t1] = 0\n", " cases = [t1]\n", " while len(cases) > 0:\n", " case = cases[0]\n", " next_case, direction = find_neighbour(echiquier, case)\n", " if next_case is None:\n", " del cases[0]\n", " continue\n", " x, y = next_case\n", " value = echiquier[case] + 1\n", " while x >= 0 and y >= 0 and x < echiquier.shape[0] and y < echiquier.shape[1]:\n", " if echiquier[x, y] == -2:\n", " break\n", " if echiquier[x, y] == -1:\n", " echiquier[x, y] = value\n", " else:\n", " echiquier[x, y] = min(value, echiquier[x, y])\n", " cases.append((x, y))\n", " x += direction[0]\n", " y += direction[1]\n", " return echiquier[t2]\n", "\n", "coloring((0, 0), (6, 6), [(0, 6), (6, 0), (1, 5), (5, 1), (5, 6), (5, 5), (6, 5)])"]}, {"cell_type": "code", "execution_count": 7, "id": "3f5813b2", "metadata": {}, "outputs": [], "source": []}, {"cell_type": "code", "execution_count": 8, "id": "c15879c6", "metadata": {}, "outputs": [], "source": []}, {"cell_type": "code", "execution_count": 9, "id": "5a87b8b1", "metadata": {}, "outputs": [], "source": ["def tour_prend_piece_obstacle(t1, t2, obstacles):\n", " # ...\n", " return # ..."]}, {"cell_type": "markdown", "id": "671e08ea", "metadata": {}, "source": ["## Ajouter d'autres tests unitaires pour cette seconde version"]}, {"cell_type": "code", "execution_count": 10, "id": "1947817c", "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "id": "e7b4d454", "metadata": {}, "source": ["## Changer la taille de l'\u00e9chiquier\n", "\n", "On consid\u00e8re que l'\u00e9chiquier est de taille connue mais plus n\u00e9cessairement 8x8. Modifier la fonction pour prendre en compte ce changement. Qu'en est-il des tests unitaires."]}, {"cell_type": "code", "execution_count": 11, "id": "af29f0eb", "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "id": "dbb8e625", "metadata": {}, "source": ["## Pour aller plus loin\n", "\n", "Les tests unitaires :\n", "\n", "* Ils sont des fonctions sans arguments dont le nom commencent par `test_`.\n", "* Ils sont indispensables quand on travaille \u00e0 plusieurs : ils assurent que quelqu'un ne *casse* pas votre fonction.\n", "* Ils s'\u00e9crivent rarement dans un notebook. On les \u00e9crit dans un fichier \u00e0 part, et ils testent des fonctions \u00e9crites dans d'autres fichiers mais pas dans des notebooks.\n", "* Les tests unitaires doivent \u00eatre rapides : ils sont ex\u00e9cut\u00e9s tr\u00e8s souvent, ils doivent \u00eatre courts et rapides.\n", "* On teste des r\u00e9sultats num\u00e9riques mais aussi qu'une fonction cr\u00e9e une exception, un warning...\n", "\n", "Des milliers de tests unitaires :\n", "\n", "* [unittest](https://docs.python.org/3/library/unittest.html) : module python d\u00e9di\u00e9s aux tests unitaires\n", "* [pytest](https://docs.pytest.org/en/7.1.x/) : c'est une librairie tr\u00e8s utilis\u00e9es. La commande `pytest ` cherche toutes les fonctions commencen\u00e7ant par `test_` et les ex\u00e9cute.\n", "\n", "Int\u00e9gration continue :\n", "\n", "Exemple avec [scikit-learn](https://github.com/scikit-learn/scikit-learn), r\u00e9sultats des tests [scikit-learn/build](https://dev.azure.com/scikit-learn/scikit-learn/_build)"]}, {"cell_type": "markdown", "id": "fdf036be", "metadata": {}, "source": ["## Tester une exception\n"]}, {"cell_type": "code", "execution_count": 12, "id": "dda739a4", "metadata": {}, "outputs": [], "source": ["def tour_prend_piece_obstacle(t1, t2, obstacles):\n", " if min(t1) < 0 or min(t2) < 0:\n", " raise ValueError(f\"Une pi\u00e8ce est en dehors de l'\u00e9chiquier, pi\u00e8ces : {t1} ou {t2}.\")\n", " return # ..."]}, {"cell_type": "code", "execution_count": 13, "id": "90950e76", "metadata": {}, "outputs": [], "source": ["def test_tour_prend_piece_obstacle_exception():\n", " # ...\n", " pass"]}, {"cell_type": "code", "execution_count": 14, "id": "b76dd5ed", "metadata": {}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.5"}}, "nbformat": 4, "nbformat_minor": 5}