Part 2–SharePoint Retreat–TDD and Unit Testing, the methodology used cont..

The other parts of this series can be found here….

  1. SPRetreat TDD – a retrospective (See what I did there? -)
  2. Part 1 — SharePoint Retreat–TDD and Unit Testing, the methodology used
  3. Part 2 — SharePoint Retreat–TDD and Unit Testing, the methodology used cont.. (This post)
  4. Part 3 — SharePoint Retreat-TDD and Unit Testing, the methodology used con..

Carrying on….

At the end of the last post we’d written the first piece of truly test driven code. Before we can look at moving to SharePoint we need to complete the Magic 8 Ball code and test it.

  • We’ve tested that our Ball returns an answer, but what if the user doesn’t ask it a question? Here’s our next test..
        public void Ask_EmptyQuestion_ReceiveArgumentException()
        {
            //Arrange
            Ball ball = new Ball();

            //Act
            //Assert
            Assert.Throws<ArgumentException>(() => ball.AskQuestion(""));
        }
  • This time you can see that the act and assert are on the same line, perfectly valid in this instance. Running the test fails as we’d expect as we’re not doing anything in the production code to test for an empty question. We’ll fix it now, remembering the MINIMUM code to fix the test that failed!
        public string AskQuestion(string question)
        {
            if (string.IsNullOrEmpty(question))
                throw new ArgumentException();

            return "a";
        }
  • Running the test again, it passes as we’d expect. Red, Green, Refactor.. Let’s make that method more readable by refactoring out the string test into it’s own method. (using Resharper)

image

  • This gives us our protected method that we can now use elsewhere.

Moving on again, the next test.

  • When we ask a question, we should receive an answer from a collection of answers.
  • This time we need ball to contain a collection of Answers. Our arrange block looks like this:-

image

  • ball.Answers  in red looks like a compile failure and thus a valid fail. As we did before, we’ll let Resharper create the public property for this, thus passing that test.
        [Test]
        public void Ask_ValidQuestion_ReceiveAnswerFromCollection()
        {
            //Arrange
            Ball ball = new Ball();
            ball.Answers = new List<string>() {"a", "b", "c"};

            //Act
            string answer = ball.AskQuestion("q");

            //Assert
            Assert.Contains(answer,ball.Answers);
        }
  • When we run the test this time, we get an exception error because of course Resharper only created the public field, but it left in “not implemented exceptions”, so we’ll fix those. You’ll also note that it only added a public property, not an matching internal field. As this is a nice simple property, I changed it to an auto property.
  • Running the test again we now pass. (You might have spotted a problem at this point.. but read on.. Remember only enough code to fix a failed test!)
  • This time our test is looking to receive a random answer from the selection available. To test this, we’ll ask 1000 questions and use a list to check we receive all three, breaking out the loop once we do.
        [Test]
        public void Ask_ValidQuestion_ReceiveRandomAnswer()
        {
            //Arrange
            Ball ball = new Ball();
            ball.Answers = new List<string>(){"a","b","c"};
            bool allAnswersChosen = false;
            List<string> returnedAnswers = new List<string>();
            

            //Act
            for (int i = 0; i < 1000; i++)
            {
                //Added Thread sleep to allow the randomiser to be random.
                Thread.Sleep(5);

                string answer = ball.AskQuestion("q");

                if (!returnedAnswers.Contains(answer))
                    returnedAnswers.Add(answer);

                if (returnedAnswers.Count == 3)
                {
                    allAnswersChosen = true;
                    break;
                }
            }

            //Assert
            Assert.IsTrue(allAnswersChosen);
        }
  • This time the test fails because we’re only returning “a” from our very first test, (Which is also the reason the last test passed once we fixed the exceptions!)
  • To fix this, we’ll add a randomiser to our code and use it as an indexer to the Answers collection.
        public string AskQuestion(string question)
        {
            GuardEmptyString(question);

            return Answers[GetRandomIndex()];
        }

        private int GetRandomIndex()
        {
            Random rnd = new Random();
            return rnd.Next(Answers.Count);
        }
  • Running the tests again, our randomiser works, BUT one of our earlier tests is now broken! Adding the randomiser has introduced a Null Reference Exception. At this point, we need to make a decision, can we safely ignore this test and mark it as such, or do we need to amend the test? In this case, I’m going to amend the test to take into account the new collection that is required.
  • Adding the following to our first test allows all the tests to pass once more.
            //Added this after later tests caused a failure.
            ball.Answers = new List<string>(){"a"};
  • During the retrospective on this, Andrew raised the point of why we’d started using logic in our Test for the random function. As he rightly pointed out, Microsoft have already tested the Random object, so why are we redoing their work? This is known as crossing the seams and isn’t necessary!

Dontcrosstheseams

  • In order to test the behaviour here, we have one of two choices, We can either use the extract and overide method, or we can use TypeMock to isolate the randomiser and replace it’s behaviour.
  • The extract and overide method requires us to create a new FakeBall class in our test namespace, that inherits from our Ball class. We then adjust the Ball class to make it’s randomiser method to be protected virtual rather than private. This allows us to override it’s behaviour in our FakeBall class.
  • In our FakeBall class, we want to pass it in a value that will be passed back to the Base ball object, thus choosing which index to return.
    public class FakeBall : Ball
    {
        public FakeBall(int chosenIndex)
        {
            requiredIndex = chosenIndex;
        }

        private int requiredIndex;

        protected override int GetRandomIndex()
        {
            return requiredIndex;
        }
    }
  • We can now use NUnit’s test case functionality to run the same test three times with different parameters. The test should be fairly self explantory, just note that we’re using the FAKEBALL object this time round, passing in our chosen index, thus bypassing the randomiser.
        [TestCase("a", 0)]
        [TestCase("b", 1)]
        [TestCase("c", 2)]
        public void Ask_ValidQuestion_GetChosenAnswer(string expectedAnswer,int chosenIndex)
        {
            //Arrange
            FakeBall ball = new FakeBall(chosenIndex);
            ball.Answers = new List<string>(){"a","b","c"};

            //Act
            string answer = ball.AskQuestion("q");

            //Assert
            Assert.AreEqual(answer,expectedAnswer);

        }
  • Run the test and if you’re only running this one, you should see 3 passes.

As I said, you could have used TypeMock in this instance too. I’ve added a reference to the TypeMock dll’s now (I’d have need them later anyhow..). Add references for TypeMock.dll and TypeMock_ArrangeActAssert.dll (You may receive an error about TypeMock requiring a later version of .net, but as we’re wanting to add a SharePoint project to this solution, we’ll leave it at .Net 3.5)

  • This code block performs exactly the same test as before, but doesn’t require any change to the production code, so a very powerful way of performing dependency injection. This is only available in the full product of TypeMock Isolator and not the SharePoint specific edition.
        [TestCase("a", 0)]
        [TestCase("b", 1)]
        [TestCase("c", 2)]
        public void Ask_ValidQuestion_GetChosenAnswerWithTypeMock(string expectedAnswer, int chosenIndex)
        {
            //Arrange
            Ball ball = new Ball();
            ball.Answers = new List<string>(){"a","b","c"};

            Isolate.NonPublic.WhenCalled(ball,"GetRandomIndex").WillReturn(chosenIndex);

            //Act
            string answer = ball.AskQuestion("q");

            //Assert
            Assert.AreEqual(answer,expectedAnswer);
        }

Now at this point, I don’t think there are any other tests that I need to perform, In fact once you start having to rack your brain for another test, it’s probably time to move on. At this point, we now have a working Magic 8 Ball object that answers questions with a random selection from a supplied list.

The next step it to take our working Magic 8 Ball and start surfacing it in a SharePoint web part and test it against the SharePoint object model using TypeMock to isolate us and stop us crossing the seam! That’ll happen in Part 3.

Paul.

Leave a Reply

Your email address will not be published.

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.