Я боролся со все более раздражающей проблемой, касающейся наших модульных тестов, которые мы внедряем в моей команде. Мы пытаемся добавить модульные тесты в унаследованный код, который не был хорошо разработан, и, хотя у нас не было никаких проблем с фактическим добавлением тестов, мы начинаем бороться с тем, как тесты получаются.
В качестве примера проблемы, скажем, у вас есть метод, который вызывает 5 других методов как часть его выполнения. Тест для этого метода может заключаться в подтверждении того, что поведение происходит в результате вызова одного из этих 5 других методов. Таким образом, поскольку модульное тестирование должно завершиться неудачей по одной и только по одной причине, вы хотите устранить потенциальные проблемы, вызванные вызовом этих четырех других методов, и смоделировать их. Большой! Выполняется модульный тест, проверенные методы игнорируются (и их поведение может быть подтверждено как часть других модульных тестов), и проверка работает.
Но есть новая проблема - модульный тест обладает глубокими знаниями о том, как вы подтвердили, что поведение и любые изменения сигнатур любого из этих 4 других методов в будущем или любых новых методов, которые необходимо добавить в «родительский метод», будут привести к необходимости изменения модульного теста, чтобы избежать возможных сбоев.
Естественно, проблему можно несколько смягчить, просто добавив больше методов для выполнения меньшего количества действий, но я надеялся, что, возможно, будет доступно более элегантное решение.
Вот пример модульного теста, который фиксирует проблему.
В качестве краткого замечания «MergeTests» - это класс модульного тестирования, который наследует от класса, который мы тестируем, и при необходимости переопределяет поведение. Это «шаблон», который мы используем в наших тестах, чтобы позволить нам переопределять вызовы внешних классов / зависимостей.
[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());
}
Как остальные из вас справились с этим, или нет великолепного «простого» способа справиться с этим?
Обновление - я ценю все отзывы. К сожалению, и это не удивительно, на самом деле не существует отличного решения, шаблона или практики, которой можно следовать при модульном тестировании, если тестируемый код плохой. Я отметил ответ, который лучше всего уловил эту простую истину.