Once you've started working with unit testing, chances are that you never want to work without them again.
I find that the biggest benefit is that refactoring my code gets very easy - by running my unit tests, I can be much more sure of that my code does what I intended, that any changes did not cause any side effects and that I did not reintroduced any regressions. Another important benefit is that you tend to structure your code better by making it testable (e.g. by separating interfaces from implementations).
A potential downside of unit testing is that the unit test themselves take some time to write (and can be quite tedious work), but this is often compensated for in the long run as refactoring the code takes much less time.
Enough about that, let's get starting on some code. This is the class that I thought we should write unit tests for:
As you see in the constructor, we are using dependency injection to inject our dependencies into the class. This way we can inject mock-objects or dummy implementations of those interfaces in our unit tests and thus do not need to test EmailProvider or EmailValidator while testing EmailSender (i.e. the test gets isolated to just test one unit of code).
You can read more about dependency injection and inversion of control here:http://www.codeaddiction.net/articles/10/dependency-injection-in-c---a-simple-introduction
1) In your solution, add a new project of type UnitTestProject
2) Rename the class (or delete the automatically created class and create a new one) called UnitTest1 to EmailSenderTest
3) Make sure that the following using-statement exists at the top of your file:
4) Make sure that your class is decorated with the TestClass-attribute:
5) Create a new method in your EmailSenderTest-class called EmailSender_SendEmail_Successfully, the method should be decorated with the TestMethod-attribute:
6) Open the windows Test Explorer and hit the link Run All
As you will see, your project has 1 unit test - and it passed. Great! The only problem is that we're not testing anything yet...
A mocking framework lets you create dummy instances from interfaces (by using Reflection) and specify how this instance should behave, e.g. we can say that if method X is called, it should return an instance of Y.
Let's grab Moq from the NuGet repository by open the Package Manager Console, selecting the test-project and executing the following command:
You are now able to use Moq in your test project!
7) So in order to test the code we need to reference it. Right click the Reference-item under the test project, and add a reference to the main project and add a using-statement to the namespace of the code that are to be tested.
8) Add a using-statement for Moq:
9) Now to the exciting part - it's time to create your mock objects as well as the instance to your class to test:
It works like this:
* Create the mock objects from the Interfaces: new Mock<IEmailProvider>()
* Make sure to return the expected values if a method on the mock-object is called: emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true)
* Create an instance of our test subject with the instances of our mock-objects: new EmailSender(emailProviderMock.Object, emailValidatorMock.Object)
10) All we have to do now is the actual testing - i.e. call the method that are to be tested and assert that it returns what we're expecting:
As we're expecting everything to go as planned in this scenario, we're giving the method parameters that makes sense, we're returning sensible values from the mock-objects and we're asserting that the method returned a positive return value.
The test-method should now look like this as a whole:
Now go ahead and Run All tests in the Test Explorer and enjoy that green bar of success! :-)
Looking at our EmailSender-class we can see that it is validating the incoming email address:
This means that the method will throw an ArgumentException if the receiverEmailAddress is empty. We can test for this by adding the ExpectedException-attribute to the test method. By doing this, the test will only succeed if the code throws an exception of the specified type. This is how it is works:
As you see, we're providing typeof(ArgumentException) to the ExpectedException-attribute and we're providing an empty string where the receiver email should be.
If you run this test is should also succeed!
Now you should know a bit of unit testing in C#, Microsoft Unit Test Framework and Moq. There are a lot more to learn but this should be enough to get you started writing your own unit tests. Make sure you do - it will make you a better coder.
I find that the biggest benefit is that refactoring my code gets very easy - by running my unit tests, I can be much more sure of that my code does what I intended, that any changes did not cause any side effects and that I did not reintroduced any regressions. Another important benefit is that you tend to structure your code better by making it testable (e.g. by separating interfaces from implementations).
A potential downside of unit testing is that the unit test themselves take some time to write (and can be quite tedious work), but this is often compensated for in the long run as refactoring the code takes much less time.
Enough about that, let's get starting on some code. This is the class that I thought we should write unit tests for:
public class EmailSender : IEmailSender
{
private readonly IEmailProvider _emailProvider;
private readonly IEmailValidator _emailValidator;
public EmailSender(IEmailProvider emailProvider, IEmailValidator emailValidator)
{
_emailProvider = emailProvider;
_emailValidator = emailValidator;
}
public bool SendEmail(string receiverEmailAddress, string subject, string body)
{
if (string.IsNullOrEmpty(receiverEmailAddress))
throw new ArgumentException("You must specify an email address",
nameof(receiverEmailAddress));
if (!_emailValidator.IsEmailValid(receiverEmailAddress))
throw new ArgumentException("You must specify a valid email address",
nameof(receiverEmailAddress));
if (string.IsNullOrEmpty(subject))
throw new ArgumentException("You must specify a email subject",
nameof(subject));
if (string.IsNullOrEmpty(body))
throw new ArgumentException("You must specify a email body ",
nameof(body));
return _emailProvider.SendEmail(receiverEmailAddress, subject, body);
}
}
As you see in the constructor, we are using dependency injection to inject our dependencies into the class. This way we can inject mock-objects or dummy implementations of those interfaces in our unit tests and thus do not need to test EmailProvider or EmailValidator while testing EmailSender (i.e. the test gets isolated to just test one unit of code).
You can read more about dependency injection and inversion of control here:http://www.codeaddiction.net/articles/10/dependency-injection-in-c---a-simple-introduction
A brief introduction to Microsoft Unit Test Framework
There are a lot of unit testing frameworks for C#, but Microsoft ships its own with Visual Studio. It may be a little limited in it's functionality but it gets you started really fast. This is how you get up and running:1) In your solution, add a new project of type UnitTestProject
2) Rename the class (or delete the automatically created class and create a new one) called UnitTest1 to EmailSenderTest
3) Make sure that the following using-statement exists at the top of your file:
using Microsoft.VisualStudio.TestTools.UnitTesting;
4) Make sure that your class is decorated with the TestClass-attribute:
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject1
{
[TestClass]
public class EmailSenderTest
{
5) Create a new method in your EmailSenderTest-class called EmailSender_SendEmail_Successfully, the method should be decorated with the TestMethod-attribute:
[TestMethod]
public void EmailSender_SendEmail_Successfully()
{
//todo...
}
6) Open the windows Test Explorer and hit the link Run All
As you will see, your project has 1 unit test - and it passed. Great! The only problem is that we're not testing anything yet...
A brief introduction to Moq
In order to test the method SendEmail we actually have to create an instance of EmailSender. The problem is that we need to inject instances of IEmailProvider and IEmailValidator in order to do that. This is where you could use a mocking-framework (other solutions exists, e.g. dummy implementations of the interfaces).A mocking framework lets you create dummy instances from interfaces (by using Reflection) and specify how this instance should behave, e.g. we can say that if method X is called, it should return an instance of Y.
Let's grab Moq from the NuGet repository by open the Package Manager Console, selecting the test-project and executing the following command:
Install-Package Moq
You are now able to use Moq in your test project!
7) So in order to test the code we need to reference it. Right click the Reference-item under the test project, and add a reference to the main project and add a using-statement to the namespace of the code that are to be tested.
8) Add a using-statement for Moq:
using Moq;
9) Now to the exciting part - it's time to create your mock objects as well as the instance to your class to test:
[TestMethod]
public void EmailSender_SendEmail_Successfully()
{
//Arrange
var emailProviderMock = new Mock<IEmailProvider>();
var emailValidatorMock = new Mock<IEmailValidator>();
emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true);
emailProviderMock.Setup(m => m.SendEmail(It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>())).Returns(true);
var emailSender = new EmailSender(emailProviderMock.Object, emailValidatorMock.Object);
//Act
//todo...
//Assert
//todo...
}
It works like this:
* Create the mock objects from the Interfaces: new Mock<IEmailProvider>()
* Make sure to return the expected values if a method on the mock-object is called: emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true)
* Create an instance of our test subject with the instances of our mock-objects: new EmailSender(emailProviderMock.Object, emailValidatorMock.Object)
10) All we have to do now is the actual testing - i.e. call the method that are to be tested and assert that it returns what we're expecting:
//Act
var returnValue = emailSender.SendEmail("foo@example.com", "Welcome to our service", "Hi Foo,\n\nWelcome to service Bar!");
//Assert
Assert.IsTrue(returnValue);
As we're expecting everything to go as planned in this scenario, we're giving the method parameters that makes sense, we're returning sensible values from the mock-objects and we're asserting that the method returned a positive return value.
The test-method should now look like this as a whole:
[TestMethod]
public void EmailSender_SendEmail_Successfully()
{
//Arrange
var emailProviderMock = new Mock<IEmailProvider>();
var emailValidatorMock = new Mock<IEmailValidator>();
emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true);
emailProviderMock.Setup(m => m.SendEmail(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);
var emailSender = new EmailSender(emailProviderMock.Object, emailValidatorMock.Object);
//Act
var returnValue = emailSender.SendEmail("foo@example.com", "Welcome to our service", "Hi Foo,\n\nWelcome to service Bar!");
//Assert
Assert.IsTrue(returnValue);
}
Now go ahead and Run All tests in the Test Explorer and enjoy that green bar of success! :-)
Expecting problems
But what if everything doesn't go as planned. What if the caller didn't provide an email address? We'll we have to test for that too!Looking at our EmailSender-class we can see that it is validating the incoming email address:
if (string.IsNullOrEmpty(receiverEmailAddress))
throw new ArgumentException("You must specify an email address",
nameof(receiverEmailAddress));
This means that the method will throw an ArgumentException if the receiverEmailAddress is empty. We can test for this by adding the ExpectedException-attribute to the test method. By doing this, the test will only succeed if the code throws an exception of the specified type. This is how it is works:
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void EmailSender_SendEmail_DidNotProvideEmailThrowsArgumentException()
{
//Arrange
var emailProviderMock = new Mock<IEmailProvider>();
var emailValidatorMock = new Mock<IEmailValidator>();
emailValidatorMock.Setup(m => m.IsEmailValid(It.IsAny<string>())).Returns(true);
emailProviderMock.Setup(m => m.SendEmail(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);
var emailSender = new EmailSender(emailProviderMock.Object, emailValidatorMock.Object);
//Act
var returnValue = emailSender.SendEmail("", "Welcome to our service",
"Hi Foo,\n\nWelcome to service Bar!");
}
As you see, we're providing typeof(ArgumentException) to the ExpectedException-attribute and we're providing an empty string where the receiver email should be.
If you run this test is should also succeed!
Now you should know a bit of unit testing in C#, Microsoft Unit Test Framework and Moq. There are a lot more to learn but this should be enough to get you started writing your own unit tests. Make sure you do - it will make you a better coder.
0 коммент.:
Post a Comment