Test Driven Development (TDD) in MVC Application
In this article we are going to focus on how to perform Test Driven Development (TDD) in MVC 4 Applications. We are going to look at following topics:
1. About MVC4 TDD
2. What is Test-Driven development ?
3. Why Test-Driven development
4. How to perform Test-Driven development
In this article we are going to focus on how to perform Test Driven Development (TDD) in MVC 4 Applications. We are going to look at following topics:
1. About MVC4 TDD
2. What is Test-Driven development ?
3. Why Test-Driven development
4. How to perform Test-Driven development
About MVC4 Test-Driven development
1. TDD works best when the framework supports them.
2. The ASP.NET MVC is designed to make it as easy as possible to use Test-Driven development.
3. Writing a unit test before we write the code helps us think about how different kinds of outcomes should be expressed before we write the implementation.
What is Test-Driven development
With TDD, we use unit tests which describe the desired behavior as per the specification for our functionality to help design our code. Generally we are used to write the Tests after we have finished coding, but this is a different concept where in we write the unit tests before we start implementing the code.
Why Test-Driven development
1. To verify the correctness of the Application by executing a set of specifications against the implementation.
2. To verify the stability of the Application.
3. It is recommended as a development style because it makes programmer think about how a change or enhancement should behave before the coding actually starts.
How to perform Test-Driven development
1.First we will decide that we need to add a new method or a feature to our application.
2. Then we will write the test that will validate the behavior of the new feature when we complete writing it.
3. Run the test to get wrong behavior
4. Write the code that implements the new feature.
5. Run the test again and correct the code until we get the correct behavior of our implementation.
6. Update the code if required such as by reorganizing the statements, renaming the variables, modifying the statements and so on.
7. Run the test to confirm that your changes have not changed the behavior of your additions.
8. Repeat the above steps, for every new functionality you add.
Test-Driven development Example
Let us write a functionality to divide a number –a by another number - b, only if the other number b is positive.
1. Let us add a stub method to the Test class as shown below:
public class Test
{
public void PerformDivision(int number)
{
throw new NotImplementedException();
}
}
2.As we can clearly observe that the above PerformDivision method does not perform the required behavior.
3. The key to TDD is to test for the right behavior before implementing the feature.
4. We are going to test for 3 different aspects of the behavior we are planning to implement.
a. When the number is negative, we should not be able to perform division.
b. When the number is positive, we can perform division.
c. When the number is 0, division cannot be performed and it should throw Divide By Zero exception
To do this we will create 3 test methods for each of the behaviors that we want to see. To create Unit Tests for our application we can add a Unit Test project to our solution as shown below
We will write the Tests that follow the arrange/act/assert pattern to create, test and validate one aspect of the overall behavior that is each Unit Test will be responsible for testing one aspect of the functionality.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void CanPerformDivisionByPositiveNumber()
{
//Arrange - Set up the scenario
int number = 5;
// Act - perform the test
Test t = new Test();
int result = t.PerformDivision(number);
//Assert - Check the Behaviour
Assert.AreEqual(4, result);
}
[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void CannotPerformDivisionByZero()
{
//Arrange
int number = 0;
//Act
Test test = new Test();
int result = test.PerformDivision(number);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CannotPerformDivisionByNegativeNumber()
{
//Arrange - Set up the scenario
int number = -5;
// Act - perform the test
Test t = new Test();
int result = t.PerformDivision(number);
}
}
Please note that the CannotPerformDivisionByZero and CannotPerformDivisionByNegativeNumber methods does not have an assert part in the method body because a successfully run test is an exception being thrown, which we assert by applying the ExpectedException attribute on the test method.
We validate the test result by ensuring that an exception is thrown and that it is an instance of type System.DivideByZeroException and InvalidOperationException for the methods CannotPerformDivisionByZero and CannotPerformDivisionByNegativeNumber respectively.
Right-Click the UnitTest class and choose Run Tests. Now run the Unit Test for the first time to test the PerformDivision method. As we expected, all of these tests will fail when we run them.
Now let us add initial implementation of the PerformDivision method to the Test class.
public int PerformDivision(int number)
{
int numerator = 20;
int result = numerator / number;
return result;
}
Running the UnitTests again, We see that, Two of the three unit tests have passed.
CannotPerformDivisionByNegativeNumber method fails because we did not add any checks to make sure that we cannot perform division with a negative number. We have to modify our implementation to put this logic in code.
public class Test
{
public int PerformDivision(int number)
{
int numerator = 20;
int result;
if (number < 0)
throw new InvalidOperationException("Number cannot be negative");
else
result = numerator / number;
return result;
}
}
As We can see that we have expressed the error condition in such a way as to satisfy the unit test we wrote before we started coding — that is, we throw an InvalidOperationException when a division is performed with a Negative number .
Each time we change the implementation of the PerformDivision method, we run our unit tests again to make sure that the changes did not break our code.
? We have implemented our new feature such that it passes all of the unit tests.
? We have to make sure that our tests does test all aspects of the behavior or feature we are implementing. If any of the aspects of the expected behavior is failing, then we need to add more tests and repeat the cycle — and we keep repeating it until we are confident that we have a set of tests and an implementation that passes all the tests.