No tot és programar!
class: center, middle count: false # No tot és programar! ## Documentar, testejar, integrar, desplegar i monitoritzar ![PyGRN](https://avatars1.githubusercontent.com/u/28831124?v=4&s=200) [Eduard Carreras i Nadal](mailto:ecarreras@gmail.com) 13/09/2017 - PyGRN #2 [![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) --- # Qui sóc jo? - Sóc un infiltrat en el món del software (sóc teleco) - Apassionat del software i del proćes **art**esanal. - Co-Fundador de [GISCE-TI, S.L](http://gisce.net) - Desenvolupant en Python des del 2007 (Python ❤️) - Amant del programari lliure - [LinkedIn](https://www.linkedin.com/in/ecarreras/), [Twitter](https://twitter.com/ecarreras) i [GitHub](https://github.com/ecarreras) --- # Agenda 1. Documentació de projectes 2. Testing 3. Integració contínua 4. Desplegament 5. Monitorització ??? Separaré la xerrada en aquests blocs Té a veure amb el cicle que m'agradaria --- # Documentació de projectes - Documentació tècnica (APIs) - Documentació d'usuari (manuals) --- ## Documentació tècnica (APIs) - [PEP 257 -- Docstring Conventions](https://www.python.org/dev/peps/pep-0257/) A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the `__doc__` special attribute of that object. ```python """Aquest mòdul de python té funcions per saludar""" def hola(): """Això imprimeix Hola!""" print('Hola!') ``` - Podem consultar l'ajuda des de l'entorn python `help(hola)` o `hola.__doc__` - Ens hauríem d'acostumar més a escriure *docstrings* com a documentació tècnica. No només les signatures de les funcions. --- ## Documentació tècnica (APIs) - Història - No hi havia cap estàndard definit. - Què passa quan es vol *enriquir* una mica el text? - HTML, DocBook, TeX, ... ### Objectius - Ha de ser fàcil de llegir des del propi codi font - Fàcil d'escriure des de qualsevol editor - No ha de tenir informació que hagi de ser deduïda de parsejar el mòdul - Ha de tenir prou *estructura* per poder-se convertir a altres formats - Ha de ser fàcil escriure **tota** la documentació d'un mòdul còmodament ### Solució - [PEP 287 -- reStructuredText Docstring Format](https://www.python.org/dev/peps/pep-0287/) --- ## Documentació tècnica (APIs) - ReStructuredText exemple ```python class Keeper(Storer): """Keep data fresher longer. Extend `Storer`. Class attribute `instances` keeps track of the number of `Keeper` objects instantiated. """ instances = 0 """How many `Keeper` objects are there?""" def __init__(self): """ Extend `Storer.__init__()` to keep track of instances. Keep count in `self.instances` and data in `self.data`. """ Storer.__init__(self) self.instances += 1 self.data = [] """Store data in a list, most recent last.""" def storedata(self, data): """ Extend `Storer.storedata()`; append new `data` to a list (in `self.data`). """ self.data = data ``` --- ## Documentació tècnica (APIs) - Metainformació - Diferents formats de metainformació - ReStructuredText - [Google](http://google.github.io/styleguide/pyguide.html?showone=Comments#Comments) - [Numpy](https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt) - [PEP 3107 -- Function Annotations](https://www.python.org/dev/peps/pep-3107/) (només Python 3!) ### ReStructuredText ```python def stupid_method(first, second): """Aquest mètode retorna la suma dels dos paràmetres. :param int first: El primer paràmentre :param int second: El segon paràmentre :return: La suma dels paràmetres :rtype: int :raises ValueError: Si algun dels dos paràmetres no és un enter """ if not (isinstance(first, int) and isinstance(second int)): raise ValueError("must be integers!") return first + second ``` --- ## Documentació tècnica (APIs) - Metainformació ### Google ```python def stupid_method(first, second): """Aquest mètode retorna la suma dels dos paràmetres. Args: first: Un int com a primer paràmetre. second: Un int com a segon paràmetre. Returns: Un int corresponent a la suma dels paràmetres Raises: ValueError: Si algun dels dos paràmetres no és un enter """ if not (isinstance(first, int) and isinstance(second, int)): raise ValueError("must be integers!") return first + second ``` --- ## Documentació tècnica (APIs) - Metainformació ### Numpy ```python def stupid_method(first, second): """Aquest mètode retorna la suma dels dos paràmetres. Parametres ---------- first: int Un int com a primer paràmetre. second: int Un int com a segon paràmetre. Returns ------- int La suma dels paràmetres Raises ------ ValueError Si algun dels dos paràmetres no és un enter """ if not (isinstance(first, int) and isinstance(second, int)): raise ValueError("must be integers!") return first + second ``` --- ## Documentació tècnica (APIs) - Metainformació ### Function Annotations ```python def stupid_method(first: int, second: int) -> int: """Aquest mètode retorna la suma dels dos paràmetres. :param first: El primer paràmentre :param second: El segon paràmentre :return: La suma dels paràmetres :raises ValueError: Si algun dels dos paràmetres no és un enter """ if not (isinstance(first, int) and isinstance(second, int)): raise ValueError("must be integers!") return first + second ``` ```python def stupid_method(first: "El primer paràmentre", second: "El segon paràmentre") -> "La suma dels paràmetres": """Aquest mètode retorna la suma dels dos paràmetres. :raises ValueError: Si algun dels dos paràmetres no és un enter """ if not (isinstance(first, int) and isinstance(second, int)): raise ValueError("must be integers!") return first + second ``` - Podem accedir a la informació des de l'intèrpret. `supid_method.func_annotations` i retorna un diccionari amb les anotacions. --- ## Documentació tècnica (APIs) - Doctest - Barreja entre tenir documentació, exemples i tests. (3 en 1). - La [documentació de doctest](https://docs.python.org/3/library/doctest.html) és molt extensa i permet fer moltes coses. ```python def stupid_method(first, second): """Aquest mètode retorna la suma dels dos paràmetres. >>> stupid_method(1, 2) 3 >>> stupid_method("a", 2) Traceback (most recent call last): ... ValueError: must be integers! >>> stupid_method(1, "a") Traceback (most recent call last): ... ValueError: must be integers! """ if not (isinstance(first, int) and isinstance(second, int)): raise ValueError("must be integers!") return first + second if __name__ == '__main__': import doctest doctest.testmod() ``` - Podem llançar els tests directament amb: ```sh $ python stupid.py -v ``` --- ## Documentació tècnica (APIs) - Eina ### [Sphinx](http://www.sphinx-doc.org) És una eina per **crear documentació** i podem destacar les següents funcionalitats: - **Diferents formats de sortida**: HTML (diferents temes), WinHelp, LaTeX (pels PDFs), ePub, Texinfo, manpages, text pla. - **Referències creuades**: Enllaços semàntics automàtics de funcions, classes, definicions, ... Fins i tot d'altres documentacions! - **Estructura jeràrquica**: Tot queda organitzat en arbre. - **Índex automàtics**: Índex general i també de tots els mòduls. - **Visualització de codi**: Fent servir la llibreria de resaltat [Pygments](http://pygments.org/) - **Extensible**: [Extensions pròpies](http://www.sphinx-doc.org/en/stable/ext/builtins.html#builtin-sphinx-extensions) o de tercers [instal·lables des de Pypi](https://pypi.org/search/?q=sphinx+extension) Té un assistent per la creació de projectes: ```sh $ pip install Sphinx $ sphinx-quickstart my-project ``` --- ## Documentació d'usuari (on i com?) - ~~Processadors de text~~ **Al carrer!👋** El descartem directament. - Formats pesats i normalment són binaris - Dificultat per fer edició per parts o de forma distribuïda - Dificultat per gestionar un històric de canvis - Dificultat per gestionar revisions de canvis i ampliacions **✨ Spoiler**: [Tracte la documentació com si fos codi](https://github.com/blog/1939-how-github-uses-github-to-document-github) - Fes servir un format de text pla que pugui ser escrit i editat des de qualsevol editor. - Guarda el manual en un repositori de control de versions (Git) - Podem editar per parts o de forma distribuïda gràcies al control de versions - Tenim històric! - Tenim un sistema de *Pull requests* per revisar i aprovar canvis (Merge!) - Fins i tot podem tenir tests del manual! --- ## Documentació d'usuari (format) Hi ha dos formats de text pla que són àmpliament reconeguts: - ReStructuredText - Markdown (ho està petant!) --- ## Documentació d'usuari (format) ### ReStructuredText vs Markdown #### ReStructuredText - 🔝 Sphinx és una de les millors eines de generació de documentació - 🔝 Molt potent. Suporta referències, notes de peu, bibliografia - 🔝 Moltes, moltes i moltes extensions - ❌ Sintàxis força complexe #### Markdown - 🔝 Sintaxi senzilla - ❌ Hi ha diferents "sabors" ➜ [CommonMark](http://commonmark.org/) - ❌ Poques funcionalitats avançades --- ## Documentació d'usuari (eines) - Ja hem parlat d'[Sphinx](#14) - Presentem també una eina per Markdown: [MKdocs](http://www.mkdocs.org/) - No té tants suports de sortida com ReStructuredText + Sphinx - D'una forma fàcil pots tenir una pàgina amb manual d'usuari - Fem més fàcil la col·laboració per membres no tècnics - Extensible amb totes les extensions de [Python-Markdown](https://pythonhosted.org/Markdown/) - Publicació: [ReadTheDocs](https://readthedocs.org/) ### Cicle possible .small[ 1. Creem una branca per afegir/modificar documentació 2. Work and commit 3. Push! 4. Pull-request 5. Testing (el format és vàlid, hi ha traduccions?, faltes d'ortografia, ...) 6. Construcció del lloc temporal amb els canvis 7. Revisió 8. Merge 9. Publicació automàtica nova versió] --- ## Testing (Què és?) **Possible definició**: >> La prova de programari és un conjunt de processos dirigits a investigar, avaluar i determinar la **integritat** i la **qualitat** del programari informàtic. >> Les proves de programari garanteixen el **compliment** d'un producte de programari en relació **amb els requisits** regulatoris, empresarials, tècnics, funcionals i d'usuaris. --- ## Testing (Quins hi ha) Definició per [Robert C. Martin](https://en.wikipedia.org/wiki/Robert_Cecil_Martin) dels [tipus de tests](http://blog.cleancoder.com/uncle-bob/2017/05/05/TestDefinitions.html). - **Tests unitaris**: Escrit pel programador per tal que el codi faci el que el programador espera que faci. - **Tests d'acceptació**: Escrit per *Negoci* amb el propòsit que el codi faci el que *Negoci* necessita. - **Tests d'integració**: Assegurar-se que els **subsistemes** funcionen de forma correcta entre ells. (No tenen a veure amb regles de *negoci*) - **Tests de sistema**: Semblants als d'integració però amb **tot el sistema** - **Micro-tests**: Semblant als tests unitaris, però per una sola funció o un grup de tests molt reduit. - **Tests funcionals**: Tests amb un abast més gran amb *mocks* per els components més lents. --- ## Testing (Quan els fem) - Abans o deprés? - Després els tests poden estar *viciats* - Testejarem **totes** les funcionalitats que hem escrit? - **TDD al rescat!** - Desenvolupament guiat per exemples - [Regles del TDD](http://www.eferro.net/2012/09/resumen-clean-coders-capitulo-6-tdd.html): - **No** escriure codi de producció si no és perquè un test està fallant - Escriure el **mínim** d'un test per fer fallar - Escriure el **mínim** codi necessari per passar el test - Tenim sempre el test abans d'escriure el codi ➜ **Bona cobertura** ### ♪ Uh! Oh! No tinc por! ♫ - **No ens fa por** netejar el codi - **No ens fa por** canviar codi - **No ens fa por** afegir noves funcionalitats - Avancem **més ràpid** --- ## Testing (eines) ### Execució de tests (UnitTest) + `self.assertX`: - Llibreria estàndard ([unittest](https://docs.python.org/3/library/unittest.html)) ```python class TestStupidMethod(TestCase): def test_two_integers_returns_the_sum(self): result = stupid_method(1, 2) self.assertEqual(result, 3) def test_first_param_no_integer_raises_value_error(self): def raise_value_error(): stupid_method("a", 1) self.assertRaises(ValueError, raise_valueerror) ``` - Millores a la llibreria estàndard (UnitTesting): - 🔝 [PyTest](https://docs.pytest.org) - ❌ [Nose2](http://nose2.readthedocs.io) --- ## Testing (eines) ### Execució de tests (BDD) + [Expects](https://expects.readthedocs.io/en/stable/): - [Behave](http://pythonhosted.org/behave/) - [Mamba](http://nestorsalceda.github.io/mamba/) ```python with description('Using the stupid_method'): with context('when passing two integers'): with it('should return the sum of them'): result = stupid_method(1, 2) expect(result).to(equal(3)) with context('when passing a string and an integer'): with it('should raise a ValueError'): def raises_value_error(): stupid_method("a", 1) expect(raise_value_error).to(raise_error(ValueError)) ``` ### Tests funcionals (eines) - [doublex](http://python-doublex.readthedocs.io/en/latest/) - [Mock](https://docs.python.org/3/library/unittest.mock.html) - [vcrpy](http://vcrpy.readthedocs.io/en/latest/) --- ## Integració Contínua (CI) - Tots els canvis s'han d'integrar a la branca *principal*: **ràpid** - S'han de passar els tests per comprovar que aquests canvis són correctes - Evita tenir el *caos* de la release - Ens permet tenir sempre una versió *testing* (binari) - Trobem ràpid bugs d'integració - Obtenir mètriques d'evolució: cobertura, complexitat, fallades. ### Eines - [Buildbot](https://buildbot.net/) (Python powered) - [Jenkins](https://jenkins.io/) - [Drone](https://github.com/drone/drone) - [Travis](https://travis-ci.org/) - [CircleCI](https://circleci.com/) - ... --- ## Desplegament - Conjunt d'activitats que fa que un sofware sigui utiltizable. - On? Altres serveis? pre-condicions, post-condicions - 3 nivells de desplegament - Manual - Semi-automàtic - Automàtic ➜ Augmentar número de desplegaments ### Eines - [Fabric](http://www.fabfile.org/) + [Fabtools](http://fabtools.readthedocs.io): Executa ordres remotes a través de SSH - [Ansible](https://www.ansible.com/application-deployment): Llibres de receptes amb un sistema de plantilles molt potent i moltes receptes ja fetes - [dh-virtualenv](http://dh-virtualenv.readthedocs.io/en/latest/): Crea paquets compatibles amb Debian amb un *virtualenv* autocontingut --- ## Monitoritzar - Volem fer desplegaments més sovint - Provar una funcionalitat a producció - Ser pro-actius - Més informació de l'error ### Eines - [Sentry](https://github.com/getsentry/sentry) (Python powered) --- class: center, middle # Preguntes? # 🙈 🙉 🙊 --- class: center, middle # Moltes gràcies!