Pruebas y Calidad de Código en Odoo
Odoo 20 incluye herramientas para probar modelos y flujos HTTP. TransactionCase ejecuta la clase en una transacción y aísla cada método mediante un savepoint; los cambios de cada prueba se revierten. setUpClass() prepara datos compartidos y setUp() se ejecuta antes de cada prueba. HttpCase añade utilidades HTTP y de navegador.
Estos tests se ejecutan sobre la biblioteca acumulada, después de completar seguridad, reportes y chatter.
1. Importar y escribir los tests #
Crea tests/__init__.py con from . import test_biblioteca. Odoo descubre el paquete tests del addon; sus módulos deben estar importados por ese inicializador.
Fichero tests/test_biblioteca.py:
1from odoo.tests import TransactionCase, tagged 2from odoo.fields import Command 3from odoo.exceptions import AccessError, UserError 4from unittest.mock import Mock, patch 5import requests 6from odoo.exceptions import ValidationError 7 8 9@tagged('post_install', '-at_install') 10class TestBiblioteca(TransactionCase): 11 @classmethod 12 def setUpClass(cls): 13 super().setUpClass() 14 cls.book = cls.env['library.book'].create({ 15 'name': 'Test book', 'author': 'Test author', 'price': 25, 16 }) 17 18 def test_discount(self): 19 self.assertAlmostEqual(self.book.discounted_price, 22.5) 20 self.book.price = 50 21 self.assertAlmostEqual(self.book.discounted_price, 45) 22 23 def test_negative_pages(self): 24 with self.assertRaises(ValidationError), self.cr.savepoint(): 25 self.book.write({'pages': -1}) 26 27 def test_availability(self): 28 self.assertFalse(self.book.is_available) 29 self.book.action_make_available() 30 self.assertTrue(self.book.is_available) 31 self.book.state = 'borrowed' 32 self.assertFalse(self.book.is_available) 33 34 def test_reader_and_company_access(self): 35 reader_group = self.env.ref('biblioteca.group_library_user') 36 reader = self.env['res.users'].create({ 37 'name': 'Library reader', 'login': 'test_library_reader', 38 'group_ids': [Command.set(reader_group.ids)], 39 'company_id': self.env.company.id, 40 'company_ids': [Command.set(self.env.company.ids)], 41 }) 42 self.assertEqual(self.book.with_user(reader).name, 'Test book') 43 with self.assertRaises(AccessError), self.cr.savepoint(): 44 self.book.with_user(reader).write({'price': 99}) 45 other = self.env['res.company'].create({'name': 'Other library company'}) 46 hidden = self.env['library.book'].create({ 47 'name': 'Other company book', 'company_id': other.id, 48 }) 49 visible = self.env['library.book'].with_user(reader).search([('id', '=', hidden.id)]) 50 self.assertFalse(visible) 51 with self.assertRaises(AccessError), self.cr.savepoint(): 52 self.book.with_user(self.env.ref('base.public_user')).read(['name']) 53 54 def test_exchange_service_errors(self): 55 service = self.env['library.exchange.service'] 56 response = Mock() 57 response.json.return_value = {'success': True, 'source': 'USD', 'quotes': {'USDEUR': 0.92}} 58 target = 'odoo.addons.biblioteca.models.exchange_service.requests.get' 59 with patch.dict('os.environ', {'EXCHANGERATE_ACCESS_KEY': 'test-key'}): 60 with patch(target, return_value=response) as get: 61 self.assertEqual(service.fetch_latest_rates()['source'], 'USD') 62 self.assertEqual(get.call_args.kwargs['timeout'], 10) 63 for payload in ({'success': False}, {'success': True, 'quotes': []}, []): 64 response.json.return_value = payload 65 with self.assertRaises(UserError): 66 service.fetch_latest_rates() 67 with patch(target, side_effect=requests.Timeout('secret-url-test-key')): 68 with self.assertRaises(UserError) as error: 69 service.fetch_latest_rates() 70 self.assertNotIn('test-key', str(error.exception)) 71 72 def test_report_html(self): 73 report = self.env.ref('biblioteca.action_report_library_book') 74 html, _ = report._render_qweb_html(report, self.book.ids) 75 self.assertIn(b'Test book', html)
2. Ejecutar en una base desechable #
No ejecutes pruebas de instalación sobre la base de trabajo o producción. Usa una base nueva biblioteca_test:
1docker compose stop web 2docker compose run --rm web odoo -d biblioteca_test -i biblioteca --test-enable --test-tags /biblioteca --stop-after-init --no-http 3docker compose up -d web
Para repetir sobre una base de pruebas existente usa -u biblioteca. --test-tags /biblioteca selecciona sus pruebas; post_install las ejecuta después de cargar los módulos.
Prueba además con usuarios normales usando with_user(): el superusuario puede ocultar errores de seguridad. Incluye lector, responsable, portal y registros de otra compañía.
3. Depuración #
1docker compose run --rm web odoo shell -d curso_odoo --no-http
En la consola está disponible env, por ejemplo env['library.book'].search([]). El shell no equivale a una sesión de navegador. Usa breakpoint() en desarrollo y evita depurar procesos de producción. Las comprobaciones estáticas y pylint-odoo complementan las pruebas de comportamiento.