Тверді одиничні випробування через необхідність надмірного глузування


21

Я боровся з все більш набридливою проблемою щодо наших тестових підрозділів, які ми впроваджуємо в своїй команді. Ми намагаємось додати одиничні тести до застарілого коду, який не був добре розроблений, і хоча ми не мали жодних труднощів з фактичним додаванням тестів, ми починаємо боротися з тим, як виходять тести.

Як приклад проблеми, скажімо, у вас є метод, який викликає 5 інших методів як частину його виконання. Тестом цього методу може бути підтвердження того, що поведінка виникає в результаті викликання одного з цих 5 інших методів. Отже, оскільки тест одиниці повинен провалюватися лише з однієї причини та однієї причини, ви хочете усунути потенційні проблеми, викликані викликом цих інших 4-х методів, і знущатися над ними. Чудово! Виконання одиничного тесту, знущаються методи ігноруються (і їх поведінку можна підтвердити як частина інших тестів одиниці), і перевірка працює.

Але є нова проблема - блок-тест має інтимні знання про те, як ви підтвердили, що поведінка та будь-яка підпис змінюється на будь-який із цих 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());
}

Як решта з вас впоралися з цим чи не існує чудового "простого" способу поводження з ним?

Оновлення - я ціную відгуки кожного. На жаль, і це не дивно, але, здається, не існує чудового рішення, зразка чи практики, за яким можна перевірити одиничне тестування, якщо код, який тестується, поганий. Я відзначив відповідь, яка найкраще захоплювала цю просту правду.


Нічого собі, я бачу лише налаштування макету, жодних екземплярів SUT чи нічого, ви протестуєте якусь реальну реалізацію тут? Хто повинен дзвонити StopSpinner? OnMerge? Вам слід знущатися над будь-якими залежностями, на які вона може закликати, але не сама річ ..
Joppe

Це трохи важко помітити, але макет <MergeTests> - це SUT. Ми встановлюємо прапор CallBase, щоб переконатися, що метод "OnMerge" виконується на фактичному об'єкті, але знущаємось з методів, викликаних "OnMerge", які могли б спричинити збій тесту через проблеми залежності тощо. Мета тесту - останній рядок - щоб переконатися, що ми зупинили спінер у цьому випадку.
PremiumTier

MergeTests звучить як інший інструментальний клас, а не те, що живе у виробництві, отже, плутанина.
Джоппе


1
Зовсім осторонь інших питань, мені здається, неправильним є те, що ваш SUT - це глузування <MergeTests>. Чому б ви випробували Макет? Чому ви не тестуєте сам клас MergeTests?
Ерік Кінг

Відповіді:


18
  1. Виправте код, щоб він був краще розроблений. Якщо у ваших тестах є ці проблеми, то ваш код матиме ще гірші проблеми, коли ви намагаєтесь змінити речі.

  2. Якщо ви не можете, то, можливо, вам потрібно бути менш ідеальним. Тест на до та після умов методу. Кого хвилює, якщо ви використовуєте інші 5 методів? Вони, ймовірно, мають свої власні одиничні тести, що дає зрозуміти (ер), що спричинило збій, коли тести не вдалися.

"Одиничні тести повинні мати лише одну причину невдачі" - це хороший настанов, але, на мій досвід, непрактичний. Важко написати тести, не пишуть. У тендітні тести не вірять.


Я повністю погоджуюся з виправленням дизайну коду, але в менш ідеальному світі розвитку для великої компанії з чіткими термінами може бути важко зробити випадок «погасити» технічну заборгованість, що виникає за минулі команди або погані рішення один раз. До вашого другого пункту значна частина глузувань не лише тому, що ми хочемо, щоб тест провалився лише з однієї причини - це тому, що виконаний код не може бути дозволений для виконання без попереднього оброблення великої кількості залежностей, створених всередині цього коду . Вибачте за перенесення цільових постів на цю.
PremiumTier

Якщо кращий дизайн не є реалістичним, я погоджуюсь із "Кого хвилює, якщо ви використовуєте інші 5 методів?" Перевірте, чи метод виконує необхідну функцію, а не як це робить.
Kwebble

@Kwebble - Зрозумів, проте метою питання було визначити, чи існує простий спосіб перевірити поведінку методу, коли вам також доведеться знущатися з інших видів поведінки, викликаних у методі, щоб взагалі запустити тест. Я хочу видалити "як", але я не знаю як :)
PremiumTier

Немає чарівної срібної кулі. Немає "простого способу" перевірити поганий код. Або тестований код потребує відновлення, або сам тестовий код також буде поганим. Або тест буде поганим, оскільки він буде надто специфічним для внутрішніх деталей, як ви зіткнулися, або, як це було запропоновано, ви можете запустити тести проти робочого середовища, але тоді тести будуть набагато повільнішими і складнішими. Так чи інакше, тести буде складніше написати, важче підтримувати та схильних до помилкових негативів.
Стівен Доггарт

8

Розбиття великих методів на більш цілеспрямовані малі методи, безумовно, є найкращою практикою. Ви бачите це як біль під час перевірки поведінки тесту, але ви відчуваєте біль і іншими способами.

Однак це єресь, але я особисто прихильник створення реалістичних тимчасових тестових середовищ. Тобто, замість того, щоб знущатися з усього, що приховано в цих інших методах, переконайтеся, що легко встановити тимчасове середовище (у комплекті з приватними базами даних і схемами - тут може допомогти SQLite), яка дозволяє запускати всі ці речі. Відповідальність за те, щоб знати, як створити / зруйнувати тестове середовище, живе з кодом, який вимагає цього, так що коли він змінюється, вам не доведеться змінювати весь тестовий код одиниці, який залежав від його існування.

Але я зазначу, що це єресь з мого боку. Люди, які сильно працюють в одиничному тестуванні, виступають за "чисті" одиничні тести і називають те, що я описав, "інтеграційні тести". Я особисто не турбуюся про це відмінність.


3

Я б розглядав можливість полегшити макети і просто сформулювати тести, які можуть включати методи, на які він закликає.

Не тестуйте як , тестуйте що . Це результат, який має значення, включіть під-методи, якщо це необхідно.

З іншого кута ви могли б сформулювати тест, змусити його пройти одним великим методом, рефактором і в кінцевому підсумку скласти дерево методів після рефакторингу. Не потрібно випробовувати кожного з них окремо. Це важливий результат.

Якщо допоміжні методи ускладнюють тестування деяких аспектів, розглядайте можливість їх розбиття на окремі класи, щоб ви могли знущатися над ними там більш чисто, без того, щоб ваш клас, який перевірявся, був сильно інструментальним / зашиваним. Важко сказати, чи ви насправді тестуєте якусь конкретну реалізацію у своєму прикладі тесту.


Проблема полягає в тому, що ми повинні знущатися над тим, як "перевірити" те, що ". Це обмеження, накладене дизайном коду. Я, звичайно, не хочу «знущатися» над тим, як саме це робить тест крихким.
PremiumTier

Дивлячись на назви методів, я думаю, що ваш тестований клас просто бере на себе занадто багато обов'язків. Прочитайте за принципом єдиної відповідальності. Позики у MVC можуть трохи допомогти, схоже, ваш клас справляється як з інтерфейсом користувача, так і з інфраструктурою та бізнесом.
Джоппе

Так :( Це був би погано розроблений спадковий код, про який я мав на увазі. Ми працюємо над перепроектуванням та рефактором, але ми вважали, що найкраще спочатку поставити джерело на тестування.
PremiumTier
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.