Чи є реальна цінність при тестуванні блоку контролера в ASP.NET MVC?


33

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

Чи є реальна цінність при тестуванні блоку контролера в ASP.NET MVC?

Що я маю на увазі під цим, це те, що більшість часу (і я не геній), мої методи контролера є, навіть у їх найскладнішому, щось подібне:

public ActionResult Create(MyModel model)
{
    // start error list
    var errors = new List<string>();

    // check model state based on data annotations
    if(ModelState.IsValid)
    {
        // call a service method
        if(this._myService.CreateNew(model, Request.UserHostAddress, ref errors))
        {
            // all is well, data is saved, 
            // so tell the user they are brilliant
            return View("_Success");
        }
    }

    // add errors to model state
    errors.ForEach(e => ModelState.AddModelError("", e));

    // return view
    return View(model);
}

Більшість важких підйомів виконується або трубопроводом MVC, або моєю службовою бібліотекою.

Тож, можливо, запитання можуть бути:

  • яке було б значення одиничного тестування цього методу?
  • чи не вдасться вийти з Request.UserHostAddressі ModelStateз NullReferenceException? Чи варто спробувати знущатися над цим?
  • якби я заломлюю цей метод на багаторазовий "помічник" (що, мабуть, мав би, враховуючи, скільки разів я це роблю!), випробував би це навіть варто, коли все, що я справді тестую, - це переважно "трубопровід", який, Імовірно, тестувався Microsoft на відстані дюйма свого життя?

Я думаю, що моє значення справді полягає в тому, що робити наступні дії видається абсолютно безглуздим і неправильним

[TestMethod]
public void Test_Home_Index()
{
    var controller = new HomeController();
    var expected = "Index";
    var actual = ((ViewResult)controller.Index()).ViewName;
    Assert.AreEqual(expected, actual);
}

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

З нетерпінням чекаю цього ... Дякую.


Я вважаю, що ІР (рентабельність інвестицій) на цьому конкретному тесті не вартує зусиль, якщо у вас не є нескінченний час і гроші. Я хотів би написати тести, на які Кевін вказує, щоб перевірити речі, які швидше зламаються, або допоможуть тобі переконструювати щось з упевненістю або забезпечити поширення помилок, як це очікувалося. Випробування трубопроводів, якщо це потрібно, можна проводити на більш глобальному / інфраструктурному рівні та на рівні індивідуальних методів буде мало цінним. Не кажучи, що вони не мають значення, але "мало". Тож якщо це забезпечує хороший РоІ у вашому випадку, займіться цим, інакше спочатку ловіть більшу рибу!
Mrchief

Відповіді:


18

Навіть для чогось такого простого, одиничний тест послужить численним цілям

  1. Впевненість у тому, що написане відповідає очікуваному результату. Перевірити, чи повертає він правильний погляд, може здатися тривіальним, але результат - об'єктивні докази того, що вимогу було виконано
  2. Регресійне тестування. Якщо метод Create потребує змін, у вас все ще є тестовий пристрій для очікуваного виводу. Так, вихід може змінюватися разом, і це призводить до крихкого тесту, але це все ще є перевіркою проти некерованого контролю змін

Для цього конкретного дії я б перевірив наступне

  1. Що станеться, якщо _myService є нульовим?
  2. Що станеться, якщо _myService.Create кидає виняток, чи кидає конкретні для обробки?
  3. Чи повертає успішний _myService.Create повернення _Success?
  4. Чи поширюються помилки до ModelState?

Ви вказали, що перевіряєте Запит та модель для NullReferenceException, і я думаю, що ModelState.IsValid подбає про обробку NullReference для Model.

Знущання із Запити дозволяє захиститись від нульового запиту, що, як правило, неможливо у виробництві, але я думаю, що може статися в одиничному тесті. У тесті інтеграції це дозволить вам надати різні значення UserHostAddress (Запит все ще вводиться користувачем, що стосується контролю, і його слід перевірити відповідно)


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

Колючий. Радий, що вам це допомогло.
Кевін

3

Мої контролери також дуже маленькі. Більшість "логіки" в контролерах обробляються за допомогою атрибутів фільтра (вбудованих і написаних вручну). Тож мій контролер зазвичай займає лише кілька робочих місць:

  • Створюйте моделі з рядків запитів HTTP, значень форми тощо.
  • Виконайте деяку базову перевірку
  • Зателефонуйте до моїх даних або бізнес-рівня
  • Створити a ActionResult

Більшість прив'язок моделі здійснюється автоматично ASP.NET MVC. DataAnnotations також обробляє більшу частину перевірки для мене.

Навіть маючи так мало тестування, я все одно їх зазвичай пишу. В основному я перевіряю, чи викликаються мої сховища та ActionResultповертається правильний тип. У мене є метод зручності ViewResultдля того, щоб переконатися, що правильний шлях перегляду повернуто, і модель перегляду виглядає так, як я його очікую. У мене є ще одна для перевірки правильного контролера / дії встановлено RedirectToActionResult. У мене є інші тести на JsonResultі т.д. і т.д.

Невдалий результат підкласифікації Controllerкласу полягає в тому, що він надає безліч методів зручності, які використовують HttpContextвнутрішньо. Це ускладнює тестування контролера. З цієї причини я, як правило, ставлю HttpContext-залежні дзвінки за інтерфейс і передаю цей інтерфейс конструктору контролера (я використовую веб-розширення Ninject для створення своїх контролерів для мене). Цей інтерфейс, як правило, я вписую властивості помічників для доступу до сеансу, налаштувань конфігурації, помічників IPrinciple та URL.

Для цього потрібна велика ретельність, але я думаю, що вона того варта.


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

Чому методи зручності були б небезпечними? У мене є BaseControllerTestsклас, де вони всі живуть. Я знущаюся зі своїх сховищ. Я підключаю їх за допомогою Ninject.
Travis Parks

Що станеться, якщо ви зробили помилку чи неправильне припущення у своїх помічників? Мій інший пункт полягав у тому, що лише тест на інтеграцію (тобто кінцевий до кінця) міг "перевірити", чи викликаються ваші сховища. Під час тестування одиниць ви все-таки "вновлювали" або знущалися над своїми сховищами вручну.
LiverpoolsNumber9

Ви передаєте сховище конструктору. Ви знущаєтесь над ним під час тесту. Ви переконайтеся, що макет діяв так, як очікувалося. Помічники просто деконструюють ActionResults, щоб перевірити пройдені URL-адреси, моделі тощо
Travis Parks

Добре справедливо - я трохи не зрозумів, що ви мали на увазі під "тестом, що називаються мої сховища".
LiverpoolsNumber9

2

Очевидно, що деякі контролери набагато складніші за це, але засновані виключно на вашому прикладі:

Що станеться, якщо myService кине виняток?

Як бічна записка.

Крім того, я б поставив під сумнів розумність передачі списку за посиланням (це зайве, оскільки c # так чи інакше передається за посиланням, але навіть якщо цього не було) - передача дії errorAction (Action), які сервіс може використовувати для завантаження повідомлень про помилки на який потім можна обробляти, як ви хочете (можливо, ви хочете додати його до списку, можливо ви хочете додати помилку моделі, можливо, ви хочете ввійти до неї).

У вашому прикладі:

замість помилок ref, зробіть, наприклад, (string s) => ModelState.AddModelError ("", s).


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

Послуга буде в окремому стилі. Але в будь-якому випадку, ви, ймовірно, маєте рацію в "ref". З іншого боку, не має значення, якщо myService кидає виняток. Я не тестую свою послугу - я б протестував методи в ній окремо. Я говорю про суто тестування "блоку" ActionResult з (мабуть) глузуванням myService.
LiverpoolsNumber9

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

Ні. Наприкінці дня сервісні методи приймають введення (зазвичай модель перегляду або навіть просто рядки / вставки), вони "роблять речі", а потім повертають bool / помилки, якщо помилкові. Немає «прямого» зв’язку між контролерами та сервісним рівнем. Вони повністю відокремлені.
LiverpoolsNumber9

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