Si desea _unitar_ probar un algoritmo aleatorio, necesita implementarlo de tal manera que pueda especificar cuáles serán los valores “aleatorios” para cualquier uso dado. La mejor manera de hacerlo varía según el paradigma del lenguaje en el que esté implementando su algoritmo, pero el tema general es implementar el algoritmo de tal manera que se pueda anular la fuente de valores aleatorios.
Para los idiomas OOP de tipo estático:
- Nunca llame a un método estático en el cuerpo del algoritmo, analice el uso de:
- Inyección de dependencia: su objeto tiene una referencia al objeto que proporciona los valores aleatorios, de esta manera puede crear un objeto auxiliar que devuelve valores especificados (también considere usar bibliotecas simuladas), o
- Delegación: su algoritmo llama a otro método en su objeto para obtener el valor aleatorio, de esta manera en sus pruebas puede heredar y anular este método a un valor para el cual conoce el valor de retorno del algoritmo cuando se utiliza este valor “aleatorio”
Para los lenguajes de tipo dinámico (donde las clases, las funciones y los módulos son objetos de primera clase), tiene los enfoques anteriores para usar, además de:
- ¿Por qué la pérdida logística es una mejor métrica para la clasificación probabilística que RMSD?
- Cómo tratar las variables categóricas al analizar los datos de la encuesta para crear una clasificación
- Diseño de vectores de características para algoritmos de aprendizaje automático. ¿Debo poner características de diferentes dominios en el mismo vector?
- ¿Qué modelo da un error de predicción más bajo cuando se usa R?
- ¿Cuáles son algunos buenos ejemplos donde el agrupamiento se usa como método para la extracción de características?
- Monkey parchea la clase / módulo que proporciona los valores aleatorios para devolver los valores que desea. Tenga en cuenta que siempre debe deshacer todos los parches o podría comenzar a afectar otras pruebas (aunque muchas bibliotecas burlonas lo harán automáticamente. Los métodos estáticos son menos problemáticos en los lenguajes dinámicos, ya que puede cambiar lo que existe en el espacio de nombres en el fly, donde al igual que con los lenguajes estáticos no hay forma de cambiar “x = 4 + random.uniform ()” para no llamar al método real random.uniform ().
Tenga en cuenta que esta sería la misma respuesta si la pregunta hubiera sido “¿cómo puedo unir el código de prueba que utiliza una conexión de base de datos?”. Respuesta: abstraiga las dependencias para que pueda modificarlas.
Ahora, algunos recomendarían que simplemente establezca la semilla aleatoria, ya que esto proporcionará un conjunto determinista de valores de un conjunto de funciones aleatorias. Sin embargo, esto conducirá a pruebas frágiles:
- La prueba no proporciona explícitamente todas sus entradas, ya que algunas de ellas están envueltas en los métodos aleatorios sembrados. Puede que no esté del todo claro cómo falla una prueba, o peor aún, por qué pasa (cuando no es posible).
- Todas sus pruebas en consumidores de valores aleatorios dependerán del orden, ya que la prueba ejecutada x necesita recibir el valor y del método aleatorio, y si se cambia el orden de la prueba, otra prueba recibirá el valor y. Esto tiene el efecto secundario indeseable de que las personas se volverán reacias a agregar más pruebas, ya que corre el riesgo de cambiar el orden de ejecución de la prueba, lo que provocará que las pruebas fallen por razones que no son evidentes.
Finalmente, hay dos enfoques para las pruebas funcionales de una aplicación estocástica que uso, y recomendaría que considere usar ambos si es posible.
El primero es elegir un conjunto de entradas, así como una semilla aleatoria predefinida y registrar las salidas. Escriba una prueba que ejecute la funcionalidad en cuestión y verifique que los resultados sean los resultados esperados. Es posible que haya notado que acabo de recomendarle que escriba una prueba de una manera que previamente recomendé evitar, sin embargo, la diferencia aquí es que las pruebas funcionales no sufren de la misma manera el enfoque de semillas aleatorias que las pruebas unitarias.
Las pruebas funcionales son casi siempre frágiles y difíciles de depurar, incluso sin aleatoriedad en la mezcla. Además, por definición, tendrá menos pruebas funcionales que pruebas unitarias, ya que tendrá menos ‘funcionalidad’ que las ‘unidades’ que lo implementan (y si esto no es cierto, tiene que refactorizar mucho) . Respaldar pruebas frágiles de uno o dos dígitos es mucho más fácil que soportar cientos o miles de pruebas unitarias frágiles. También debe tener cuidado con el pedido, pero puede resolverlo ejecutando las pruebas individualmente (en sus propios procesos). Esto requeriría mucho tiempo para un gran volumen de pruebas, pero nuevamente, no tendrá un alto volumen de pruebas funcionales.
La segunda opción es mucho más complicada de implementar correctamente (y propensa a errores) pero le brinda una mejor seguridad de que la aleatorización real funciona como lo espera. En resumen, usted define la distribución de resultados que espera para una entrada o entradas explícitas, llama a la funcionalidad con estas entradas repetidamente por un número predefinido de veces que retiene todos los resultados y finalmente verifica que todos los resultados ‘ajustar’ la distribución esperada dentro de cierta tolerancia. Esto es complicado, ya que debe tener una comprensión muy profunda de sus algoritmos a un alto nivel para determinar una distribución precisa de los resultados, y calcular un recuento de iteraciones y tolerancia que no es excesivamente excesivo pero que aún proporciona resultados significativos También puede resultar difícil. Tendrá fallas periódicas debido al conjunto patológico ocasional de valores aleatorios (su frecuencia dependerá de la elección de las iteraciones y la tolerancia), y probablemente también desee implementar algún tipo de contabilidad para que cuando fallan pueda ver qué entradas causaron la distribución inesperada de resultados. Sin embargo, a pesar de todos estos inconvenientes, este estilo de pruebas es la única forma de ejercer explícitamente el (los) algoritmo (s) en cuestión y demostrar que funcionan como se espera en una situación del “mundo real”.