He estado luchando con un problema cada vez más molesto con respecto a nuestras pruebas unitarias que estamos implementando en mi equipo. Estamos intentando agregar pruebas unitarias en el código heredado que no fue bien diseñado y, aunque no hemos tenido ninguna dificultad con la adición real de las pruebas, estamos empezando a tener dificultades con la forma en que se están desarrollando las pruebas.
Como ejemplo del problema, supongamos que tiene un método que llama a otros 5 métodos como parte de su ejecución. Una prueba para este método podría ser confirmar que se produce un comportamiento como resultado de uno de estos otros 5 métodos llamados. Entonces, debido a que una prueba unitaria debe fallar por una razón y solo por una, desea eliminar los problemas potenciales causados al llamar a estos otros 4 métodos y simularlos. ¡Excelente! La prueba unitaria se ejecuta, los métodos simulados se ignoran (y su comportamiento puede confirmarse como parte de otras pruebas unitarias) y la verificación funciona.
Pero hay un nuevo problema: la prueba unitaria tiene un conocimiento íntimo de cómo confirmó que el comportamiento y cualquier cambio de firma a cualquiera de esos otros 4 métodos en el futuro, o cualquier método nuevo que deba agregarse al 'método principal', resultar en tener que cambiar la prueba de la unidad para evitar posibles fallas.
Naturalmente, el problema podría mitigarse un poco simplemente haciendo que más métodos logren menos comportamientos, pero esperaba que tal vez hubiera una solución más elegante disponible.
Aquí hay una prueba de unidad de ejemplo que captura el problema.
Como nota rápida, 'MergeTests' es una clase de prueba unitaria que hereda de la clase que estamos probando y anula el comportamiento según sea necesario. Este es un "patrón" que empleamos en nuestras pruebas para permitirnos anular llamadas a clases / dependencias externas.
[TestMethod]
public void VerifyMergeStopsSpinner()
{
var mockViewModel = new Mock<MergeTests> { CallBase = true };
var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());
mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
mockViewModel.Setup(
m =>
m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
It.IsAny<bool>()));
mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
mockViewModel.Setup(m => m.SwitchToOverviewTab());
mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));
mockViewModel.Object.OnMerge(It.IsAny<MergeState>());
mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}
¿Cómo han lidiado con esto el resto de ustedes o no hay una gran forma 'simple' de manejarlo?
Actualización: agradezco los comentarios de todos. Desafortunadamente, y no es una sorpresa realmente, no parece haber una gran solución, patrón o práctica que uno pueda seguir en las pruebas unitarias si el código que se prueba es deficiente. Marqué la respuesta que mejor capturó esta simple verdad.