diff --git a/software_engineering/1_Architecture/index.rst b/software_engineering/1_Architecture/index.rst index 4334fd6..a3267c7 100644 --- a/software_engineering/1_Architecture/index.rst +++ b/software_engineering/1_Architecture/index.rst @@ -11,12 +11,13 @@ ========================= #. Have a clean structure +#. Get ready for the future #. A word about licenses #. Coding convention #. PEP8 #. PEP20 - +#. Tools #. Structure of a minimalistic project ---- @@ -28,12 +29,13 @@ Have a clean structure * libraries are reusable -- Separate GUI from calculation: +- Separate user interface from calculation: * Otherwise maintenance becomes a nightmare * allows to change the front-end * allows to access to core function without the GUI * simplify unit tests + * ease collaboration - Separate I/O from calculation @@ -49,10 +51,31 @@ Will also help you to : ---- -example -------- +Python 2 end of life +-------------------- + +By 2020, the `support of Python2 will end `_. +All your projects must be python3 based, supporting Python2 has become optional. + + +Look at the `developer statistics `_ + +---- + +Nice features of Python 3 +------------------------- + +Python3 prevents you from mixing tab and space indentation in your code. + +Python3 enforces you to decode early your data to avoid mixing *bytes* and *unicode* + +You can use the `six library `_ to provide code that +runs both under Python2 and Python3. + +Presenter Notes +............... -TODO +python-future is a higher-level compatibility layer than six that includes more backported functionality from Python 3, more forward-ported functionality from Python 2 ---- @@ -109,27 +132,24 @@ Why MIT instead of Apache 2.0 ? ---- -Coding convention -================= - ----- - -Python 2 end of support ------------------------ - -By 2020, the `support of Python2 will end `_. -All your projects must be python3 based, supporting Python2 becomes optional. - +Coding convention: +================== -Look at the `developer statistics `_ - -You can use the `six library `_ to provide code that -runs both under Python2 and Python3. +Set of guidelines for a specific programming language that recommend: programming style, practices, and methods for each aspect of a program written in that language. It contains: -Presenter Notes -............... +* File organization, +* indentation, +* comments, +* declarations, +* statements, +* white space, +* naming conventions, +* programming practices, +* programming principles, +* programming rules of thumb, +* architectural best practices, -python-future is a higher-level compatibility layer than six that includes more backported functionality from Python 3, more forward-ported functionality from Python 2 +Why ? reduce cost of software maintenance. ---- @@ -197,16 +217,18 @@ Zen of Python: `PEP20 `_ Tools ----- -* `flake8 `_ -* `pylint `_ -* `modernize `_ -* `autopep8 `_ -* `landscape.io `_: `Example `_ -* IDE +* Use an Integrated Development Environments (IDE) like: + + - `pyCharm `_ Probably the best IDE for Python + - `pyDev `_ Eclipse plugin - - `pyDev (eclipse) `_ - - `pyCharm `_ +* Other tools to improve your code: + - `pylint `_: Validation of Python code, syntax, variable names + - `flake8 `_: Validation of code style (PEP8) + - `modernize `_: Helps you upgrade to Python3 + - `autopep8 `_: rewrites your code in PEP8 ! + ---- Scafold of a minimalistic Python project @@ -217,7 +239,9 @@ Scafold of a minimalistic Python project pet_project/ pet_project/ __init__.py + LICENSE.txt README.txt + [requirements.txt] setup.py diff --git a/software_engineering/2_Git/images/branches_real.png b/software_engineering/2_Git/images/branches_real.png new file mode 100644 index 0000000..9b62d1a Binary files /dev/null and b/software_engineering/2_Git/images/branches_real.png differ diff --git a/software_engineering/2_Git/images/branches_real.svg b/software_engineering/2_Git/images/branches_real.svg new file mode 100644 index 0000000..7774eed --- /dev/null +++ b/software_engineering/2_Git/images/branches_real.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/software_engineering/2_Git/images/git_branch.png b/software_engineering/2_Git/images/git_branch.png index d6311c4..6120e9c 100644 Binary files a/software_engineering/2_Git/images/git_branch.png and b/software_engineering/2_Git/images/git_branch.png differ diff --git a/software_engineering/2_Git/index.rst b/software_engineering/2_Git/index.rst index 29772d2..0f8b534 100644 --- a/software_engineering/2_Git/index.rst +++ b/software_engineering/2_Git/index.rst @@ -8,6 +8,9 @@ } +.. include:: + + GIT === @@ -74,11 +77,12 @@ encourage collaboration using forks of projects. The main advantages are: * simplify contribution. -* many tutorials for `gitHub `_ and `gitlab `_. +* many tutorials for `github `_ and `gitlab `_. * web page hosting for projects. * over the years a cluster of services have pop up to help developers like `Travis `_ and `AppVeyor `_). * `offer a fixed pipeline based on *Pull request* `_. -* lead to some 'normalization' of projects. +* lead to some 'standardization' of projects. + ---- @@ -86,7 +90,7 @@ github vs gitlab ```````````````` * github should bring to your project an `Higher visibility compared to other hosting (in 2017) `_. -* github is usually one step ahead of gitlab regarding features and usability. +* github is usually one step ahead compare to gitlab regarding features and usability. * activities on github are monitored by head-hunters and can be useful for professional placement. * gitlab allows you to select a privacy level for your projects. Public projects can be seen from outside: https://gitlab.esrf.fr/public not private projects. * github is free for open-source project. @@ -98,8 +102,10 @@ github vs gitlab Presenter Notes ............... -The amount of the acquirement by Microsoft show the importance of github on the today life of the developer. -As a consequence thousands of project has move from github to gitlab. (50000 after a week) +* github intend to open itself to services, gitlab intend to embed them. Usage is the same but philosophy is different. +* The amount of the acquirement by Microsoft show the importance of github on the today life of the developer. + As a consequence thousands of project has move from github to gitlab. (50000 after a week) +* gitlab is free and open source. But has big compamies behind (nas, bayer, siemens...) ---- @@ -117,9 +123,9 @@ git permits several workflows: Presenter Notes ............... -- centralized : a single point of entry 'central repository'. Let each users to deal with synchronization -- feature branch workflow: each new feature should take place in a dedicated branch -- gitflow : strict management of branches designed for releases. One branch per: +* centralized : a single point of entry 'central repository'. Let each users to deal with synchronization +* feature branch workflow: each new feature should take place in a dedicated branch +* gitflow : strict management of branches designed for releases. One branch per: - releases - each feature - fix @@ -139,13 +145,14 @@ Presenter Notes ............... Why the 'forking flow' ? ==> commonly used. The one of silx for example. +This schema is very important ant we will follow it during all along training The idea is that each developer can interact with other from his own fork. Each developer can request to merge some modifications (feature, bug fix...) with others: this is a pull request -- simplify branch forking -- Always keep upstream branch ready for deployment with features and fixes -- Each new branch starts from the master (up to date) -- Use merge request for each new feature +* simplify branch forking +* Always keep upstream branch ready for deployment with features and fixes +* Each new branch starts from the master (up to date) +* Use merge request for each new feature ---- @@ -197,7 +204,7 @@ Example: create a new git project (2) .. code-block:: bash - git remote add origin git@gitlab.esrf.fr:silx/silx-trainings/pypolynom.git + git remote add origin git@gitlab.esrf.fr:silx/pypolynom.git 7. add files to a commit (detail later) @@ -234,11 +241,10 @@ Hands on: fork an existing project 1.1 login to github / gitlab - 1.2 look for the 'https://gitlab.esrf.fr/silx/silx-trainings/pypolynom_completed' project + 1.2 look for the 'https://gitlab.esrf.fr/silx/pypolynom' project 1.3 fork the project and go to the homepage of the fork you just created -.. include:: |rarr| This will provide you an url to your personal repository. @@ -257,7 +263,7 @@ Hands on: fork an existing project .. code-block:: bash - git clone git@gitlab.esrf.fr:[my_id]/pypolynom_completed.git + git clone git@gitlab.esrf.fr:[my_id]/pypolynom.git |rarr| You are now ready for making some modifications and share them with others. @@ -274,15 +280,24 @@ branches |rarr| Two branches can be merge together. +The case we will consider: + .. image:: images/git_branch.png :align: center + :width: 50% +A more realistic case: + +.. image:: images/branches_real.png + :align: center + :width: 28% Presenter Notes ``````````````` Eah commit has an 'ID': SHA-1 checksum from modifications + commit message + author + date +To simplify thinks, we will consider the master branch to be the develop branch. ---- @@ -323,6 +338,11 @@ commits with authors, data time, file changed, and the chain of commits called * .. note:: *graphic tools as* `git-gui `_ *and* `gitk `_ *might help you for commits and to have a graphic representation of the tree view.* +Presenter Notes +``````````````` + +git add / git commit can be repeated + ---- Some useful git commands @@ -333,20 +353,21 @@ Some useful git commands * *git diff* : show changes between commits * *git tag* : add a tag at a specific point of the history ----- - Presenter Notes ............... git tag can help you to retrieve some milestone git reflog +---- + Hands on: propose modifications ------------------------------- For this exercise you can use a `git Cheat sheet `_ if you like. -Now we want to make some modifications on the source code (for a bug fix for example). +The goal is to make some modifications on the source code (for a bug fix for example). + We need to: * create a new branch relative to the bug fix @@ -477,7 +498,7 @@ the one from the other developers. .. code-block:: bash - git remote add upstream git@gitlab.esrf.fr:silx/silx-trainings/pypolynom.git + git remote add upstream git@gitlab.esrf.fr:silx/pypolynom.git # git remote add co-worker git@gitlab.esrf.fr:/pypolynom.git 2. merge those modification on your master branch @@ -519,3 +540,9 @@ or you might want to contribute to other projects. How to contribute to an Open Source project is presented in `this document `_ for scikit-image. + + +Presenter Notes +............... + +I hope you where happy to meet your new best friend diff --git a/software_engineering/5_Test/img/test-kind.svg b/software_engineering/5_Test/img/test-kind.svg index 9c2523f..32bb173 100644 --- a/software_engineering/5_Test/img/test-kind.svg +++ b/software_engineering/5_Test/img/test-kind.svg @@ -14,7 +14,7 @@ viewBox="0 0 150.94202 84.703125" version="1.1" id="svg8" - inkscape:version="0.92.3 (2405546, 2018-03-11)" + inkscape:version="0.48.5 r10040" sodipodi:docname="test-kind.svg"> @@ -25,16 +25,16 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.98994949" - inkscape:cx="293.86581" - inkscape:cy="139.50725" + inkscape:zoom="1.4" + inkscape:cx="250.30873" + inkscape:cy="130.85432" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" - inkscape:window-width="1217" - inkscape:window-height="558" - inkscape:window-x="71" - inkscape:window-y="39" + inkscape:window-width="1432" + inkscape:window-height="975" + inkscape:window-x="691" + inkscape:window-y="310" inkscape:window-maximized="0" fit-margin-top="0" fit-margin-left="0" @@ -48,7 +48,7 @@ image/svg+xml - + @@ -73,38 +73,14 @@ x="49.483749" y="54.277294" ry="13.589852" /> - - - - + ry="2.620635" /> Application - + ry="2.4190476" /> - - - - + y="62.087799" + ry="3.4269841" /> Processing + y="75.485954" + style="font-weight:bold;stroke:none">Processing - - - - Data + transform="matrix(0.61620494,0,0,0.61620494,13.108149,10.611319)"> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker-start:none;marker-mid:none;marker-end:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + transform="matrix(0.61620495,0,0,0.61620495,91.251455,10.611317)"> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker-start:none;marker-mid:none;marker-end:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:none;stroke:#000000;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> - - - User + + My test + My code +   Mock + + + + + + + Input + Output + «creates» + «depends on» + + sodipodi:nodetypes="ccccccccccc" /> + + + + + + + diff --git a/software_engineering/5_Test/img/test.svg b/software_engineering/5_Test/img/test.svg new file mode 100644 index 0000000..f15f4ed --- /dev/null +++ b/software_engineering/5_Test/img/test.svg @@ -0,0 +1,181 @@ + + + + + + + + + + image/svg+xml + + + + + + + My test + My code +   Mock + + + + + + + Input + Output + «creates» + «depends on» + + diff --git a/software_engineering/5_Test/img/ttd-workflow.svg b/software_engineering/5_Test/img/ttd-workflow.svg index c42cd03..26876ef 100644 --- a/software_engineering/5_Test/img/ttd-workflow.svg +++ b/software_engineering/5_Test/img/ttd-workflow.svg @@ -9,11 +9,11 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="654.58539" + width="689.64081" height="213.27" id="svg3874" version="1.1" - inkscape:version="0.92.3 (2405546, 2018-03-11)" + inkscape:version="0.48.5 r10040" sodipodi:docname="ttd-workflow.svg"> @@ -24,9 +24,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.7" - inkscape:cx="358.26591" - inkscape:cy="108.38849" + inkscape:zoom="0.98994949" + inkscape:cx="160.17407" + inkscape:cy="126.6609" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -34,8 +34,8 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:window-width="1206" - inkscape:window-height="502" + inkscape:window-width="1621" + inkscape:window-height="1008" inkscape:window-x="108" inkscape:window-y="136" inkscape:window-maximized="0" /> @@ -47,7 +47,7 @@ image/svg+xml - + @@ -55,53 +55,55 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-78.239969,-220.84491)"> + transform="translate(-56.016613,-220.84491)"> WriteWritefailingfailingtests + style="font-size:40px;font-weight:bold;line-height:1;font-family:sans-serif">tests MakeMakethe teststhe testspass + style="font-size:40px;font-weight:bold;line-height:1;font-family:sans-serif">pass   Refactoring + style="font-size:40px;font-weight:bold;line-height:1.25;font-family:sans-serif">Refactor diff --git a/software_engineering/5_Test/index.rst b/software_engineering/5_Test/index.rst deleted file mode 100644 index cc0eaeb..0000000 --- a/software_engineering/5_Test/index.rst +++ /dev/null @@ -1,298 +0,0 @@ - -.. raw:: html - - - -.. |br| raw:: html - -
- -Testing -======= - -#. Introduction -#. Python `unittest` module -#. Additional tools -#. Continuous integration - ------- - -What is it? ------------ - -- A task consisting of checking that the **program** is working as expected -- Manually written **tests** which can be automatically executed -- Different methodologies: Always and before anything else (test-driven development, *TDD*, [TDDwithPython]) - -.. image:: img/ttd-workflow.png - -.. [TDDwithPython] `Harry J.W. Percival. Test-Driven Development with Python. O'Reilly 2014. `_ - (Oriented towards web development). - -Presenter Notes -............... - -- Test inject input to the program, and check output -- Answer valid or not -- For maintenance: Reproduce bug in a test, then fix it. - ----- - -Why testing? ------------- - -- Validate the code to the specifications -- Find problems early -- Facilitates change -- Documentation -- Code quality - - - Better design - - Simplifies integration - ----- - -Why not? --------- - -- Extra work -- Not perfect -- Extra maintenance - - - More difficult to refactoring - - Maintain environments - -- Delays integration - -Presenter Notes -............... - -- 30% percent of the time of the project -- Having the structure set-up for testing encourages writing tests. -- Then... let's talk about the structure :-) - ----- - -What kinds of tests? --------------------- - -- **Functional tests**: Tests scripts, public API, GUI -- **Integration tests**: Tests components, code correctly integrated -- **Unit tests**: Tests independant pieces of code - -.. image:: img/test-pyramid.png - -.. [TestPyramid] Mike Cohn. Succeeding with Agile: Software Development Using Scrum. 2009. - -Presenter Notes -............... - -The test pyramid is a concept developed by Mike Cohn, described in his book "Succeeding with Agile" - -- Unit tests (dev point of view, fast, low cost) -- Integration tests -- Functional tests (user point of view, but slow, and expensive) - -- Cost: unit << integration << functional -- Fast to execute: unit >> integration >> functional - ------- - -Where to put the tests? ------------------------ - -Separate tests from the source code: - -- Run the test from the command line. -- Separate test code when distributing. -- `... `_ - -Folder structure: - -- In a separate `test/` folder. -- In `test` sub-packages in each Python package/sub-package, - so that tests remain close to the source code. - Tests are installed with the package and can be run from the installation. -- A `test_*.py` for each module and script (an more if needed). -- Consider separating tests that are long to run from the others. - ------- - -Where to put the tests? ------------------------ - -:: - - project - setup.py - run_tests.py - package/ - __init__.py - module1.py - test/ - __init__.py - test_module1.py - subpackage/ - __init__.py - module1.py - module2.py - test/ - __init__.py - test_module1.py - test_module2.py - -Presenter Notes -............... - - scripts/ - my_script.py - my_other_script.py - test/ - test_my_script.py - test_my_other_script.py - ------- - -.. include:: unittest.rst - ------- - -Extra tools ------------ - ------- - -QTest -..... - -For GUI based on `PyQt`, `PySide` it is possible to use Qt's `QTest `_. - -It provides the basic functionalities for GUI testing. -It allows to send keyboard and mouse events to widgets. - -.. code-block:: python - - from PyQt4.QtTest import QTest - from PyQt4 import QtCore - - ... - - widget = ... - - QTest.qWaitForWindowShown(widget) - QTest.mouseClick(widget, QtCore.Qt.LeftButton, pos=QtCore.QPoint(1, 1)) - QTest.keyClicks(widget, 'test', delay=100) # Wait 100ms - - ... - -Tighly coupled with the code it tests. -It needs to know the widget's instance and hard coded position of mouse events. - ------- - -Test coverage -............. - -Using `coverage.py `_ to gather coverage statistics while running the tests: - -#. Install `coverage.py` package: `pip install coverage`. -#. Run the tests: `python -m coverage run --source run_tests.py` -#. Show report: - - - `python -m coverage report` - - `python -m coverage html` - -:: - - Name Stmts Miss Cover - ---------------------------------------------------------- - rounding/__init__ 5 1 80% - rounding/tests/__init__ 13 4 69% - rounding/tests/test_parametric_round 27 1 96% - rounding/tests/test_round 23 1 96% - ---------------------------------------------------------- - TOTAL 68 7 90% - ------- - -Extra test tools -................ - -Extending `unittest`: - -- `pytest `_ -- `nose `_ - -Running the tests on different Python environments: - -- `tox `_ automates testing of Python packages - ------- - -Continuous integration ----------------------- - -Benefits: - -- Test on multiple platform/configuration (e.g., different version of Python). -- Test often: each commit, each pull request, daily... - -Costs: - -- Set-up and maintenance. -- Test needs to be automated. - ------- - -Continuous integration: In-house --------------------------------- - -- Cron -- `Buildbot `_: - -.. image:: img/buildbot-overview.png - -Presenter Notes -............... - -Buildbot: -- `Master` server with a web interface that controls the tests. -- `Slave` nodes that runs the tests. - ------- - -Continuous integration: Cloud ------------------------------ - -- `Travis-CI `_: Linux and MacOS -- `AppVeyor `_: Windows - -Principle: - -- Add a `.yml` file to your repository describing: - - - The test environment - - Build and installation of the dependencies and the package - - The way to run the tests. - -- Upon commit, clones the repository and runs the tests. -- Displays the outcome on a web page. -- Feedback github Pull Requests with test status. - ------- - -Sum-up ------- - -- Different test strategies. -- Python `unittest` (and extra packages) to write and run the tests. -- Additional tools to efficiently run the tests: Continuous Integration. -- Next step: Continuous Deployment. diff --git a/software_engineering/5_Test/test_projects.ipynb b/software_engineering/5_Test/notebook.ipynb similarity index 71% rename from software_engineering/5_Test/test_projects.ipynb rename to software_engineering/5_Test/notebook.ipynb index 1d597e9..2380fc4 100644 --- a/software_engineering/5_Test/test_projects.ipynb +++ b/software_engineering/5_Test/notebook.ipynb @@ -1,8 +1,19 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "# To execute before running the slides" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "slideshow": { "slide_type": "skip" @@ -10,23 +21,93 @@ }, "outputs": [], "source": [ - "# This have to be executed before running the slides\n", - "# Monkey patch unittest to be able to run it in the notebook\n", - "\n", "import unittest\n", "\n", "def apply_jupyter_patch():\n", - " default_unittest_main = unittest.main\n", - "\n", + " \"\"\"Monkey patch unittest to be able to run it in the notebook\"\"\"\n", " def jupyter_unittest_main(**kwargs):\n", " if \"argv\" not in kwargs:\n", " kwargs[\"argv\"] = ['ignored']\n", " kwargs[\"exit\"] = False\n", - " default_unittest_main(**kwargs)\n", + " jupyter_unittest_main._original(**kwargs)\n", + "\n", + " if unittest.main.__module__ != \"unittest.main\":\n", + " # Restiture the previous state, in case\n", + " unittest.main = unittest.main._original\n", "\n", + " # Apply the patch\n", + " jupyter_unittest_main._original = unittest.main\n", " unittest.main = jupyter_unittest_main\n", "\n", - "apply_jupyter_patch()" + "apply_jupyter_patch()\n", + "\n", + "\n", + "def polynom(a, b, c):\n", + " \"\"\"The function that will be tested.\"\"\"\n", + " delta = (b**2.0) - 4.0 * a * c\n", + " solutions = []\n", + " if delta > 0:\n", + " solutions.append((-b + (delta**0.5)) / (2.0 * a))\n", + " solutions.append((-b - (delta**0.5)) / (2.0 * a))\n", + " elif delta == 0:\n", + " solutions.append(-b / (2.0 * a))\n", + " return solutions\n", + "\n", + "\n", + "try:\n", + " from PyQt5 import Qt\n", + "\n", + " qapp = Qt.QApplication.instance()\n", + " if Qt.QApplication.instance() is None:\n", + " qapp = Qt.QApplication([])\n", + "\n", + " class PolynomSolver(Qt.QMainWindow):\n", + " \n", + " def __init__(self, parent=None):\n", + " super(PolynomSolver, self).__init__(parent=parent)\n", + " self.initGui()\n", + " \n", + " def initGui(self):\n", + " self.setWindowTitle(\"Polygon Solver\")\n", + " self._inputLine = Qt.QLineEdit(self)\n", + " self._processButton = Qt.QPushButton(self)\n", + " self._processButton.setText(u\"Solve ax² + bx + c = 0\")\n", + " self._processButton.clicked.connect(self.processing)\n", + " self._resultWidget = Qt.QLabel(self)\n", + " \n", + " widget = Qt.QWidget()\n", + " layout = Qt.QFormLayout(widget)\n", + " layout.addRow(\"Coefs a b c:\", self._inputLine)\n", + " layout.addRow(\"Solutions:\", self._resultWidget)\n", + " layout.addRow(self._processButton)\n", + " self.setCentralWidget(widget)\n", + " \n", + " def getCoefs(self):\n", + " text = self._inputLine.text()\n", + " data = [float(i) for i in text.split()]\n", + " a, b, c = data\n", + " return a, b, c\n", + " \n", + " def processing(self):\n", + " try:\n", + " a, b, c = self.getCoefs()\n", + " except Exception as e:\n", + " Qt.QMessageBox.critical(self, \"Error while reaching polygon coefs\", str(e))\n", + " return\n", + " try:\n", + " result = polynom(a, b, c)\n", + " except Exception as e:\n", + " Qt.QMessageBox.critical(self, \"Error while computing the polygon solution\", str(e))\n", + " return\n", + " \n", + " if len(result) == 0:\n", + " text = \"No solution\"\n", + " else:\n", + " text = [\"%0.3f\" % x for x in result]\n", + " text = \" \".join(text)\n", + " self._resultWidget.setText(text)\n", + "except ImportError as e:\n", + " print(str(e))" ] }, { @@ -56,13 +137,11 @@ "source": [ "# What is it?\n", "\n", + "- Part of the software quality\n", "- A task consisting of checking that the **program** is working as expected\n", "- Manually written **tests** which can be automatically executed\n", - "- Different methodologies: Always and before anything else\n", "\n", - "\n", - "\n", - "- Harry J.W. Percival (2014). [Test-Driven Development with Python. O'Reilly](https://www.oreilly.com/library/view/test-driven-development-with/9781449365141/)" + "" ] }, { @@ -75,12 +154,12 @@ "source": [ "# Presenter Notes\n", "\n", - "- Test inject input to the program, and check output\n", - "- Answer valid or not\n", - "- For maintenance: Reproduce bug in a test, then fix it.\n" + "- A test injects input to the program, and checks output\n", + "- It answers if the code is valid or not (for a specific usecase)" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { @@ -88,16 +167,13 @@ } }, "source": [ - "# Why testing?\n", + "# Different methodologies\n", + "\n", + "- Test-driven development: Always and before anything else\n", "\n", - "- Validate the code to the specifications\n", - "- Find problems early\n", - "- Facilitates change\n", - "- Documentation\n", - "- Code quality\n", + "\n", "\n", - " - Better design\n", - " - Simplifies integration\n" + "- Harry J.W. Percival (2014). [Test-Driven Development with Python. O'Reilly](https://www.oreilly.com/library/view/test-driven-development-with/9781449365141/)" ] }, { @@ -108,16 +184,16 @@ } }, "source": [ - "# Why not?\n", - "\n", - "- Extra work\n", - "- Not perfect\n", - "- Extra maintenance\n", - "\n", - " - More difficult to refactoring\n", - " - Maintain environments\n", + "# Why testing?\n", "\n", - "- Delays integration" + "| Benefits | Disadvantage |\n", + "|-----------------------------------------|---------------------------------------------|\n", + "| Find problems early | Extra work (to write and execute) |\n", + "| Globally reduce the cost | Maintain test environments |\n", + "| To validate the code to specifications | Does not mean it's bug-free |\n", + "| Safer to changes of the code with | More difficult to change the code behaviour |\n", + "| Improve the software design |   |\n", + "| It's part of documentation and examples |   |\n" ] }, { @@ -131,8 +207,9 @@ "# Presenter Notes\n", "\n", "- 30% percent of the time of a project\n", - "- Having the structure set-up for testing encourages writing tests.\n", - "- Then... let's talk about the structure :-)\n" + "- Cost reduction: If you find a problem late (at deployment for example) the cost can be very hight\n", + "- Automated tests (in CI) reduce the cost of execution, and help contribution and merges\n", + "- Having the structure set-up for testing encourages writing tests\n" ] }, { @@ -148,7 +225,7 @@ "- **Unit tests**: Tests independant pieces of code\n", "- **Integration tests**: Tests components together\n", "- **System tests**: Tests a completely integrated application\n", - "- **Acceptance tests**: Tests the application with selected users (can't be automated)\n", + "- **Acceptance tests**: Tests the application with the customer\n", "\n", "" ] @@ -167,9 +244,10 @@ "\n", "- Unit tests (dev point of view, fast, low cost)\n", "- Integration tests\n", - "- Functional tests (user point of view, but slow, and expensive)\n", + "- System tests\n", + "- Acceptance tests (customer point of view, but slow, and expensive, can't be automated)\n", "\n", - "- Cost: unit << integration << system\n", + "- Cost: unit << integration (not always true) << system\n", "- Fast to execute: unit >> integration >> system\n" ] }, @@ -186,7 +264,7 @@ "Separate tests from the source code:\n", "\n", "- Run the test from the command line.\n", - "- Separate test code when distributing.\n", + "- Separate tests and code distributing.\n", "- [...](https://docs.python.org/3/library/unittest.html#organizing-test-code)\n", "\n", "Folder structure:\n", @@ -323,20 +401,11 @@ } }, "source": [ - "# Example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def my_true_round(value):\n", - " # Default python3 round uses the round-to-even method (bankers’ rounding)\n", - " # Here we use the round-half-away-from-zero method\n", - " rounded = (int(value + (0.5 if value > 0 else -0.5)))\n", - " return rounded" + "# Example\n", + "\n", + "Test the `polynom` function provided in the `pypolynom` sample project.\n", + "\n", + "It solves the equation $ax^2 + bx + c = 0$." ] }, { @@ -347,26 +416,25 @@ "source": [ "import unittest\n", "\n", - "class TestMyTrueRound(unittest.TestCase):\n", - "\n", - " def test_positive(self):\n", - " self.assertEqual(my_true_round(1.3), 1)\n", - "\n", - " def test_negative(self):\n", - " self.assertEqual(my_true_round(-1.3), -1)\n", + "class TestPolynom(unittest.TestCase):\n", "\n", - " def test_halfway_even(self):\n", - " self.assertEqual(my_true_round(2.5), 3)\n", + " def test_0_roots(self):\n", + " result = polynom(2, 0, 1)\n", + " self.assertEqual(len(result), 0)\n", "\n", - " def test_negative_halfway_even(self):\n", - " self.assertEqual(my_true_round(-2.5), -3)\n", + " def test_1_root(self):\n", + " result = polynom(2, 0, 0)\n", + " self.assertEqual(len(result), 1)\n", + " self.assertEqual(result, [0])\n", "\n", - " def test_returned_type(self):\n", - " self.assertIsInstance(my_true_round(0.0), int)\n", + " def test_2_root(self):\n", + " result = polynom(4, 0, -4)\n", + " self.assertEqual(len(result), 2)\n", + " self.assertEqual(set(result), set([-1, 1]))\n", "\n", "if __name__ == \"__main__\":\n", - " unittest.main(defaultTest=\"TestMyTrueRound\")\n", - " # unittest.main(verbosity=2, defaultTest=\"TestMyTrueRound\")" + " unittest.main(defaultTest=\"TestPolynom\")\n", + " # unittest.main(verbosity=2, defaultTest=\"TestPolynom\")" ] }, { @@ -451,7 +519,7 @@ "class TestCaseWithFixture(unittest.TestCase):\n", "\n", " def setUp(self):\n", - " self.file = open(\"img/test-pyramid.png\", \"rb\")\n", + " self.file = open(\"img/test-pyramid.svg\", \"rb\")\n", " print(\"open file\")\n", "\n", " def tearDown(self):\n", @@ -489,21 +557,21 @@ "metadata": {}, "outputs": [], "source": [ - "class TestMyTrueRound(unittest.TestCase):\n", + "class TestPolynom(unittest.TestCase):\n", "\n", - " def test_bad_types(self):\n", + " def test_argument_error(self):\n", " try:\n", - " my_true_round('2')\n", + " polynom(0, 0, 0)\n", " self.fail()\n", - " except TypeError:\n", + " except ZeroDivisionError:\n", " self.assertTrue(True)\n", "\n", - " def test_bad_types__better_way(self):\n", - " with self.assertRaises(TypeError):\n", - " my_true_round('2')\n", + " def test_argument_error__better_way(self):\n", + " with self.assertRaises(ZeroDivisionError):\n", + " result = polynom(0, 0, 0)\n", "\n", "if __name__ == \"__main__\":\n", - " unittest.main(defaultTest='TestMyTrueRound')" + " unittest.main(defaultTest='TestPolynom')" ] }, { @@ -537,95 +605,25 @@ "metadata": {}, "outputs": [], "source": [ - "class TestMyTrueRound(unittest.TestCase):\n", + "class TestPolynom(unittest.TestCase):\n", "\n", - " HALFWAY_TESTS = ((0.5, 1), (1.5, 2), (2.5, 3))\n", + " TESTCASES = {\n", + " (2, 0, 1): [],\n", + " (2, 0, 0): [0],\n", + " (4, 0, -4): [1, -1]\n", + " }\n", "\n", " def test_all(self):\n", - " for value, expected in self.HALFWAY_TESTS:\n", - " self.assertEqual(my_true_round(value), expected)\n", + " for arguments, expected in self.TESTCASES.items():\n", + " self.assertEqual(polynom(*arguments), expected)\n", "\n", " def test_all__better_way(self):\n", - " for value, expected in self.HALFWAY_TESTS:\n", - " with self.subTest(value=value, expected=expected):\n", - " self.assertEqual(my_true_round(value), expected)\n", + " for arguments, expected in self.TESTCASES.items():\n", + " with self.subTest(arguments=arguments, expected=expected):\n", + " self.assertEqual(polynom(*arguments), expected)\n", "\n", "if __name__ == \"__main__\":\n", - " unittest.main(defaultTest='TestMyTrueRound')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Chaining tests\n", - "\n", - "How-to run tests from many ``TestCase`` and many files at once:\n", - "\n", - "- Explicit:\n", - " Full control, boilerplate code.\n", - "\n", - "- Automatic:\n", - " No control\n", - "\n", - "- Mixing approach\n", - "\n", - "\n", - "The [TestSuite](https://docs.python.org/3/library/unittest.html#unittest.TestSuite) class aggregates test cases and test suites through:\n", - "\n", - "- Allow to test specific use cases\n", - "- Full control of the test sequence\n", - "- But requires some boilerplate code" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Chaining tests example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class TestCase1(unittest.TestCase):\n", - " def test(self): pass\n", - "class TestCase2(unittest.TestCase):\n", - " def test(self): pass\n", - "class TestCaseGui1(unittest.TestCase):\n", - " def test(self): pass\n", - "class TestCaseGui2(unittest.TestCase):\n", - " def test(self): pass\n", - "\n", - "def suite_without_gui():\n", - " loadTests = unittest.defaultTestLoader.loadTestsFromTestCase\n", - " suite = unittest.TestSuite()\n", - " suite.addTest(loadTests(TestCase1))\n", - " suite.addTest(loadTests(TestCase2))\n", - " return suite\n", - "\n", - "def suite_with_gui():\n", - " loadTests = unittest.defaultTestLoader.loadTestsFromTestCase\n", - " suite = unittest.TestSuite()\n", - " suite.addTest(suite_without_gui())\n", - " suite.addTest(loadTests(TestCaseGui1))\n", - " suite.addTest(loadTests(TestCaseGui2))\n", - " return suite\n", - "\n", - "if __name__ == \"__main__\":\n", - " # unittest.main(defaultTest='suite_without_gui')\n", - " unittest.main(defaultTest='suite_with_gui')" + " unittest.main(defaultTest='TestPolynom')" ] }, { @@ -703,36 +701,37 @@ "metadata": {}, "outputs": [], "source": [ - "import unittest, socket\n", - "\n", - "def get_database_access():\n", - " socks = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", - " socks.settimeout(0.300)\n", + "import unittest, os, sys\n", + "\n", + "def is_gui_available():\n", + " # Is there a display\n", + " if sys.platform.startswith('linux'):\n", + " if os.environ.get('DISPLAY', '') == '':\n", + " return False\n", + " # Is there the optional library\n", " try:\n", - " socks.connect((\"my_smart_database\", 42))\n", + " import PyQt8\n", " except:\n", - " return None\n", - " finally:\n", - " socks.close()\n", + " return False\n", + " return True\n", "\n", - "@unittest.skipIf(get_database_access() is None, 'No database')\n", - "class TestMyTrueRoundUsingDatabase(unittest.TestCase):\n", + "@unittest.skipUnless(is_gui_available(), 'GUI not available')\n", + "class TestPolynomGui(unittest.TestCase):\n", "\n", " def setUp(self):\n", - " if get_database_access() is None:\n", - " self.skipTest('No database')\n", + " if not is_gui_available():\n", + " self.skipTest('GUI not available')\n", "\n", - " def test1(self):\n", - " if get_database_access() is None:\n", - " self.skipTest('No database')\n", - " ...\n", + " def test_1(self):\n", + " if not is_gui_available():\n", + " self.skipTest('GUI not available')\n", "\n", - " @unittest.skipIf(get_database_access() is None, 'No database')\n", - " def test2(self):\n", + " @unittest.skipUnless(is_gui_available() is None, 'GUI not available')\n", + " def test_2(self):\n", " pass\n", "\n", "if __name__ == \"__main__\":\n", - " unittest.main(defaultTest='TestMyTrueRoundUsingDatabase')" + " unittest.main(defaultTest='TestPolynomGui')" ] }, { @@ -814,57 +813,69 @@ { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from PyQt5.QtTest import QTest\n", + "\n", + "class TestPolynomGui(unittest.TestCase):\n", + "\n", + " def test_type_and_process(self):\n", + " widget = PolynomSolver()\n", + " QTest.qWaitForWindowExposed(widget)\n", + " QTest.keyClicks(widget._inputLine, '2.000 0 -1', delay=100) # Wait 100ms\n", + " QTest.mouseClick(widget._processButton, Qt.Qt.LeftButton, pos=Qt.QPoint(1, 1))\n", + " self.assertEqual(widget._resultWidget.text(), \"0.707 -0.707\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " unittest.main(defaultTest='TestGui')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tighly coupled with the code it tests.\n", + "It needs to know the widget's instance and hard coded position of mouse events." + ] + }, + { + "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "skip" + "slide_type": "slide" } }, - "outputs": [], "source": [ - "from PyQt5 import Qt\n", + "# Chaining tests\n", + "\n", + "How-to run tests from many ``TestCase`` and many files at once:\n", + "\n", + "- Explicit:\n", + " Full control, boilerplate code.\n", + "\n", + "- Automatic:\n", + " No control\n", "\n", - "qapp = Qt.QApplication.instance()\n", - "if Qt.QApplication.instance() is None:\n", - " qapp = Qt.QApplication([])" + "- Mixing approach\n", + "\n", + "\n", + "The [TestSuite](https://docs.python.org/3/library/unittest.html#unittest.TestSuite) class aggregates test cases and test suites through:\n", + "\n", + "- Allow to test specific use cases\n", + "- Full control of the test sequence\n", + "- But requires some boilerplate code" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "skip" + "slide_type": "slide" } }, - "outputs": [], "source": [ - "from PyQt5 import Qt\n", - "\n", - "class MyApplication(Qt.QMainWindow):\n", - "\n", - " def __init__(self, parent=None):\n", - " super(MyApplication, self).__init__(parent=parent)\n", - " self.initGui()\n", - "\n", - " def initGui(self):\n", - " self._inputLine = Qt.QLineEdit(self)\n", - " self._processButton = Qt.QPushButton(self)\n", - " self._processButton.setText(\"Round\")\n", - " self._processButton.clicked.connect(self.processing)\n", - " self._resultWidget = Qt.QLabel(self)\n", - "\n", - " widget = Qt.QWidget()\n", - " layout = Qt.QVBoxLayout(widget)\n", - " layout.addWidget(self._inputLine)\n", - " layout.addWidget(self._resultWidget)\n", - " layout.addWidget(self._processButton)\n", - " self.setCentralWidget(widget)\n", - "\n", - " def processing(self):\n", - " text = self._inputLine.text()\n", - " data = float(text)\n", - " result = my_true_round(data)\n", - " self._resultWidget.setText(\"%d\" % result)" + "# Chaining tests example" ] }, { @@ -873,27 +884,22 @@ "metadata": {}, "outputs": [], "source": [ - "from PyQt5.QtTest import QTest\n", - "\n", - "class TestGui(unittest.TestCase):\n", + "def suite_without_gui():\n", + " loadTests = unittest.defaultTestLoader.loadTestsFromTestCase\n", + " suite = unittest.TestSuite()\n", + " suite.addTest(loadTests(TestPolynom))\n", + " return suite\n", "\n", - " def test_type_and_process(self):\n", - " app = MyApplication()\n", - " QTest.qWaitForWindowExposed(app)\n", - " QTest.keyClicks(app._inputLine, '2.5', delay=100) # Wait 100ms\n", - " QTest.mouseClick(app._processButton, Qt.Qt.LeftButton, pos=Qt.QPoint(1, 1))\n", - " self.assertEqual(app._resultWidget.text(), \"3\")\n", + "def suite_with_gui():\n", + " loadTests = unittest.defaultTestLoader.loadTestsFromTestCase\n", + " suite = unittest.TestSuite()\n", + " suite.addTest(suite_without_gui())\n", + " suite.addTest(loadTests(TestPolynomGui))\n", + " return suite\n", "\n", "if __name__ == \"__main__\":\n", - " unittest.main(defaultTest='TestGui')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Tighly coupled with the code it tests.\n", - "It needs to know the widget's instance and hard coded position of mouse events." + " # unittest.main(defaultTest='suite_without_gui')\n", + " unittest.main(defaultTest='suite_with_gui')" ] }, { @@ -1036,19 +1042,19 @@ " - python: 3.6\n", " - python: 3.7\n", "\n", - "before_install: # Upgrade distribution modules\n", + "before_install: # Upgrade distribution modules\n", " - python -m pip install --upgrade pip\n", " - pip install --upgrade setuptools wheel\n", "\n", - "install: # Generate source archive and wheel\n", + "install: # Generate source archive and wheel\n", " - python setup.py bdist_wheel\n", "\n", - "before_script: # Install wheel package\n", + "before_script: # Install wheel package\n", " - pip install --pre dist/pypolynom*.whl\n", "\n", - "script: # Run the tests from the installed module\n", + "script: # Run the tests from the installed module\n", " - mkdir tmp ; cd tmp\n", - " - python -m unittest discover pypolynom.test" + " - python -m unittest pypolynom.test.suite_without_gui" ] }, { @@ -1061,12 +1067,12 @@ "source": [ "# Sum-up\n", "\n", - "- Testing is a real job\n", - "- The amount of work must not be under estimate. Designing good test is about 1/3 of resources a project\n", - "- Tests should be done early, to identify design problems, and to improve team work\n", - "- To be tested, an application have to be architectured in this way\n", - "- Continuous integration is particularly useful to prevent regressions, and contributions\n", - "- Aiming exhausting tests is not needed and utopic. A coverage of most cases is a very good start\n", + "- The amount of work must not be under estimate. Designing good test is about 1/3 of resources a project.\n", + "- Tests should be done early, to identify design problems, and to improve team work.\n", + "- To be tested, an application have to be architectured in this way.\n", + "- Continuous integration is particularly useful to prevent regressions, and help contributions.\n", + "- Aiming at exhaustive tests is not needed and utopic.\n", + "- A coverage of most cases is a good start (80/20%).\n", "- Next step: Continuous deployment." ] } @@ -1088,7 +1094,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/software_engineering/5_Test/unittest.rst b/software_engineering/5_Test/unittest.rst deleted file mode 100644 index be96977..0000000 --- a/software_engineering/5_Test/unittest.rst +++ /dev/null @@ -1,510 +0,0 @@ - -`unittest` Python module ------------------------- - -#. QuickStart -#. Chaining tests -#. Running tests -#. More on TestCase - ------- - -QuickStart -.......... - -`unittest `_ is the default Python module for testing. - -It provides features to: - -- Write tests -- Discover tests -- Run those tests - ------- - -TestCase -^^^^^^^^ - -The classe `unittest.TestCase` is the base class for writting tests for -Python code. - -Usage: - - .. code-block:: python - - import unittest - - class ClassName(unittest.TestCase): - - def testFooBar1(self): - ... # Test code - - ... # Other test methods - ------- - -TestCase assert methods -^^^^^^^^^^^^^^^^^^^^^^^ - - .. code-block:: python - - assertEqual(a, b, message=None) - - assertTrue(x, message=None) - -- Argument(s) to compare/evaluate. -- An additional error message. - -========================= ==================== ======= -Method Checks that New in -========================= ==================== ======= -assertEqual(a, b) a == b -assertNotEqual(a, b) a != b -assertTrue(x) bool(x) is True -assertFalse(x) bool(x) is False -assertIs(a, b) a is b 2.7 3.1 -assertIsNone(x) x is None 2.7 3.1 -assertIn(a, b) a in b 2.7 3.1 -assertIsInstance(a, b) isinstance(a, b) 2.7 3.2 -========================= ==================== ======= - -There's more, see `unittest TestCase documentation `_. -or `Numpy testing documentation `_. - ------- - -Run the tests -^^^^^^^^^^^^^ - -test_round.py: - - .. code-block:: python - - ... - - if __name__ == "__main__": - unittest.main() - -The function `unittest.main()` provides a command line interface to -discover and run the tests. - ------- - -Example -^^^^^^^ - -test_round.py: - -.. code-block:: python - - import unittest - - class TestBuiltInRound(unittest.TestCase): - - def test_positive(self): - result = round(1.3) - self.assertEqual(result, 1) - - def test_negative(self): - result = round(-1.3) - self.assertEqual(result, -1) - - def test_halfway_even(self): - result = round(2.5) - self.assertEqual(result, 2, msg="round(2.5) -> %f != 2" % result) - - def test_returned_type(self): - self.assertIsInstance(round(0.), int) - - if __name__ == "__main__": - unittest.main() - ------- - -Example: Result in Python3 -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Running tests from the command line on Python3:: - - $ python3 test_builtin_round.py - .... - ---------------------------------------------------------------------- - Ran 4 tests in 0.000s - - OK - - ------- - -Example: Result in Python2 -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:: - - $ python2 test_builtin_round.py - F..F - ====================================================================== - FAIL: test_halfway_even (__main__.TestRound) - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "test_builtin_round.py", line 16, in test_halfway_even - self.assertEqual(result, 2, msg="round(2.5) -> %f != 2" % result) - AssertionError: round(2.5) -> 3.000000 != 2 - - ====================================================================== - FAIL: test_returned_type (__main__.TestRound) - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "test_builtin_round.py", line 19, in test_returned_type - self.assertIsInstance(round(0.), int) - AssertionError: 0.0 is not an instance of - - ---------------------------------------------------------------------- - Ran 4 tests in 0.000s - - FAILED (failures=2) - ------- - -Example: Command line arguments -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Running a specific ``TestCase``: - - .. code-block:: bash - - $ python3 test_builtin_round.py TestBuiltInRound - .... - ------------------------------------------------------------- - Ran 4 tests in 0.000s - - OK - -Running a specific test method: - - .. code-block:: bash - - $ python3 test_builtin_round.py TestBuiltInRound.test_positive - . - ------------------------------------------------------------- - Ran 1 test in 0.000s - - OK - ------- - -Chaining tests -.............. - -How-to run tests from many ``TestCase`` and many files at once: - -- Explicit: - Full control, boilerplate code. - -- Automatic: - No control - -- Mixing approach - ------- - -Chaining tests: Suite -^^^^^^^^^^^^^^^^^^^^^ - -The `TestSuite `_ class aggregates test cases and test suites through: - -- `TestSuite.addTest(test)` -- `TestSuite.addTests(test)` - -Example: - - .. code-block:: python - - suite = unittest.TestSuite() - suite.addTest(TestBuiltInRound('test_positive')) - ... - ... - ... - ------- - -Chaining tests: Loader -^^^^^^^^^^^^^^^^^^^^^^ - -`unittest.defaultTestLoader` (an instance of `unittest.TestLoader`) creates `TestSuite` from classes and modules. - -`TestLoader.loadTestsFromTestCase(testCaseClass)`` method creates a `TestSuite` from all `test*` method of a `TestCase` subclass. - - .. code-block:: python - - loadTests = unittest.defaultTestLoader.loadTestsFromTestCase - suite = unittest.TestSuite() - suite.addTest(loadTests(TestBuiltInRound)) - ----- - -Chaining tests: Module -^^^^^^^^^^^^^^^^^^^^^^ - -First, write a ``suite`` function for each module (i.e., file): - -.. code-block:: python - - # test_round.py - - ... - - def suite(): - loadTests = unittest.defaultTestLoader.loadTestsFromTestCase - suite = unittest.TestSuite() - suite.addTest(loadTests(TestBuiltInRound)) - return suite - ------- - -Chaining tests: Package -^^^^^^^^^^^^^^^^^^^^^^^ - -Then a ``suite`` function collecting all tests in a package (i.e., directory). - -.. code-block:: python - - # __init__.py or test_all.py - - from . import test_builtin_round - ... - - def suite(): - suite = unittest.TestSuite() - suite.addTest(test_builtin_round.suite()) - ... - return suite - -This can be used to create a ``TestSuite`` from all tests in a project: - -- Full control over the creation of the ``TestSuite``. -- Requires some boilerplate code. - ------- - -Chaining tests: Runner -^^^^^^^^^^^^^^^^^^^^^^ - -To run the ``suite`` from command line: - -.. code-block:: python - - ... - - def suite(): - ... - - if __name__ == "__main__": # True if run as a script - unittest.main(defaultTest='suite') - ------- - -Project: Running tests -^^^^^^^^^^^^^^^^^^^^^^ - -- `unittest.main` to run each module independantly. -- Command line: `python -m unittest ...` -- With a `run_tests.py` script. - -Minimal run_tests.py: - -.. code-block:: python - - import unittest - import mymodule.tests - - runner = unittest.TextTestRunner() - runner.run(mymodule.tests.suite()) - ------- - -Sum-up -^^^^^^ - -- For each modules - - Write a test module with tests as `TestCase` sub-class - - Use `assert` methods in the tests - - Run the tests as a script from the command line - -- For packages and project - - Chain tests with `TestSuite` - - Create a script to run your tests - ------- - -More features -^^^^^^^^^^^^^ - -- Fixture -- Testing exception -- Skipping tests -- Parametric tests -- Test data - ------- - -Fixture -^^^^^^^ - -Tests might need to share some common initialisation/finalisation (e.g., create a temporary directory). - -This can be implemented in ``setUp`` and ``tearDown`` methods of ``TestCase``. -Those methods are called before and after each test. - -.. code-block:: python - - class TestCaseWithFixture(unittest.TestCase): - - def setUp(self): - ... # Pre-test code - - def tearDown(self): - ... # Post-test code - - ... # Tests - ----- - -More fixture -^^^^^^^^^^^^ - -.. code-block:: python - - # Module fixture - - def setUpModule(): # Called before all the tests of this module - ... - - def tearDownModule(): # Called after all the tests of this module - ... - - # Class fixture: - - class TestSample(unittest.TestCase): - - @classmethod - def setUpClass(cls): # Called before all the tests of this class - ... - - @classmethod - def tearDownClass(cls): # Called after all the tests of this class - ... - ------- - -Testing exception -^^^^^^^^^^^^^^^^^ - -``TestCase.assertRaises``: - -.. code-block:: python - - class TestBuiltInRound(unittest.TestCase): - - def test_raise_type_error(self): - with self.assertRaises(TypeError) - result = round('2') - -``TestCase.assertRaisesRegexp`` also checks the message of the exception. - ------- - -Skipping tests -^^^^^^^^^^^^^^ - -Why skipping a test: Test requires a specific OS or a specific version of a library... - -To skip a test, call ``TestCase.skipTest(reason)`` from the test* or ``setUp`` method. - -Also available through decorators ``unittest.skip``, ``unittest.skipIf``, ``unittest.skipUnless``. - -.. code-block:: python - - import sys - import unittest - - class TestBuiltInRound(unittest.TestCase): - - def test_python2(self): - if sys.version_info[0] != 2: - self.skipTest('Requires Python 2') - self.assertEqual(round(2.5), 3.0) - - @unittest.skipIf(sys.version_info[0] != 3, 'Requires Python 3') - def test_python3(self): - self.assertEqual(round(2.5), 2) - - ------- - -Parametric tests -^^^^^^^^^^^^^^^^ - -Running the same test with multiple values: - -.. code-block:: python - - class TestBuiltInRound(unittest.TestCase): - - HALFWAY_TESTS = ((0.5, 0), (1.5, 2), (2.5, 2)) - - def test_halfways(self): - for value, expected in self.HALFWAY_TESTS: - self.assertEqual(round(value), expected) - -Problems: - -- The first failure stops the test, remaining test values are not processed. -- There is no information on the value for which the test has failed. - ------- - -Parametric tests: Python >= 3.4 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Using ``TestCase.subTest``: - -.. code-block:: python - - class TestBuiltInRound(unittest.TestCase): - - HALFWAY_TESTS = ((0.5, 0), (1.5, 2), (2.5, 2)) - - def test_halfways(self): - for value, expected in self.HALFWAY_TESTS: - with self.subTest(value=value, expected=expected): - self.assertEqual(round(value), expected) - -Run tests for all parameters and advertise which one has failed. - -Limitation: Require Python >= 3.4 - ------- - -Parametric tests: Python < 3.4 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -- Use extra tools. -- Use a compatibility class providing the same API: - E.g., `ParametricTestCase `_ - - - Advertise the failed test parameters. - - Limitation: Stop at the first failure. - ------- - -Test data -^^^^^^^^^ - -How to handle test data? - -Need to separate (possibly huge) test data from python package. - -Download test data and store it in a temporary directory during the tests if not available. - -Example: `pyFAI/test/utilstest.py `_ diff --git a/software_engineering/6_Documentation/images/conf_py_extensions.png b/software_engineering/6_Documentation/images/conf_py_extensions.png new file mode 100644 index 0000000..9e78cf2 Binary files /dev/null and b/software_engineering/6_Documentation/images/conf_py_extensions.png differ diff --git a/software_engineering/6_Documentation/images/is_power_of_two_doc_screenshot.png b/software_engineering/6_Documentation/images/is_power_of_two_doc_screenshot.png new file mode 100644 index 0000000..49d61a7 Binary files /dev/null and b/software_engineering/6_Documentation/images/is_power_of_two_doc_screenshot.png differ diff --git a/software_engineering/6_Documentation/images/myimage.png b/software_engineering/6_Documentation/images/myimage.png new file mode 100644 index 0000000..a4499dd Binary files /dev/null and b/software_engineering/6_Documentation/images/myimage.png differ diff --git a/software_engineering/6_Documentation/images/rst_cheat_sheet.png b/software_engineering/6_Documentation/images/rst_cheat_sheet.png new file mode 100644 index 0000000..77b58f3 Binary files /dev/null and b/software_engineering/6_Documentation/images/rst_cheat_sheet.png differ diff --git a/software_engineering/6_Documentation/index.rst b/software_engineering/6_Documentation/index.rst index c8bb7d5..cb98581 100644 --- a/software_engineering/6_Documentation/index.rst +++ b/software_engineering/6_Documentation/index.rst @@ -8,19 +8,31 @@ } +.. include:: + +============= +Documentation +============= + +---- + Documenting Python packages -=========================== +--------------------------- Outline -------- +....... #. Introduction -#. Readme -#. Docstrings #. reStructuredText +#. Readme file +#. Docstrings #. Sphinx #. Continuous Documentation -#. Conclusion + +Presenter Notes +............... + +Hands on on the ReadMe file and sphinx ------ @@ -34,7 +46,13 @@ What is this function doing? def p(x): return (x & (x - 1)) == 0 ------- + +Presenter Notes +............... + +Can you guess what is this function + +---- Introduction (2) ---------------- @@ -46,7 +64,7 @@ What is this function doing? def is_power_of_two(value): return (value & (value - 1)) == 0 ------- +---- Introduction (3) ---------------- @@ -65,6 +83,19 @@ What is this function doing? """ return (value & (value - 1)) == 0 + +Presenter Notes +............... + +This is why you should consider documenting your code. +If you have no documentation yet. You should start it. + +It will be easier today than tomorrow. + +You can start by documenting each function you use for you new development. +It is faster that you can expect. + + ------ Different types of documentations @@ -77,80 +108,227 @@ Expert Python Programming. Chapter 10: Documenting your project. September 2008, PACKT Publishing. https://tarekziade.files.wordpress.com/2008/09/chapter-10.pdf ------- - -Some rules for technical writing: - -- Write in two steps: Content first, then organisation and style. -- Target the readership. -- Use a simple style. -- Limit the scope of the information: One concept at a time. -- Use realistic code examples. -- Choose which documentation to write and avoid endless document. -- (Re)Use templates. +See also `software documentation `_. ----- Different types of documentation: -- **Usage**: How to use the software from API, command line or GUI: +* **Operation**: Installation, FAQ + +* **Usage**: How to use the software from API, command line or GUI: - - *Cookbooks*: how to *do* something specific, - - *Tutorials*: how to *use* a feature step-by-step, - - *module API*. + * *Cookbooks*: how to *do* something specific, + * *Tutorials*: how to *use* a feature step-by-step, + * *module API*. -- **Design**: How the software works, how code is organized. +* **Design**: How the software works, how code is organized. - Intended audiance: developers, advanced users looking for insights. + Intended audience: developers, advanced users looking for insights. -- **Operation**: Installation, FAQ Structure all the documents: Index page, tree structure. + ------ -README +Main rules for technical writing: + +* Write in two steps: Ideas first, then organisation and style. +* Target the readership. +* Use a simple style. +* Limit the scope of the information: One concept at a time. +* Use realistic code examples. +* Choose which documentation to write and avoid endless document. +* (Re)Use templates. + + +Presenter Notes +............... + +Tarek Ziadé. +Expert Python Programming. Chapter 10: Documenting your project. +September 2008, PACKT Publishing. + ------ -First look at the project. +reStructuredText (rst) +---------------------- + +wikipedia definition: -README: +`reStructuredText `_ is a file format for textual data used [...] for technical documentation. + +* *Easy-to-read* text markup syntax. +* Conversion to different formats (e.g., html, pdf, latex). +* Version Control System friendly: Text files with one sentence per line. +* Primarily for Python documentation. + +.. note:: All this presentation has been made using rst. -- Name of the project -- Brief description (i.e., abstract) -- Installation -- Documentation: Getting started and/or link to documentation. -- License -- ... ------ -README Formatting +rst inline markup ----------------- -Text file with readable markup syntax. On github: +Allows character style and functionality: + ++-----------------------------------------------------+-----------------------------------------------+ +| rst | result | ++=====================================================+===============================================+ +| .. code-block:: text | | +| | | +| *emphasis* | *emphasis* | ++-----------------------------------------------------+-----------------------------------------------+ +| .. code-block:: text | | +| | | +| **strong emphasis** | **strong emphasis** | ++-----------------------------------------------------+-----------------------------------------------+ +| .. code-block:: text | | +| | `Python hyperlink `_. | +| `Python hyperlink `_. | | ++-----------------------------------------------------+-----------------------------------------------+ + +for more inline markup: http://docutils.sourceforge.net/docs/user/rst/quickref.html#inline-markup + +---- + +rst roles +--------- + +.. code-block:: rst + + :role_name:`content` + +Examples: + +- :literal:`:math:\`\\sqrt{\\frac{x^2}{3}}\`` |rarr| :math:`\sqrt{\frac{x^2}{3}}` +- `1\\ :superscript:`st`` |rarr| 1\ :superscript:`st` + +`Documentation relative to roles `_ + +---- + +roles are presented with colon, role name, colon, content + +rst directive +------------- + +.. code-block:: rst -- Markdown (README.md): - `QuickRef `_, - `QuickRef on github `_. + .. directive_type:: arguments + :option: value - :: + Content: indented and separated by blank lines. - Module - ====== +Example: **Code block** with syntax highlighting: - This is a Python module. +.. code-block:: rst - Installation - ------------ + .. code-block:: python - pip install myexample - ... + def add(a, b): + return a + b -- reStructuredText_ (README.rst): See later. +|rarr| This directive will produce: + +.. code-block:: python + + def add(a, b): + return a + b + +`Documentation relative to directives `_. + +Presenter Notes +............... + +Now we will see in practice how we can use this rst format. + +---- + +README +------ + +It will be the 'first look' at your project. + +It should contains: + +* Name of the project +* Brief description (i.e., abstract) +* Installation +* Documentation: Getting started and/or link to documentation. +* License +* Authors +* More if you think it is relevant + + +You can start from an existing template file: + +* https://github.com/konstantint/python-boilerplate-template/blob/master/README.rst +* https://github.com/rtfd/template/blob/master/README.rst + +Presenter Notes +............... + +README is also commonly write in a .md file: markdown format. +Markdown is the format adopted by Doxygen. Both are very close. ------ +Hands-on +-------- + +Write the README.rst of the project. + +It should include the project name, description, installation, license and author. + +You can use a `rst cheat sheet `_ and create/edit the README.rst file directly from github / gitlab (result preview can help you) + + +.. image:: images/rst_cheat_sheet.png + :width: 80% + +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | rst | result | +.. +=====================================================+===============================================+ +.. | .. code-block:: text | | +.. | | Title | +.. | Title | | +.. | ===== | | +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | .. code-block:: text | | +.. | | | +.. | Section title | Section title | +.. | ------------- | | +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | .. code-block:: text | | +.. | | | +.. | .. image:: imagesmyimage.png | .. image:: images/myimage.png | +.. | :width: 10% | :width: 10% | +.. | | | +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | .. code-block:: text | | +.. | | | +.. | **bold text** | **bold text** | +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | .. code-block:: text | | +.. | | | +.. | *italique text* | *italique text* | +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | .. code-block:: text | | +.. | | `Python hyperlink `_. | +.. | `Python hyperlink `_. | | +.. +-----------------------------------------------------+-----------------------------------------------+ +.. | .. code-block:: text | .. code-block:: python | +.. | | | +.. | .. code-block:: python | res = pypolynom.polymom(a=2, b=-6, c=1) | +.. | | | +.. | res = pypolynom.polymom(a=2, b=-6, c=1) | | +.. +-----------------------------------------------------+-----------------------------------------------+ + +---- + Docstrings ---------- @@ -167,7 +345,6 @@ Docstrings class RandomGenerator(object): """Pseudo random generator class. - It is based on the XORShift algorithm. """ # Class docstring @@ -182,6 +359,11 @@ Docstrings """Returns a pseudo-random float.""" # Method docstring ... +Presenter Notes +............... + +The docstring are accessible from python using help() or __doc__ + ------ Docstrings Content @@ -189,62 +371,324 @@ Docstrings Content `PEP 257 `_ docstring content recommendation: -- For **script**: Module docstring should be its **usage message** from the command line. -- For **module**: List of the classes, exceptions and functions with a one-line summary of each. -- For **class**: **Behavior summary**, list of the public method and instance variables. -- For **function** and **method**: **Behavior summary**, documentation of **arguments**, **return value**, side effects, exceptions raised, restrictions. +* For **script**: Module docstring should be its **usage message** from the command line. +* For **module**: List of the classes, exceptions and functions with a one-line summary of each. +* For **class**: **Behavior summary**, list of the public method and instance variables. +* For **function** and **method**: **Behavior summary**, documentation of **arguments**, **return value**, side effects, exceptions raised, restrictions. ----- -TODO: spinx, speak about directives and roles - +Sphinx ------ -.. include:: restructuredtext.rst +Wikipedia definition: +"Sphinx is a documentation generator written and used by the Python community. It is written in Python, and also used in other environments." + +Sphinx is parsing docstrings to build html / pdf / latex documentation(s). +Sphinx is also able to interpret the rst format. + +.. code-block:: python + + def is_power_of_two(value): + """Function to check given values are a power of two + + :param value: array of value to test + :type value: numpy array of int + :return: True if value is a power of two or is 0. + :rtype: numpy array of bool + """ + return (value & (value - 1)) == 0 + + +.. image:: images/is_power_of_two_doc_screenshot.png + +Presenter Notes +............... + +sphinx is able to produce html / pdf documentation from docstring and a valid synthax based on rst. + +This mean that you can add some rst information to be interpreted by sphinx. + +---- + +Sphinx ------ -.. include:: sphinx.rst +install sphinx using: + +.. code-block:: bash + + pip install sphinx + + +.. note:: `First steps with Sphinx `_. + +---- + +Start up Sphinx +--------------- + +Sphinx documentation generation is based on a configuration file (conf.py) + +Steps to create the sphinx configuration file +1. create a doc folder at the root level +2. execute + +.. code-block:: bash + + sphinx-quickstart ./ + +this will create a source directory and a Makefile. To build the documentation just: + +.. code-block:: bash + + make html + +you can also call from the root dir: + +.. code-block:: bash + + python setup.py build_sphinx -b html + +or + +.. code-block:: bash + + sphinx-build -b html sourcedir builddir + +---- + +Start up Sphinx (2) +------------------- + +|rarr| the doc/source directory contains the 'master document' file. By default the name is 'index.rst'. + +|rarr| index.rst file is the entry point of the documentation. + +|rarr| you can include other ``*.rst`` files to write the documentation. + + +index.rst file contains: + +Table of content +................ + +.. code-block:: rst + + Contents: + + .. toctree:: + :maxdepth: 2 + + polynom.rst + mathutils.rst + +---- + +Start up Sphinx (3) +------------------- + +The different \*.rst files are used to + +* structure the documentation. +* select what is documented. +* avoid pollution of the source code with too much documentation. + +---- + +Start up Sphinx (4) +------------------- + +autodoc +....... + +The sphinx extension ``sphinx.ext.autodoc`` includes docstrings from source code in the generated documentation. + +This will allow you to define what should be included in the documentation and what shouldn't + +.. code-block:: rst + + .. autofunction:: + + .. automodule:: + :members: + :undoc-members: + + .. autoclass:: + :members: + :undoc-members: + :inherited-members: + +And more: ``autoexception, autodata, automethod, autoattribute`` + +Warning: autodoc **imports** the modules to be documented. +The modules must be installed or added to ``sys.path`` in ``conf.py``. +Take care which version gets documented. + +See `sphinx.ext.autodoc documentation `_. + +---- + +autodoc (2) +........... + +For example a simple polynom.rst can look like: + +.. code-block:: rst + + :mod:`polynom`: Polynom + ----------------------- + + .. automodule:: pypolynom.polynom + :members: polynom + +---- + +Info field lists +................ + +Info field list are sequences of fields marked up. +They are usefull in order to document functions for example: + +.. code-block:: python + + def random_xorshift32(last_value, shift_triple=(13, 17, 5)): + """32 bits pseudo-random generator. + + :param numpy.uint32 last_value: Previously returned number or the seed. + :param shift_triple: Bit shifts to use. + :type shift_triple: 3-tuple of int + :return: The generated random number. + :rtype: numpy.uint32 + :raises ValueError: if x is not a numpy.uint32 + """ + x = numpy.uint32(last_value) # Work with 32bits unsigned integer + x ^= numpy.uint32(last_value) << shift_triple[0] + x ^= x >> shift_triple[1] + x ^= x << shift_triple[2] + return x + +Alternative syntax from `Napoleon extension `_ + +- `Google style `_ +- `Numpy style `_ + +---- + +Hands on +-------- + +1. Generate the html documentation of your master branch. Open it using a web browser. +2. Embed documentation of the polynom module. Make sure it contains the documentation of the polynom function. +3. Document the mathutil module with (rst) docstring and regenerate documentation + + |rarr| add docstrings (rst format) to the mathutil.py file + + |rarr| add a mathutil.rst file to embed documentation about the mathutil module + + |rarr| specify the class, function... to include in the documentation + + |rarr| include the mathutil.rst file into the index.rst file + + |rarr| regenerate the documentation + +---- + +Sphinx ------ -Embed a jupyter notebook in the documentation ---------------------------------------------- +.. note:: *.rst files can be generated automatically using **sphinx-apidoc**. See https://www.sphinx-doc.org/en/master/man/sphinx-autogen.html -TODO: use nbsphinx + add a jupyter note book in the source code and associate -documentation with it, +.. code-block:: bash -https://nbsphinx.readthedocs.io/en/0.4.2/ + sphinx-apidoc -o doc/ ./ ---- +sphinx extensions +----------------- + +Sphinx is including several extensions like: + +* mathjax: for math formula, rendered in the browser by MathJax +* autodoc: automatically insert docstrings from modules +* viewcode: include links to source + +.. note:: the extensions to use has to be registred on your sphinx conf.py file + +.. code-block:: python + + extensions = [ + # automatically insert docstrings from modules + 'sphinx.ext.autodoc', + + # include math, rendered in the browser by MathJax + 'sphinx.ext.mathjax', + + # include links to source + 'sphinx.ext.viewcode' + ] + +`see more sphinx extensions `_ + +---- + +sphinx extensions (2) +--------------------- + +several third party libraries are also defining there own directives and/or roles. +rst format is fairly easy to extend. + +* `matplotlib plot `_ +* `embed video `_ +* `list of reference contribution `_ +* `github sphinx contrib `_ + +.. note:: those extensions has to be installed independently of sphinx + +---- + +Embed a jupyter notebook into doc +--------------------------------- + +You can also embed jupyter notebooks into documentation using `'nbsphinx' extension `_. + +Especially if tutorials are already existing, no need to duplicate code. + +see: https://gitlab.esrf.fr/silx/pypolynom_completed/blob/master/doc/source/index.rst which embed the tutorial.ipynb notebook. + +---- + Continuous documentation ------------------------ -Building documentation automatically: `Read the Docs `_. +1. Building documentation automatically from `Read the Docs `_. + + It builds the documentation with Sphinx: -It builds the documentation with Sphinx: + * Install dependencies defined in a *requirements file*. + * Install the package with ``setup.py install``. + * Look for a ``conf.py`` file and use it to build the documentation. + * Make documentation available: ``http://.readthedocs.org/``. -- Install dependencies defined in a *requirements file*. -- Install the package with ``setup.py install``. -- Look for a ``conf.py`` file and use it to build the documentation. -- Make documentation available: ``http://.readthedocs.org/``. + `Documentation of Read the Docs `_ -`Documentation of Read the Docs `_ +2. gitlab also offer the feature `'pages' `_. which allows you to publish static websites that can be generated from CI. +see the `gitlab-ci.yml file `_ and the `gitlab project pages `_ ------ Conclusion ---------- -- Different documentation for different purposes. -- Tools to ease the process. -- Very modular (no need to use everything, e.g., making your own template). -- Having a build system that generates the documentation encourages writing it. -- Documentation becomes out-dated, keeping it with the source code helps maintaining it: update the code and the documentation at the same time. - - -This presentation is written in reStructuredText_. - -.. _reStructuredText: http://docutils.sourceforge.net/rst.html +* If no documentation yet |rarr| start it right away ! +* README file matter +* Different documentation for different purposes +* Powerful tools exist to ease the process +* You should document your project !!! +* Having a build system that generates the documentation encourages writing it. +* Documentation becomes out-dated, keeping it with the source code helps maintaining it: update the code and the documentation at the same time. +* You really should document your project !!! diff --git a/software_engineering/6_Documentation/isonum.txt b/software_engineering/6_Documentation/isonum.txt new file mode 100644 index 0000000..35793b3 --- /dev/null +++ b/software_engineering/6_Documentation/isonum.txt @@ -0,0 +1,82 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + . + Processed by unicode2rstsubs.py, part of Docutils: + . + +.. |amp| unicode:: U+00026 .. AMPERSAND +.. |apos| unicode:: U+00027 .. APOSTROPHE +.. |ast| unicode:: U+0002A .. ASTERISK +.. |brvbar| unicode:: U+000A6 .. BROKEN BAR +.. |bsol| unicode:: U+0005C .. REVERSE SOLIDUS +.. |cent| unicode:: U+000A2 .. CENT SIGN +.. |colon| unicode:: U+0003A .. COLON +.. |comma| unicode:: U+0002C .. COMMA +.. |commat| unicode:: U+00040 .. COMMERCIAL AT +.. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN +.. |curren| unicode:: U+000A4 .. CURRENCY SIGN +.. |darr| unicode:: U+02193 .. DOWNWARDS ARROW +.. |deg| unicode:: U+000B0 .. DEGREE SIGN +.. |divide| unicode:: U+000F7 .. DIVISION SIGN +.. |dollar| unicode:: U+00024 .. DOLLAR SIGN +.. |equals| unicode:: U+0003D .. EQUALS SIGN +.. |excl| unicode:: U+00021 .. EXCLAMATION MARK +.. |frac12| unicode:: U+000BD .. VULGAR FRACTION ONE HALF +.. |frac14| unicode:: U+000BC .. VULGAR FRACTION ONE QUARTER +.. |frac18| unicode:: U+0215B .. VULGAR FRACTION ONE EIGHTH +.. |frac34| unicode:: U+000BE .. VULGAR FRACTION THREE QUARTERS +.. |frac38| unicode:: U+0215C .. VULGAR FRACTION THREE EIGHTHS +.. |frac58| unicode:: U+0215D .. VULGAR FRACTION FIVE EIGHTHS +.. |frac78| unicode:: U+0215E .. VULGAR FRACTION SEVEN EIGHTHS +.. |gt| unicode:: U+0003E .. GREATER-THAN SIGN +.. |half| unicode:: U+000BD .. VULGAR FRACTION ONE HALF +.. |horbar| unicode:: U+02015 .. HORIZONTAL BAR +.. |hyphen| unicode:: U+02010 .. HYPHEN +.. |iexcl| unicode:: U+000A1 .. INVERTED EXCLAMATION MARK +.. |iquest| unicode:: U+000BF .. INVERTED QUESTION MARK +.. |laquo| unicode:: U+000AB .. LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +.. |larr| unicode:: U+02190 .. LEFTWARDS ARROW +.. |lcub| unicode:: U+0007B .. LEFT CURLY BRACKET +.. |ldquo| unicode:: U+0201C .. LEFT DOUBLE QUOTATION MARK +.. |lowbar| unicode:: U+0005F .. LOW LINE +.. |lpar| unicode:: U+00028 .. LEFT PARENTHESIS +.. |lsqb| unicode:: U+0005B .. LEFT SQUARE BRACKET +.. |lsquo| unicode:: U+02018 .. LEFT SINGLE QUOTATION MARK +.. |lt| unicode:: U+0003C .. LESS-THAN SIGN +.. |micro| unicode:: U+000B5 .. MICRO SIGN +.. |middot| unicode:: U+000B7 .. MIDDLE DOT +.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE +.. |not| unicode:: U+000AC .. NOT SIGN +.. |num| unicode:: U+00023 .. NUMBER SIGN +.. |ohm| unicode:: U+02126 .. OHM SIGN +.. |ordf| unicode:: U+000AA .. FEMININE ORDINAL INDICATOR +.. |ordm| unicode:: U+000BA .. MASCULINE ORDINAL INDICATOR +.. |para| unicode:: U+000B6 .. PILCROW SIGN +.. |percnt| unicode:: U+00025 .. PERCENT SIGN +.. |period| unicode:: U+0002E .. FULL STOP +.. |plus| unicode:: U+0002B .. PLUS SIGN +.. |plusmn| unicode:: U+000B1 .. PLUS-MINUS SIGN +.. |pound| unicode:: U+000A3 .. POUND SIGN +.. |quest| unicode:: U+0003F .. QUESTION MARK +.. |quot| unicode:: U+00022 .. QUOTATION MARK +.. |raquo| unicode:: U+000BB .. RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +.. |rarr| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |rcub| unicode:: U+0007D .. RIGHT CURLY BRACKET +.. |rdquo| unicode:: U+0201D .. RIGHT DOUBLE QUOTATION MARK +.. |reg| unicode:: U+000AE .. REGISTERED SIGN +.. |rpar| unicode:: U+00029 .. RIGHT PARENTHESIS +.. |rsqb| unicode:: U+0005D .. RIGHT SQUARE BRACKET +.. |rsquo| unicode:: U+02019 .. RIGHT SINGLE QUOTATION MARK +.. |sect| unicode:: U+000A7 .. SECTION SIGN +.. |semi| unicode:: U+0003B .. SEMICOLON +.. |shy| unicode:: U+000AD .. SOFT HYPHEN +.. |sol| unicode:: U+0002F .. SOLIDUS +.. |sung| unicode:: U+0266A .. EIGHTH NOTE +.. |sup1| unicode:: U+000B9 .. SUPERSCRIPT ONE +.. |sup2| unicode:: U+000B2 .. SUPERSCRIPT TWO +.. |sup3| unicode:: U+000B3 .. SUPERSCRIPT THREE +.. |times| unicode:: U+000D7 .. MULTIPLICATION SIGN +.. |trade| unicode:: U+02122 .. TRADE MARK SIGN +.. |uarr| unicode:: U+02191 .. UPWARDS ARROW +.. |verbar| unicode:: U+0007C .. VERTICAL LINE +.. |yen| unicode:: U+000A5 .. YEN SIGN diff --git a/software_engineering/6_Documentation/sphinx.rst b/software_engineering/6_Documentation/sphinx.rst index b561bd0..2986f8c 100644 --- a/software_engineering/6_Documentation/sphinx.rst +++ b/software_engineering/6_Documentation/sphinx.rst @@ -32,7 +32,7 @@ Building the documentation requires that your Python package and its dependencie ------ Set-up Sphinx for a project -........................... +...........................n ``sphinx-quickstart`` is a shell command line tool that sets-up a default documentation project. It asks you questions and generates ``conf.py`` and the entry point of the documentation ``index.rst``. diff --git a/software_engineering/Makefile b/software_engineering/Makefile index 50d514e..5df6469 100644 --- a/software_engineering/Makefile +++ b/software_engineering/Makefile @@ -18,6 +18,10 @@ png: $(PNGS_FROM_SVGS) # SLIDES # +build/html/%.html: %/notebook.ipynb + @mkdir -p build/html + cd $(dir $<);jupyter nbconvert --to html notebook.ipynb --output=../$@ + build/html/%.html: %/index.rst png @mkdir -p build/html cd $(dir $<);landslide --embed -d ../$@ index.rst