Real World TDD

I was first introduced to unit tests as a Junior .NET dev back in 2007. The lead dev I was working with was very talented with regards to dev skills, but talentless when it came to management and people skills. He was also very arrogant and not good to work with. I am who I am today because of him. I’m very approachable, give praise to work well done and ensure arrogance is not tolerated in my dev team. Ill create an article in the near future to discuss what I believe from experience works well for dev teams but for now were talking about Unit tests and TDD (Test Driven Development).

Due to his nature, he didn’t explain why unit tests were beneficial and was very strict that they needed to be written first before any code. To a Junior dev this was very hard to understand, but I did see the benefit of having an isolated testing environment for code separate to your project.

Fast forward nearly 10 years, 3 different work places, many, developer colleagues and countless projects. Today I would describe the way I utilise TDD as Real world TDD.

So, what do I mean by real world TDD?

Real World TDD is taking from the development process of Test Driven Development and implementing what works for you and your Dev Team. You may have a large team, a small team, you may have an experienced team that have been writing unit tests for years, with full continuous integration and build servers that are triggered on every successful code check in, or the opposite extreme, a team full of junior devs that like me back in 2007 just don’t get it.

Units Tests” == (UnitTests && IntegrationTests)

In my view of Real World TDD the term Unit Tests and integration tests are the same thing, I don’t enforce that unit tests should test a small “unit” of code, but instead a process, such as an MVC Controller action result or the result of a form submission to the DB. The result is that unit tests cover far more of your system code.

Real World TDD - Development Process

Let’s take the code that at time of writting submits an answer on IntermittentBug.

  1. Write a test that saves dummy form data to the database.
  2. Refactor this into a method.
  3. Refactor the test to point to the method.
  4. Use the newly unit tested method in your application

Let’s see the code.

Step 1 - Write a test that saves dummy form data to the database.

[TestMethod]
public void TestPostArticleAnswer_Logic()
{
    int DBArticleAnswerCount = 0;

    using (var DB = new IntermittentBug_DBEntities())
    {
        DBArticleAnswerCount = DB.tbl_Answer.Count();

        tbl_Answer NewAnswer = new tbl_Answer();

        NewAnswer.ArticleID = 1;
        NewAnswer.Content = ""unit test answer"";
        NewAnswer.UserID = 1;
        NewAnswer.DatePosted = DateTime.Now;

        DB.tbl_Answer.Add(NewAnswer);
        DB.SaveChanges();

        Assert.IsTrue(DB.tbl_Answer.Count() == (DBArticleAnswerCount + 1));
    }
}

Step 2 - Refactor this into a method.

public static tbl_Answer PostAnswer(int ArticleID, int UserID, string AnswerContent)
{
    try
    {
        using (var DB = new IntermittentBug_DBEntities())
        {
            tbl_Answer NewAnswer = new tbl_Answer();

            NewAnswer.ArticleID = ArticleID;
            NewAnswer.Content = AnswerContent;
            NewAnswer.UserID = UserID;
            NewAnswer.DatePosted = DateTime.Now;

            DB.tbl_Answer.Add(NewAnswer);
            DB.SaveChanges();

            return NewAnswer;
        }
    }
    catch (Exception ex)
    {
        MethodBase mb = MethodBase.GetCurrentMethod();
        _Log.Error(mb.DeclaringType.Name + " " + mb.Name + " " + ex.Message, ex);
        throw;
    }
}

Step 3 - Refactor the test to point to the method.

[TestMethod]
public void TestPostArticleAnswer_Logic()
{
    int DBArticleAnswerCount = 0;
            
    using (var DB = new IntermittentBug_DBEntities())
    {
        DBArticleAnswerCount = DB.tbl_Answer.Count();
        AnswerLogic.PostAnswer(1, 1, "unit test answer");
        Assert.IsTrue(DB.tbl_Answer.Count() == (DBArticleAnswerCount + 1));
    }            
}

Step 4 - Use the newly unit tested method in your application

/// <summary>
/// Post an answer for an article to the database
/// </summary>
/// <returns></returns>
[EnsureUserIsValidated]
[HttpPost, ValidateInput(false)]
public JsonResult PostAnswer(FormCollection Collection)
{
    PostArticleAnswerJsonResult returnObj = new PostArticleAnswerJsonResult();

    try
    {
        int ArticleID = Convert.ToInt32(Collection["ArticleID"]);
        int UserID = Convert.ToInt32(Collection["UserID"]);
        string Answer = Collection["Answer"];

        returnObj.Answer = AnswerLogic.PostAnswer(ArticleID, UserID, Answer);
        returnObj.Success = true;
    }
    catch (Exception ex)
    {
        MethodBase mb = MethodBase.GetCurrentMethod();
        _log.Error(mb.DeclaringType.Name + " " + mb.Name + " " + ex.Message, ex);

        returnObj.Success = false;
        returnObj.Answer = null;
        returnObj.ExceptionMessage = ex.Message;
    }

    return Json(returnObj, JsonRequestBehavior.DenyGet);
}

This is entirely optional, but I always have a TestBase base class that can store globals such as test objects that are reused in different test classes and any code where you want to initially setup test data. I use a static constructor to ensure this is run on each unit test call. Below is the TestBase class for IntermittentBug at time of writting.

public class TestBase
{
	protected int _TestUserID = 1;
	protected int _TestArticleID = TestArticleID;

	private static int TestArticleID;

	static TestBase()
	{
		// setup the data first
		using (var DB = new IntermittentBug_DBEntities())
		{
			TestArticleID = DB.Usp_ConfigureUnitTestData().Single().Value;
		}
	}

	protected List<tbl_Answer> GetAllTestAnswers
	{
		get
		{
			using (var DB = new IntermittentBug_DBEntities())
			{
				return DB.tbl_Answer.Where(x => x.ArticleID == _TestArticleID).ToList();
			}                    
		}
	}
}

Real World TDD – Testing Data

There is a lot of articles that push the ethos that unit tests should never interact with your database. I find this odd as every application I have ever worked on is dependent on the database, it’s a fundamental aspect and is critical to ensure that the data is integral. However, the downside is that you end up flooding your dev database with test data.

To overcome this issue, I have a clean-up script, usually a stored proc that is executed in my TestBase static constructor. This script removes any test data setting the DB up fresh. As it’s a static constructor, it automatically executes as soon as a test runs, see above…

Real World TDD – Mocking Data

Certain sections of code are intrinsically difficult to test. Things like the Request and Response objects, Cookies, Sessions, Getting the URL, etc. There are Mocking frameworks available to mock these objects and I would recommend using these if you are testing code that depend on 3rd party library’s. A mocking framework I have used in the past is Moq. Using Mocking frameworks can increase the complexity of your unit test project significantly and I have found that with some clever refactoring you can avoid the need of these frameworks all together.

TDD purists will hate this, but if I can achieve what’s required with 1 line of code rather than installing complex mocking frameworks, I’ll take the easiest and simplest approach thank you very much! If you are testing your own code, you can use a somewhat hacky, but very effective and simple way to detect code being executed by a unit test. The following method will detect if the code is being executed in a unit test.

/// <summary>
/// Detects if the current code is being executed from a unit test.
/// </summary>
/// <returns></returns>
public static bool IsUnitTestRunningLogic()
{
	string testAssemblyName = "Microsoft.VisualStudio.QualityTools.UnitTestFramework";
	return AppDomain.CurrentDomain.GetAssemblies()
					.Any(a => a.FullName.StartsWith(testAssemblyName));
}

Using this method, you can work with code that is tricky to unit test, see the example below that uses the value of a cookie…

string CookieValue = String.Empty;

if (DataObjectsHelper.IsUnitTestRunningLogic())
{
	CookieValue = "Unit test cookie value";
}
else
{
	CookieValue = HttpContext.Current.Request.Cookies["CookieName"];
}

// use the CookieValue...

I will update this article with more examples of the best ways to unit test controllers that return JSON without having to use mocking framworks.

 

 


JGilmartin Profile Image

JGilmartin

Technical Architect at Pinewood Technologies

Rating: 2890

C# Expert

Offline


Tutorial Statistics
  • Views: 410
  • Comments: 0
  • Author: JGilmartin (2890)
  • Date: 24/11/2016 21:53
Tags
C# .NET ASP.NET MVC

© 2016 - 2018 - IntermittentBug