# como hacer tests definiendo comportamiento y no implementación Después de estar haciendo tests durante mucho tiempo me he dado cuenta de que muchas veces estamos testeando implementación y no comportamiento. En este ejemplo he podido comprobar como hemos realizado la aproximación de un mismo caso, y como lo he aplicado. ## historia de usuario / funcionalidad Actualmente nuestro sistema deprovisiona onts(cpe) pero no es muy eficiente ya que hay una parte en la que dependemos del hardware, hacemos peticiones en exceso y de forma repetitva con lo que se consume mucho tiempo y necesitamos optimizarlo. El proceso de provisión consiste en 'deseganchar' de forma lógica ( la otra es tirando del cable ) un cpe de del sistema. Para ello necesitamos deprovisionar en la cabecera OLT y darle de baja en nuestro sistema de persistencia de onts que nos indica que onts están provisionadas. A veces por problema de hardware, migración de hardware, operaciones de urgencia o bugs perdemos información de onts, o no tenemos la información actualizada. ## Estado actual ``` python class Deprovisioner(object): def __init__(self, network, ont_repository, olt_provisioner, olt_interface, olt_devices_retriever): self.network = network self.ont_repository = ont_repository self.olt_provisioner = olt_provisioner self.olt_interface = olt_interface self.olt_devices_retriever = olt_devices_retriever def deprovision(self, *serial_numbers): logger.info("Deprovisioned (Forced) begins. Trying to force deprovision {} onts".format(len(serial_numbers))) provisioned_onts_from_olt = self._get_all_provisioned_onts_from_all_olts() serial_numbers_to_deprovision = set(provisioned_onts_from_olt.keys()).intersection(serial_numbers) serial_numbers_not_found = set(serial_numbers) - set(provisioned_onts_from_olt.keys()) if len(serial_numbers_not_found): logger.info("Deprovisioned (Forced): Serial Numbers Not found at any olts {serial_numbers}".format( serial_numbers=list(serial_numbers_not_found))) counter = 0 total = len(serial_numbers_to_deprovision) for serial_number in serial_numbers_to_deprovision: counter += 1 logger.info("Deprovisioned (Forced): serial number {serial_number}. Number {number} of {total}".format( serial_number=serial_number, number=counter, total=total)) self._deprovision_from_olt(provisioned_onts_from_olt.get(serial_number)) self._deprovision_from_repository(serial_number) logger.info("Deprovisioned (Forced) ends") def _get_all_provisioned_onts_from_all_olts(self): provisioned_onts = {} for olt in self.olt_devices_retriever.olt_devices(): try: for ont in self.olt_interface.get_provisioned_from_olt(olt): provisioned_onts[ont.serial_number] = ont except Exception as exc: logger.info('Deprovisioned (Forced) getting info of provisioned onts from olt fails: {olt} exc:{exc}'.format( olt=olt, exc=exc)) return provisioned_onts def _deprovision_from_olt(self, provisioned_ont): self.olt_provisioner.deprovision_from_olt(provisioned_ont) def _deprovision_from_repository(self, serial_number): provisioned_ont = self.ont_repository.find_by(serial_number) if provisioned_ont: self.ont_repository.remove(provisioned_ont) else: logger.info('Deprovisioned (Forced): Serial Number {serial_number} Not found at repository/cache'.format( serial_number=serial_number)) def _get_all_provisioned_onts_from(self, olt): provisioned_onts = [] try: for ont in self.olt_interface.get_provisioned_from_olt(olt): provisioned_onts.append(ont) except Exception as exc: logger.info('Deprovisioned (Forced) getting info of provisioned onts from olt fails: {olt} exc:{exc}'.format(olt=olt, exc=exc)) return provisioned_onts ``` El método que realmente nos ralentiza es `_get_all_provisioned_onts_from_all_olts()`. Este método recorre todas cabeceras disponibles para conocer la ubicación de todos los números de serie de las onts y posteriormente saber donde deprovisionar los números de serie solicitados. Se realizan una serie de operaciones con conjuntos para obtener los elementos a deprovisionar y los que no van a ser posible encontrar, para informar al operador. El código de test: ``` python from mamba import description, context, it, before from expects import expect from doublex import Spy, Stub, when from doublex_expects import have_been_called_with, have_been_called from huawei_olt.huawei_olt_common_tests import object_mother from huawei_olt import provisioners, repositories, olt A_NETWORK = 'a_network' A_SERIAL_NUMBER = 'a_serial_number' ANOTHER_SERIAL_NUMBER = 'another_serial_number' AN_OLT_ID = 'an_olt_id' ANOTHER_OLT_ID = 'another_olt_id' A_CACHED_ONT = 'an_ont' ANOTHER_CACHED_ONT = 'another_ont' with description('Deprovisioner') as self: with before.each: self.ont_repository = Spy(repositories.ONTRepository) self.olt_provisioner = Spy(provisioners.OLTProvisioner) self.olt_interface = Spy(olt.OLTInterface) self.olt_devices_retriever = Stub(olt.OLTDevicesRetriever) self.deprovisioner = provisioners.Deprovisioner(A_NETWORK, self.ont_repository, self.olt_provisioner, self.olt_interface, self.olt_devices_retriever) self.an_olt = object_mother.an_olt(id=AN_OLT_ID) self.a_provisioned_ont = object_mother.an_ont(serial_number=A_SERIAL_NUMBER, olt_id=self.an_olt.id) with context('FEATURE: deprovision all onts in a olt'): with it('does the deprovision all the provisioned onts'): when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([self.a_provisioned_ont]) when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(A_CACHED_ONT) self.deprovisioner.deprovision_all(self.an_olt) expect(self.ont_repository.remove).to(have_been_called_with(A_CACHED_ONT)) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(self.a_provisioned_ont)) with context('FEATURE: deprovision by serial numbers'): with context('when deprovision only one serial number'): with before.each: self.another_olt = object_mother.an_olt(id=ANOTHER_OLT_ID) when(self.olt_devices_retriever).olt_devices().returns([self.an_olt, self.another_olt]) when(self.olt_devices_retriever).find_by_id(AN_OLT_ID).returns(self.an_olt) when(self.olt_devices_retriever).find_by_id(ANOTHER_OLT_ID).returns(self.another_olt) with context('when exist a provisioned ont for that serial number'): with context('search the serial number at the OLT where is provisioned'): with context('when serial number is found at that OLT'): with before.each: when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(self.a_provisioned_ont) when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([self.a_provisioned_ont]) with it('does the deprovision'): self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.ont_repository.remove).to(have_been_called_with(self.a_provisioned_ont)) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(self.a_provisioned_ont)) with it('does NOT continue searching throw the rest of OLTS'): self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.olt_interface.get_provisioned_from_olt).to(have_been_called.once) with context('when serial number is NOT found at that OLT'): with context('search the serial number at other OLTs'): with before.each: when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(self.a_provisioned_ont) with context('when serial number is found at another OLT'): with before.each: when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([]) when(self.olt_interface).get_provisioned_from_olt(self.another_olt).returns( [self.a_provisioned_ont]) with it('does the deprovision'): self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.ont_repository.remove).to(have_been_called_with(self.a_provisioned_ont)) expect(self.olt_provisioner.deprovision_from_olt).to( have_been_called_with(self.a_provisioned_ont)) with it('does NOT continue searching throw the rest of OLTS'): self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.olt_interface.get_provisioned_from_olt).to(have_been_called.twice) with context('when serial number is NOT found at any OLT'): with it('does NOT do the deprovision'): when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([]) when(self.olt_interface).get_provisioned_from_olt(self.another_olt).returns([]) self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.ont_repository.remove).not_to(have_been_called) expect(self.olt_provisioner.deprovision_from_olt).not_to(have_been_called) with context('when does NOT exist a provisioned ont for that serial number'): with context('search the serial number throw all OLTS'): with context('when serial number is found at ont OLT'): with before.each: when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([self.a_provisioned_ont]) when(self.olt_interface).get_provisioned_from_olt(self.another_olt).returns([]) with it('does the deprovision only from OLT'): self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.ont_repository.remove).not_to(have_been_called) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(self.a_provisioned_ont)) with it('does NOT continue searching throw the rest of OLTS'): self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.olt_interface.get_provisioned_from_olt).to(have_been_called.once) with context('when serial number is NOT found at any OLT'): with it('does NOT do the deprovision'): when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([]) when(self.olt_interface).get_provisioned_from_olt(self.another_olt).returns([]) self.deprovisioner.deprovision(A_SERIAL_NUMBER) expect(self.ont_repository.remove).not_to(have_been_called) expect(self.olt_provisioner.deprovision_from_olt).not_to(have_been_called) with context('when deprovision two serial numbers'): with context('when each serial number is provisioned at different olts'): with it('does deprovision each serial numbers'): another_olt = object_mother.an_olt(id=ANOTHER_OLT_ID) another_provisioned_ont = object_mother.an_ont(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID) when(self.olt_devices_retriever).olt_devices().returns([self.an_olt, another_olt]) when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([self.a_provisioned_ont]) when(self.olt_interface).get_provisioned_from_olt(another_olt).returns([another_provisioned_ont]) when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(A_CACHED_ONT) when(self.ont_repository).find_by(ANOTHER_SERIAL_NUMBER).returns(ANOTHER_CACHED_ONT) self.deprovisioner.deprovision(A_SERIAL_NUMBER, ANOTHER_SERIAL_NUMBER) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called.exactly(2)) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(self.a_provisioned_ont)) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(another_provisioned_ont)) expect(self.ont_repository.remove).to(have_been_called.exactly(2)) expect(self.ont_repository.remove).to(have_been_called_with(A_CACHED_ONT)) expect(self.ont_repository.remove).to(have_been_called_with(ANOTHER_CACHED_ONT)) with context('when fails getting information from one olt'): with it('continues deprovisioning next onts'): another_olt = object_mother.an_olt(id=ANOTHER_OLT_ID) another_provisioned_ont = object_mother.an_ont(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID) when(self.olt_devices_retriever).olt_devices().returns([self.an_olt, another_olt]) when(self.olt_interface).get_provisioned_from_olt(self.an_olt).raises(KeyError) when(self.olt_interface).get_provisioned_from_olt(another_olt).returns([another_provisioned_ont]) when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(A_CACHED_ONT) when(self.ont_repository).find_by(ANOTHER_SERIAL_NUMBER).returns(ANOTHER_CACHED_ONT) self.deprovisioner.deprovision(A_SERIAL_NUMBER, ANOTHER_SERIAL_NUMBER) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called.exactly(1)) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(another_provisioned_ont)) expect(self.ont_repository.remove).to(have_been_called.exactly(1)) expect(self.ont_repository.remove).to(have_been_called_with(ANOTHER_CACHED_ONT)) ``` Este es el modo documentation ``` python Deprovisioner when deprovision all onts in a olt ✓ it does the deprovision all the provisioned onts for that olt when deprovision by serial numbers when deprovision only one serial number when serial number is found ✓ it does the deprovision when serial number is not found ✓ it does not do the deprovision when deprovision two serial numbers when each serial number is provisioned at different olts ✓ it does deprovision each serial numbers when fails getting information from one olt ✓ it continues deprovisioning next onts 5 examples ran in 0.0606 seconds ``` En este test describimos lo que queremos testear, pero hablamos por ejemplo de 'when serial number is found' y 'it does the deprovision' o el segundo ejemplo 'when each serial number is provisioned at different olts ' y 'it does deprovision each serial numbers'. Estamos hablando de cosas genéricas como 'deprovisiona' pero también qué sucede 'when serial number is found'. En una tercera versión del script, me llego a preguntar si realmente esto es importante, y en este momento pienso que la respuesta es que no. Ya que estamos contando lo que hace el código demasiado detallado, pero lo que nos interesa es saber qué y no el cómo. ## Intentando testear sólo comportamiento. Forzarme sólo a pensar en comportamiento me ha resultado dificil y me ha llevado varios intentos de tirar código a la basura, y el resultado actual es el siguiente: ``` python def deprovision(self, *serial_numbers): logger.info("Deprovisioned (Forced) begins. Trying to force deprovision {} onts".format(len(serial_numbers))) deprovisioned_total = 0 deprovisioned_serial_numbers = [] serial_numbers_total = len(serial_numbers) for serial_number in serial_numbers: ont = self.ont_repository.find_by(serial_number) olt = self.olt_devices_retriever.find_by_id(ont.olt_id) provisioned_onts = self._cached_provisioned_onts_at(olt) for provisioned_ont in provisioned_onts: if provisioned_ont.serial_number == serial_number: self.olt_provisioner.deprovision_from_olt(provisioned_ont) self.ont_repository.remove(provisioned_ont) deprovisioned_serial_numbers.append(serial_number) deprovisioned_total += 1 logger.info("Deprovisioned (Forced) deprovisioned serial number: {serial_number} [{number}/{total}]".format( serial_number=serial_number, number=deprovisioned_total, total=serial_numbers_total)) serial_numbers = set(serial_numbers) - set(deprovisioned_serial_numbers) deprovisioned_serial_numbers = [] if serial_numbers: for olt in self.olt_devices_retriever.olt_devices(): for provisioned_ont in self._cached_provisioned_onts_at(olt): for serial_number in serial_numbers: if provisioned_ont.serial_number == serial_number: self.olt_provisioner.deprovision_from_olt(provisioned_ont) self.ont_repository.remove(provisioned_ont) deprovisioned_serial_numbers.append(serial_number) deprovisioned_total += 1 # Puede pasar que un serial_number ya haya sido # deprovisionado (efecto raro) logger.info("Deprovisioned (Forced) deprovisioned serial number: {serial_number} [{number}/{total}]".format( serial_number=serial_number, number=deprovisioned_total, total=serial_numbers_total)) continue serial_numbers = set(serial_numbers) - set(deprovisioned_serial_numbers) if serial_numbers: logger.info("Deprovisioned (Forced) Could not deprovision {total} serial numbers {serial_numbers}".format( total=len(serial_numbers), serial_numbers=list(serial_numbers))) logger.info("Deprovisioned (Forced) ends") # TODO: esta cache debería ser reponsabilidad de otro def _cached_provisioned_onts_at(self, olt): self._cache = getattr(self, '_cache', {}) if olt.id in self._cache.keys(): return self._cache[olt.id] provisioned_onts = self.olt_interface.get_provisioned_from_olt(olt) self._cache[olt.id] = provisioned_onts return provisioned_onts ``` El test: ``` python with context('FEATURE: deprovision force many serial numbers'): with context('having onts stored at ont repository (detalle de implementacion ?)'): with before.each: self.an_olt = object_mother.an_olt(id=AN_OLT_ID) self.another_olt = object_mother.an_olt(id=ANOTHER_OLT_ID) self.an_ont = object_mother.an_ont(serial_number=A_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=None, iptv_service_port=None) self.another_ont = object_mother.an_ont(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=None, iptv_service_port=None) self.an_ont_at_olt = object_mother.an_ont(serial_number=A_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT) self.another_ont_at_olt = object_mother.an_ont(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT) with context('that are provisioned at stored olt_id'): with it('deprovisions ont using data from olt'): when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(self.an_ont) when(self.ont_repository).find_by(ANOTHER_SERIAL_NUMBER).returns(self.another_ont) when(self.olt_devices_retriever).find_by_id(AN_OLT_ID).returns(self.an_olt) when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([self.an_ont_at_olt, self.another_ont_at_olt]) self.deprovisioner.deprovision(A_SERIAL_NUMBER, ANOTHER_SERIAL_NUMBER) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(have_properties(serial_number=A_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(have_properties(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) expect(self.ont_repository.remove).to(have_been_called_with(have_properties(serial_number=A_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) expect(self.ont_repository.remove).to(have_been_called_with(have_properties(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=AN_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) with context('that are NOT provisioned at stored olt_id'): with context('when is provisioned in another olt'): with it('deprovisions ont using data from olt'): self.an_ont_at_olt = object_mother.an_ont(serial_number=A_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT) self.another_ont_at_olt = object_mother.an_ont(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT) when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(self.an_ont) when(self.ont_repository).find_by(ANOTHER_SERIAL_NUMBER).returns(self.another_ont) when(self.olt_devices_retriever).find_by_id(AN_OLT_ID).returns(self.an_olt) when(self.olt_devices_retriever).olt_devices().returns([self.an_olt, self.another_olt]) when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([]) when(self.olt_interface).get_provisioned_from_olt(self.another_olt).returns([self.an_ont_at_olt, self.another_ont_at_olt]) self.deprovisioner.deprovision(A_SERIAL_NUMBER, ANOTHER_SERIAL_NUMBER) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(have_properties(serial_number=A_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) expect(self.olt_provisioner.deprovision_from_olt).to(have_been_called_with(have_properties(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) expect(self.ont_repository.remove).to(have_been_called_with(have_properties(serial_number=A_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) expect(self.ont_repository.remove).to(have_been_called_with(have_properties(serial_number=ANOTHER_SERIAL_NUMBER, olt_id=ANOTHER_OLT_ID, igmp_user_index=AN_IGMP_USER_INDEX_FROM_OLT, iptv_service_port=AN_IPTV_SERVICE_PORT_FROM_OLT))) with context('when is not provisioned in any olt'): with it('do not deprovision anything'): when(self.ont_repository).find_by(A_SERIAL_NUMBER).returns(self.an_ont) when(self.ont_repository).find_by(ANOTHER_SERIAL_NUMBER).returns(self.another_ont) when(self.olt_devices_retriever).find_by_id(AN_OLT_ID).returns(self.an_olt) when(self.olt_devices_retriever).olt_devices().returns([self.an_olt, self.another_olt]) when(self.olt_interface).get_provisioned_from_olt(self.an_olt).returns([]) when(self.olt_interface).get_provisioned_from_olt(self.another_olt).returns([]) self.deprovisioner.deprovision(A_SERIAL_NUMBER, ANOTHER_SERIAL_NUMBER) expect(self.olt_provisioner.deprovision_from_olt).not_to(have_been_called) expect(self.ont_repository.remove).not_to(have_been_called) ``` y la salida de mamba ``` python Deprovisioner FEATURE: deprovision all onts in a olt ✓ it does the deprovision all the provisioned onts FEATURE: deprovision force many serial numbers having onts stored at ont repository (detalle de implementacion ?) that are provisioned at stored olt_id ✓ it deprovisions ont using data from olt that are NOT provisioned at stored olt_id when is provisioned in another olt ✓ it deprovisions ont using data from olt when is not provisioned in any olt ✓ it do not deprovision anything ``` ## reflexiones. En una revisión con Apa, me mostró que tenía todo el codigo hecho, pero realmente no estaba testeando lo importante aquí que era deprovisionar con los datos de la OLT en vez de los del repo. Eso me dio una bofetada y me hizo pensar claramente que ese era realmente el comportamiento que yo quería. De esa manera decidí que el como se obtuvieran los datos me daba igual, era un detalle de implementación, que lo realmente importante es deprovisionar con los valores correctos. Más allá la solución de agilizar la consulta necesita un sistema de caché, que debería ser transparente para el deprovisionador, que lo que sí que sabe es que hay un repo de onts y que tiene que deprovosionar en hardware. Por otro lado, el tests soporta reimplementaciones, debería funcionar con otra implementación, con cambios mínimos o ninguno de los dobles. No bifurco tests de si / sino dependiendo de una aserción de doble. Sólo los preparo. Creo que es dificil decidir donde vas a testear implementación y que puede que sea necesario en algún caso. Usar un par de valores en el sut, para forzar a que sea una lista, no sé si tiene valor, ensucia mucho el test y la preparación. Pensar mucho en qué es reponsabilidad de cada uno y pensar en generalizaciones de la clase. ### referencias https://teamgaslight.com/blog/testing-behavior-vs-testing-implementation https://www.barbarianmeetscoding.com/blog/2014/06/15/thoughts-on-unit-testing-and-tdd-test-behavior