Рекомендации по работе с TDD для .NET с использованием NUnit - PullRequest
4 голосов
/ 08 апреля 2011

ОБНОВЛЕНИЕ: Я внес серьезные изменения в этот пост - подробности смотрите в истории изменений.

Я начинаю погружаться в TDD с NUnit и, несмотря на то, что мне понравилось проверять некоторые ресурсы, которые я нашел здесь в stackoverflow, я часто не получаю хорошую тягу.

Так что я действительно пытаюсь добиться, это приобрести какой-то видконтрольного списка / рабочего процесса - и вот где вы, ребята, должны помочь мне - или « План тестирования », который даст мне достойное покрытие кода.

Итак, давайте предположим идеальный сценарий, где мыможет начать проект с нуля, скажем, вспомогательный класс Mailer, который будет иметь следующий код:

(я создал класс просто для того, чтобы помочь с вопросом с примером кода, поэтому любая критика или советприветствуется и будет приветствоваться)

Mailer.cs

using System.Net.Mail;
using System;

namespace Dotnet.Samples.NUnit
{
    public class Mailer
    {
        readonly string from;
        public string From { get { return from; } }

        readonly string to;
        public string To { get { return to; } }

        readonly string subject;
        public string Subject { get { return subject; } }

        readonly string cc;
        public string Cc { get { return cc; } }

        readonly string bcc;
        public string BCc { get { return bcc; } }

        readonly string body;
        public string Body { get { return body; } }

        readonly string smtpHost;
        public string SmtpHost { get { return smtpHost; } }

        readonly string attachment;
        public string Attachment { get { return Attachment; } }

        public Mailer(string from = null, string to = null, string body = null, string subject = null, string cc = null, string bcc = null, string smtpHost = "localhost", string attachment = null)
        {
            this.from = from;
            this.to = to;
            this.subject = subject;
            this.body = body;
            this.cc = cc;
            this.bcc = bcc;
            this.smtpHost = smtpHost;
            this.attachment = attachment;
        }

        public void SendMail()
        {
            if (string.IsNullOrEmpty(From))
                throw new ArgumentNullException("Sender e-mail address cannot be null or empty.", from);

            SmtpClient smtp = new SmtpClient();
            MailMessage mail = new MailMessage();
            smtp.Send(mail);
        }
    }
}

MailerTests.cs

using System;
using NUnit.Framework;
using FluentAssertions;

namespace Dotnet.Samples.NUnit
{
    [TestFixture]
    public class MailerTests
    {
        [Test, Ignore("No longer needed as the required code to pass has been already implemented.")]
        public void SendMail_FromArgumentIsNotNullOrEmpty_ReturnsTrue()
        {
            // Arrange
            dynamic argument = null;

            // Act
            Mailer mailer = new Mailer(from: argument);

            // Assert
            Assert.IsNotNullOrEmpty(mailer.From, "Parameter cannot be null or empty.");
        }

        [Test]
        public void SendMail_FromArgumentIsNullOrEmpty_ThrowsException()
        {
            // Arrange
            dynamic argument = null;
            Mailer mailer = new Mailer(from: argument);

            // Act
            Action act = () => mailer.SendMail();
            act.ShouldThrow<ArgumentNullException>();

            // Assert
            Assert.Throws<ArgumentNullException>(new TestDelegate(act));
        }

        [Test]
        public void SendMail_FromArgumentIsOfTypeString_ReturnsTrue()
        {
            // Arrange
            dynamic argument = String.Empty;

            // Act
            Mailer mailer = new Mailer(from: argument);

            // Assert
            mailer.From.Should().Be(argument, "Parameter should be of type string.");
        }

        // INFO: At this first 'iteration' I've almost covered the first argument of the method so logically this sample is nowhere near completed.
        // TODO: Create a test that will eventually require the implementation of a method to validate a well-formed email address.
        // TODO: Create as much tests as needed to give the remaining parameters good code coverage.
    }
}

Итак, после моих первых 2 провальных тестов следующийочевидным шагом будет реализация функциональности для их прохождения, но если я оставлю неудачных тестов и создам новые после реализации кода, который сделает эти прохождения, или я модифицирую существующие после их прохождения?

Любой совет по этой теме будет очень ценен.

Ответы [ 4 ]

3 голосов
/ 08 апреля 2011

Если вы установите TestDriven.net , один из компонентов (называемый NCover) фактически поможет вам понять, какая часть вашего кода покрыта модульным тестом.

За исключением этого, лучшим решением является проверка каждой строки и выполнение каждого теста, чтобы убедиться, что вы хотя бы один раз нажали эту строку.

1 голос
/ 08 апреля 2011

Когда люди (наконец-то!) Решают применить тестовое покрытие к существующей кодовой базе, нецелесообразно все тестировать; у вас нет ресурсов, и зачастую не так много реальной ценности.

В идеале вы должны убедиться, что ваши тесты применимы ко вновь написанному / измененному коду и ко всему, на что могут повлиять эти изменения .

Для этого нужно знать:

  • какой код вы изменили. Ваша система управления исходным кодом поможет вам здесь на уровне изменения этого файла.

  • какой код выполняется в результате выполнения нового кода. Для этого вам понадобится либо статический анализатор, который может отслеживать последующее воздействие кода (не знаю многих из них), либо инструмент покрытия тестов, который может показать, что было выполнено при запуске ваших конкретных тестов. Любой такой исполняемый код, вероятно, тоже нуждается в повторном тестировании.

Поскольку вы хотите свести к минимуму объем тестового кода, который вы пишете, вы явно хотите лучше, чем гранулярность с точностью до файла «изменено». Вы можете использовать инструмент сравнения (часто встроенный в вашу систему управления исходным кодом), чтобы отрегулировать фокусировку на определенных линиях. Инструменты Diff на самом деле не понимают структуру кода, поэтому то, что они сообщают, имеет тенденцию быть ориентированным на строки, а не на структуру, производя гораздо большие различия, чем необходимо; и при этом они не сообщают вам удобную точку доступа к тестированию, которая, вероятно, будет методом , потому что весь стиль модульного тестирования сфокусирован на методах тестирования.

Вы можете получить лучшие инструменты сравнения. Наши Smart Differencer инструменты предоставляют различия в терминах программных структур (выражений, операторов, методов) и абстрактных операций редактирования (вставка, удаление, копирование, перемещение, замена, переименование), которые облегчают интерпретацию кода. изменения. Это напрямую не решает "какой метод изменился?" вопрос, но это часто означает, что нужно принимать во внимание гораздо меньше вещей, чтобы принять это решение.

Вы можете получить инструменты тестирования покрытия, которые ответят на этот вопрос. У наших инструментов Test Coverage есть возможность сравнить предыдущие прогоны тестового покрытия с текущими тестовыми покрытиями, чтобы определить, какие тесты необходимо повторить. Они делают это путем изучения различий в коде (что-то вроде Smart Differencer), но абстрагируют изменения до уровня метода.

1 голос
/ 08 апреля 2011

Я бы посоветовал вам подобрать какой-нибудь инструмент, например NCover , который может подключаться к вашим тестам для получения статистики покрытия кода.Существует также общественная версия NCover, если вам не нужна лицензионная версия.

1 голос
/ 08 апреля 2011

Если вы используете фреймворк, такой как NUnit, есть доступные методы, такие как AssertThrows, где вы можете утверждать, что метод генерирует требуемое исключение при вводе: http://www.nunit.org/index.php?p=assertThrows&r=2.5

По сути, проверяется ожидаемое поведениехороший и плохой вход - лучшее место для начала.

...