# 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