SlideShare a Scribd company logo
1 of 11
Download to read offline
Test Doubles
Rubén Bernárdez
 www.rubenbp.com
   @rubenbpv
    #aos2011
¿Por qué usamos dobles?
El colaborador:
class Communicator():

    def send_email(self, address, total_savings):
        suponemos_que_esto_envia_un_email = True



El SUT (System Under Test):
class SavingsService():

    def __init__(self, communicator):
        self.communicator = communicator

    def analyze_month(self, month_savings):
        calculation = month_savings[0] + month_savings[1]
        self.communicator.send_email('savings@iexpertos.com', calculation)
Pruébalo cobarde! … pecador!...
class Communicator():

    def send_email(self, address, total_savings):
        suponemos_que_esto_envia_un_email = True

class SavingsService():

    def __init__(self, communicator):
        self.communicator = communicator

    def analyze_month(self, month_savings):
        calculation = month_savings[0] + month_savings[1]
        self.communicator.send_email('savings@iexpertos.com', calculation)

class LoFlipoEnEl_AOS(unittest.TestCase):
    def test_report_correcto_es_enviado_a_la_central(self):
        communicator = spy(Communicator())
        month_savings = [1234,1276435]
        service = SavingsService(communicator)

        service.analyze_month(month_savings)
        assert_that(communicator.send_email).was_called()
¿Mock o Spy?
class LoFlipoEnEl_AOS(unittest.TestCase):
    def test_al_menos_es_enviado_a_la_central(self):
        communicator = spy(Communicator())
        month_savings = [1234,1276435]
        service = SavingsService(communicator)

       service.analyze_month(month_savings)

       assert_that(communicator.send_email).was_called()

   def test_se_envia_a_la_central_y_nada_mais(self):
       communicator = mock(Communicator())
       expect_call(communicator.send_email)
       month_savings = [1234,1276435]
       service = SavingsService(communicator)

       service.analyze_month(month_savings)

       communicator.assert_that_is_satisfied()
¿Qué test se romperá?
class Communicator():

    def send_email(self, address, total_savings):
        suponemos_que_esto_envia_un_email = True

    def post_savings(self, total_savings):
        suponemos_que_esto_llama_a_un_webservice = True

class SavingsService():

    def __init__(self, communicator):
        self.communicator = communicator

    def analyze_month(self, month_savings):
        calculation = month_savings[0] + month_savings[1]
        self.communicator.send_email('savings@iexpertos.com', calculation)
        self.communicator.post_savings(calculation)
Un diseño distinto...                 ¿Ahora qué hacemos?

class Communicator():

    def send_email(self, address, total_savings):
        suponemos_que_esto_envia_un_email = True

class AccountRepository():
    def month_savings(self):
        suponemos_que_esto_devuelve_de_bd_los_ahorros = True

class SavingsService():

    def __init__(self, communicator, repository):
        self.communicator = communicator
        self.repository = repository

    def analyze_month(self):
        month_savings = self.repository.month_savings()
        calculation = month_savings[0] + month_savings[1]
        self.communicator.send_email('savings@iexpertos.com', calculation)
Mejor Stub que Spy esta vez! fistro!
class SavingsService():

    def __init__(self, communicator, repository):
        self.communicator = communicator
        self.repository = repository

    def analyze_month(self):
        month_savings = self.repository.month_savings()
        calculation = month_savings[0] + month_savings[1]
        self.communicator.send_email('savings@iexpertos.com', calculation)


class LoFlipoEnEl_AOS(unittest.TestCase):
    def test_si_hay_report_envialo_a_la_central(self):
        communicator = spy(Communicator())
        repository = stub(AccountRepository())
        when(repository.month_savings).then_return([1234,1276435])
        service = SavingsService(communicator, repository)

        service.analyze_month(month_savings)

        assert_that(communicator.send_email).was_called()
Tests frágiles VS legibilidad
    Classic VS Mockist
Test doubles frameworks
   ¿Qué más comportamientos se pueden
    programar en los dobles de test? Con qué
    frameworks?
                pyDoubles
                Mockito
                Jmock
                Moq
                Rhino.Mocks
                PHPUnit
                GoogleMock
pyDoubles.org
        Made with TDD and open source!




   when(obj.method).with_args(1).then_return(1000)
   when(obj.method).then_raise(ApplicationException())
   when(obj.method).then_return(1000)
   expect_call(obj.method).with_args(1)
   expect_call(obj.method).with_args(1, ANY_ARG)
   assert_that(obj.method).was_called().with_args(1)
   when(obj.method).with_args(
              str_containing(”abc”)).then_return(1)


                                  Powered by...
iExpertos.com
   Formación para equipos de desarrollo:
       Iniciación a TDD y TDD Avanzado
       eXtreme Programming a fondo (XP)
       Coaching

   Desarrollamos contigo:
       Dos iExpertos en tu empresa desde el arranque
        del proyecto.
       Formación y desarrollo, todo en uno.

More Related Content

Similar to Test Doubles - stubs, spies & mocks

Very basic functional design patterns
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patternsTomasz Kowal
 
Python Magic Methods: a practical example
Python Magic Methods: a practical examplePython Magic Methods: a practical example
Python Magic Methods: a practical exampleNacho Gentile
 
Introduction to coding using Python
Introduction to coding using PythonIntroduction to coding using Python
Introduction to coding using PythonDan D'Urso
 
Micropatterns
MicropatternsMicropatterns
Micropatternscameronp
 
Odoo - From v7 to v8: the new api
Odoo - From v7 to v8: the new apiOdoo - From v7 to v8: the new api
Odoo - From v7 to v8: the new apiOdoo
 
Clean Architecture Applications in Python
Clean Architecture Applications in PythonClean Architecture Applications in Python
Clean Architecture Applications in PythonSubhash Bhushan
 
Empower your App by Inheriting from Odoo Mixins
Empower your App by Inheriting from Odoo MixinsEmpower your App by Inheriting from Odoo Mixins
Empower your App by Inheriting from Odoo MixinsOdoo
 
Monads in python
Monads in pythonMonads in python
Monads in pythoneldariof
 
Tdd for BT E2E test community
Tdd for BT E2E test communityTdd for BT E2E test community
Tdd for BT E2E test communityKerry Buckley
 
PyCon US 2012 - State of WSGI 2
PyCon US 2012 - State of WSGI 2PyCon US 2012 - State of WSGI 2
PyCon US 2012 - State of WSGI 2Graham Dumpleton
 
Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...
Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...
Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...Codemotion
 
Tip Top Typing - A Look at Python Typing
Tip Top Typing - A Look at Python TypingTip Top Typing - A Look at Python Typing
Tip Top Typing - A Look at Python TypingPatrick Viafore
 
Class 26: Objectifying Objects
Class 26: Objectifying ObjectsClass 26: Objectifying Objects
Class 26: Objectifying ObjectsDavid Evans
 

Similar to Test Doubles - stubs, spies & mocks (20)

Very basic functional design patterns
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patterns
 
Python Magic Methods: a practical example
Python Magic Methods: a practical examplePython Magic Methods: a practical example
Python Magic Methods: a practical example
 
Introduction to coding using Python
Introduction to coding using PythonIntroduction to coding using Python
Introduction to coding using Python
 
Micropatterns
MicropatternsMicropatterns
Micropatterns
 
Odoo from 7.0 to 8.0 API
Odoo from 7.0 to 8.0 APIOdoo from 7.0 to 8.0 API
Odoo from 7.0 to 8.0 API
 
Odoo - From v7 to v8: the new api
Odoo - From v7 to v8: the new apiOdoo - From v7 to v8: the new api
Odoo - From v7 to v8: the new api
 
Clean Architecture Applications in Python
Clean Architecture Applications in PythonClean Architecture Applications in Python
Clean Architecture Applications in Python
 
Kaggle KDD Cup Report
Kaggle KDD Cup ReportKaggle KDD Cup Report
Kaggle KDD Cup Report
 
Lecture 8.pdf
Lecture 8.pdfLecture 8.pdf
Lecture 8.pdf
 
Empower your App by Inheriting from Odoo Mixins
Empower your App by Inheriting from Odoo MixinsEmpower your App by Inheriting from Odoo Mixins
Empower your App by Inheriting from Odoo Mixins
 
Monads in python
Monads in pythonMonads in python
Monads in python
 
Tdd for BT E2E test community
Tdd for BT E2E test communityTdd for BT E2E test community
Tdd for BT E2E test community
 
Python classes objects
Python classes objectsPython classes objects
Python classes objects
 
Testing in Django
Testing in DjangoTesting in Django
Testing in Django
 
PyCon US 2012 - State of WSGI 2
PyCon US 2012 - State of WSGI 2PyCon US 2012 - State of WSGI 2
PyCon US 2012 - State of WSGI 2
 
Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...
Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...
Joe Bew - Apprendi un nuovo linguaggio sfruttando il TDD e il Clean Code - Co...
 
Tip Top Typing - A Look at Python Typing
Tip Top Typing - A Look at Python TypingTip Top Typing - A Look at Python Typing
Tip Top Typing - A Look at Python Typing
 
Class 26: Objectifying Objects
Class 26: Objectifying ObjectsClass 26: Objectifying Objects
Class 26: Objectifying Objects
 
Python advance
Python advancePython advance
Python advance
 
Python programming : Inheritance and polymorphism
Python programming : Inheritance and polymorphismPython programming : Inheritance and polymorphism
Python programming : Inheritance and polymorphism
 

Recently uploaded

The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?Antenna Manufacturer Coco
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUK Journal
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 

Recently uploaded (20)

The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 

Test Doubles - stubs, spies & mocks

  • 1. Test Doubles Rubén Bernárdez www.rubenbp.com @rubenbpv #aos2011
  • 2. ¿Por qué usamos dobles? El colaborador: class Communicator(): def send_email(self, address, total_savings): suponemos_que_esto_envia_un_email = True El SUT (System Under Test): class SavingsService(): def __init__(self, communicator): self.communicator = communicator def analyze_month(self, month_savings): calculation = month_savings[0] + month_savings[1] self.communicator.send_email('savings@iexpertos.com', calculation)
  • 3. Pruébalo cobarde! … pecador!... class Communicator(): def send_email(self, address, total_savings): suponemos_que_esto_envia_un_email = True class SavingsService(): def __init__(self, communicator): self.communicator = communicator def analyze_month(self, month_savings): calculation = month_savings[0] + month_savings[1] self.communicator.send_email('savings@iexpertos.com', calculation) class LoFlipoEnEl_AOS(unittest.TestCase): def test_report_correcto_es_enviado_a_la_central(self): communicator = spy(Communicator()) month_savings = [1234,1276435] service = SavingsService(communicator) service.analyze_month(month_savings) assert_that(communicator.send_email).was_called()
  • 4. ¿Mock o Spy? class LoFlipoEnEl_AOS(unittest.TestCase): def test_al_menos_es_enviado_a_la_central(self): communicator = spy(Communicator()) month_savings = [1234,1276435] service = SavingsService(communicator) service.analyze_month(month_savings) assert_that(communicator.send_email).was_called() def test_se_envia_a_la_central_y_nada_mais(self): communicator = mock(Communicator()) expect_call(communicator.send_email) month_savings = [1234,1276435] service = SavingsService(communicator) service.analyze_month(month_savings) communicator.assert_that_is_satisfied()
  • 5. ¿Qué test se romperá? class Communicator(): def send_email(self, address, total_savings): suponemos_que_esto_envia_un_email = True def post_savings(self, total_savings): suponemos_que_esto_llama_a_un_webservice = True class SavingsService(): def __init__(self, communicator): self.communicator = communicator def analyze_month(self, month_savings): calculation = month_savings[0] + month_savings[1] self.communicator.send_email('savings@iexpertos.com', calculation) self.communicator.post_savings(calculation)
  • 6. Un diseño distinto... ¿Ahora qué hacemos? class Communicator(): def send_email(self, address, total_savings): suponemos_que_esto_envia_un_email = True class AccountRepository(): def month_savings(self): suponemos_que_esto_devuelve_de_bd_los_ahorros = True class SavingsService(): def __init__(self, communicator, repository): self.communicator = communicator self.repository = repository def analyze_month(self): month_savings = self.repository.month_savings() calculation = month_savings[0] + month_savings[1] self.communicator.send_email('savings@iexpertos.com', calculation)
  • 7. Mejor Stub que Spy esta vez! fistro! class SavingsService(): def __init__(self, communicator, repository): self.communicator = communicator self.repository = repository def analyze_month(self): month_savings = self.repository.month_savings() calculation = month_savings[0] + month_savings[1] self.communicator.send_email('savings@iexpertos.com', calculation) class LoFlipoEnEl_AOS(unittest.TestCase): def test_si_hay_report_envialo_a_la_central(self): communicator = spy(Communicator()) repository = stub(AccountRepository()) when(repository.month_savings).then_return([1234,1276435]) service = SavingsService(communicator, repository) service.analyze_month(month_savings) assert_that(communicator.send_email).was_called()
  • 8. Tests frágiles VS legibilidad Classic VS Mockist
  • 9. Test doubles frameworks  ¿Qué más comportamientos se pueden programar en los dobles de test? Con qué frameworks?  pyDoubles  Mockito  Jmock  Moq  Rhino.Mocks  PHPUnit  GoogleMock
  • 10. pyDoubles.org Made with TDD and open source!  when(obj.method).with_args(1).then_return(1000)  when(obj.method).then_raise(ApplicationException())  when(obj.method).then_return(1000)  expect_call(obj.method).with_args(1)  expect_call(obj.method).with_args(1, ANY_ARG)  assert_that(obj.method).was_called().with_args(1)  when(obj.method).with_args( str_containing(”abc”)).then_return(1) Powered by...
  • 11. iExpertos.com  Formación para equipos de desarrollo:  Iniciación a TDD y TDD Avanzado  eXtreme Programming a fondo (XP)  Coaching  Desarrollamos contigo:  Dos iExpertos en tu empresa desde el arranque del proyecto.  Formación y desarrollo, todo en uno.