6
\$\begingroup\$

I'm starting to dive into TDD with NUnit and despite I've enjoyed checking some resources I've found here at stackoverflow, I often find myself not gaining good traction.

So what I'm really trying to achieve is to acquire some sort of checklist/workflow —and here's where I need you guys to help me out— or "Test Plan" that will give me decent Code Coverage.

So let's assume an ideal scenario where we could start a project from scratch with let's say a Mailer helper class that would have the following code:

(I've created the class just for the sake of aiding the question with a code sample so any criticism or advice is encouraged and will be very welcome)

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 attach;
        public string Attach { get { return Attach; } }

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

        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;

    namespace Dotnet.Samples.NUnit
    {
        [TestFixture]
        public class MailerTests
        {
            [Test]
            public void SendMail_FromArgumentIsNotNull_ReturnsTrue()
            {
                // Arrange
                string argument = null;
                // Act
                Mailer mailer = new Mailer(from: argument);
                // Assert
                Assert.IsNotNull(mailer.From, "parameter 'from' cannot be null.");
            }

            [Test]
            public void SendMail_FromArgumentIsNotEmpty_ReturnsTrue()
            {
                // Arrange
                string argument = String.Empty;
                // Act
                Mailer mailer = new Mailer(from: argument);
                // Assert
                Assert.IsNotEmpty(mailer.From, "parameter 'from' cannot be empty.");
            }

            [Test]
            public void SendMail_FromArgumentIsNullOrEmpty_ThrowsException()
            {
                // Arrange
                dynamic argument = null;
                // Act
                Mailer mailer = new Mailer(from: argument);
                Action act = () => mailer.SendMail();
                // Assert
                Assert.Throws<ArgumentNullException>(new TestDelegate(act));
            }
        }
    }

So after having my first 2 failing tests the next obvious step would be implementing the functionality to make them pass, but, should I keep the failing tests and create new ones after implementing the code that will make those pass, or should I modify the existing ones after making them pass?

Any advice about this topic will really be enormously appreciated.

\$\endgroup\$
4
\$\begingroup\$

It is important to get one's mindset in a TDD mode beforehand. What I mean by this is

1) Identify the responsibility of a class - this helps in identifying the test cases. 2) Identify the interactions of the class with external dependencies - Unit testing shouldn't be testing the behaviour of the external dependency.

In this case - you have written a wrapper class around SendMail whose responsibility is to "Collect and Despatch".

I'd say SmtpClient is a dependency - you should be able to Mock it's behaviour in your test.

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; } }

        private SmtpClient smtpClient;

        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, SmtpClient smtpClient= new SmtpClient())
        {
            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);

            MailMessage mail = new MailMessage();
            smtpClient.Send(mail);
        }
    }
}

Testing SendMail

[Test]
public voud ShouldCallSendOfSmtpClientWhenSendMailIsCalled
{
    //Stub & Setup
    var mockSmtpClient = MockRepository.GenerateMock<SmtpClient>();
    var mailer = new Mailer(smtpClient: mockSmtpClient);

    //Act
    var message = new MailMessage();
    mailer.SendMail(message);

    //Assert
    mockSmtpClient.AssertWasCalled(m=>m.Send(Arg<MailMessage>.Is.Anything));
}
\$\endgroup\$
  • \$\begingroup\$ Thanks a lot for your comment! It's been definitely informative. \$\endgroup\$ – Nano Taboada Jun 1 '11 at 20:47
3
\$\begingroup\$

sometimes you make new ones, sometimes you extend the current test you are working on. There's no real rules. You are trying to keep your tests well factored like your code. So sometimes you DELETE a test also.

Just treat your test code like production code. Remove duplication. Keep it simple. Basically, with unit tests, you want as few tests as possible.

that doesn't mean you don't test something, but reduce your tests down to them minimum that will validate the units function. Too often unit tests get large and messy and simple changes in the code cause lots of tests to break. So don't be afraid to refactor your tests. Also keep your tests at the right level of abstraction, meaning, don't dig into the details of your code and test the internal workings.

Looking at your code, you have started with argument validation, this is not a good way to start. You want to start with something significant..... add validation of parameters later once you understand the nature of your objects.

Also, dont be afraid of multiple test fixtures to do your arrange... (even though the code is kind of testing the wrong thing)

namespace Dotnet.Samples.NUnit
{
    [TestFixture]
    public class NullFromAddressFixture
    {
        private Mailer mailer;

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

        }

        [Test]
        public void SendMail_FromArgumentIsNotNullOrEmpty_ReturnsTrue()
        {
            Assert.IsNotNullOrEmpty(mailer.From, "Parameter cannot be null or empty.");
        }

        [Test]
        public void SendMail_FromArgumentIsNullOrEmpty_ThrowsException()
        {
            // Act
            Action act = () => mailer.SendMail();
            act.ShouldThrow<ArgumentNullException>();

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

    [TestFixture]
    public class EmptyFromAddressFixture
    {
        [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 just 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.
    }
}
\$\endgroup\$
3
\$\begingroup\$

To answer your question directly, you don't typically change a test once it passes, unless it breaks (you'll often need to modify the setup for a given class as new requirements are added, but this can be addressed by refactoring your test classes and using inheritance). The point is to have a set of tests that verify the expected outcome, so unless the expected outcome changes, you shouldn't change your tests. And your tests should start out by testing for the expected outcome. You don't normally write 1 condition, make it pass, then add more conditions to the same test; you either start out with multiple conditions you're testing for, or write additional tests.

To use a simplified example to make sure I correctly understand what you're asking, if you were testing a function Add(int arg1, int arg2), and you wrote 2 tests:

[Test]
public void Add_2_plus_2_is_4()
{
    Assert.AreEqual(4, Add(2, 2));
}

[Test]
public void Add_Throws_Argument_Exception_For_Negative_Number()
{
    Assert.Throws<ArgumentException>(Add(2, -1));
}

Once your tests pass, you would leave them in place when you go on to write your unit tests for the Subtract(int arg1, int arg2) method. You wouldn't change the 2 test you just wrote to instead validate your Subtract method. Is that what you were asking?

\$\endgroup\$
  • \$\begingroup\$ Thank you! Your contribution is definitely helpful, I'll start following your advice and I'd quite probably update the sample code. Thanks again. \$\endgroup\$ – Nano Taboada Jun 2 '11 at 23:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.