Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. Create a new Test Project in the FindHighestNumber Solution

  2. Install the packages you want (MSTest already installed, or NUnit - we are going to be using NUnit)

  3. Create a new setup the new test class as shown here (remember you will have errors)

  4. Code Block
    languagec#
    namespace TopicManagerTests
    {
        public class TopicManagerTests
        {
            [Test]
            public void find_heighest_score_in_empty_array_return_empty_array()
            {
                // Arrange
                int[] array = {};
                TopicManager cut = new TopicManager();
                int[] expectedResult = {};
    
                // Act
                Topic[] result = cut.findTopicHighScores(array);
    
                // Assert
                Assert.That(result, Is.EqualTo(result));
            }
        }
    }
  5. Create a new class called TopicManager in the FindHighestNumberService project

  6. Clean the code up so that the class is in a namespace called TopicManagerService

  7. Lines 9, 11, and 14 give us enough information to allow us to start thinking about what we are trying to design here. based on the requirements, we want to pass into the method findTopicHighScores an array of Topics and their accompanying scores. It should return the topic score of each Topic, we will call this TopicTopScore. Each item in the array being passed into the findTopicHighScores will be called TopicScores (plural I know, but it matches the context, you many may want to lose the ‘s' at the end of the class name). Let’s refactor the code to reflect what we have outlined here.

  8. Code Block
    languagec#
        public class TopicManagerTests
        {
            [Test]
            public void find_heighest_score_in_empty_array_return_empty_array()
            {
                // Arrange
                TopicScores[] array = Array.Empty<TopicScores>();
                TopicManager cut = new TopicManager();
                TopicTopScore[] expectedResult = Array.Empty<TopicTopScore>();
    
                // Act
                TopicTopScore[] result = cut.findTopicHighScores(array);
    
                // Assert
                Assert.That(result, Is.EqualTo(result));
            }
        }
  9. We are now in a good position to get VS to generate the findTopicHighScores with the correct signature

  10. Code Block
    languagec#
    namespace TopicManagerService
    {
        public class TopicManager // TopicManager.cs
        {
            public TopicTopScore[] findTopicHighScores(TopicScores[] array)
            {
                // This block oc code assumes that the input is an empty array, there is no need for an if statement
                return Array.Empty<TopicTopScore>();
            }
        }
    }
  11. Now we need to complete the first piece of implementation code to pass the test

  12. So you should have the following pieces of code

  13. Code Block
    namespace TopicManagerService
    {
        public class TopicTopScore // TopicTopSscore.cs
        {
        }
    }
  14. Code Block
    namespace TopicManagerService
    {
        public class TopicScores // TopicScores.cs
        {
        }
    }

...

  1. Implement this second test

  2. Code Block
    languagec#
            public void find_heighest_score_with_array_of_one_return_array_of_one()
            {
                // Arrange
                int[] scores = { 56, 67, 45, 89 };
                string topicName = "Physics";
                TopicScores[] topicScores = { new TopicScores(topicName, scores) };
                TopicManager cut = new TopicManager();
                TopicTopScore[] expectedResult = {new TopicTopScore(topicName, 89)};
    
                // Act
                TopicTopScore[] result = cut.findTopicHighScores(topicScores);
    
                // Assert
                Assert.AreEqual(result[0].TopicName, expectedResult[0].TopicName);
                Assert.AreEqual(result[0].TopScore, expectedResult[0].TopScore);
            }
  3. And here is the implementation class

  4. Code Block
    languagec#
        public class TopicManager
        {
            private HighestNumberFinder highestNumberFinder = new HighestNumberFinder();
    
            public TopicTopScore[] findTopicHighScores(TopicScores[] array)
            {
                if(array.Length == 1)
                {
                    List<TopicTopScore> topScores = new List<TopicTopScore>();
    
                    TopicScores ts = array[0];
                    int topScore = highestNumberFinder.findHighestNumber(ts.Scores);
    
                    topScores.Add(new TopicTopScore(ts.TopicName, topScore));
    
                    return topScores.ToArray();
                }
                else
                    return Array.Empty<TopicTopScore>();
            }
        }
  5. Line 3 represents the elephant in the code

    1. We already have tests for the HighestNumberFinder class, and yes class TopicManager requires HighestNumberFinder. But the coupling between these two classes is such that we cannot test TopicManger independently of HighestNumberFinder. Actually, this is a code smell.

    2. we can not substitute out HighestNumberFinder, this also is a code smell.

    3. We can remove the code smell by injecting HighestNumberFinder into TopicManager, when TopicManager is created.

  6. Modify TopicManager as follows

    1. remove the creation of HighestNumberFinder at line 5

    2. Pass an instance of a HighestNumberFinder into TopicManager as a constructor parameter

  7. Code Block
    languagec#
    public class TopicManager
    {
        private HighestNumberFinder highestNumberFinder;
    
        public  TopicManager( HighestNumberFinder hnf )
        {
            highestNumberFinder = hnf;
        }
    
        public TopicTopScore[] findTopicHighScores(TopicScores[] array)
        {
            if(array.Length == 1)
            {
                List<TopicTopScore> topScores = new List<TopicTopScore>();
    
                TopicScores ts = array[0];
                int topScore = highestNumberFinder.findHighestNumber(ts.Scores);
    
                topScores.Add(new TopicTopScore(ts.TopicName, topScore));
    
                return topScores.ToArray();
            }
            else
                return Array.Empty<TopicTopScore>();
        }
    }
  8. Refactor the tests so that they run. You should not have to change any of the test data.

...

But when you try and use this class in the tests, you should see type errors. This is because, in the implementation unit of TopicManager, it's typed against HighestNumberFinder in a namespace called FindHighestNumberService.v6. So namespaces do not offer us the solution we are looking for.

...

  1. Create a new interface that is part of the HighestNumberFinder project

  2. Code Block
    languagec#
    namespace FindHighestNumberService
    {
        public interface IHighestNumberFinder
        {
            int findHighestNumber(int[] values);
        }
    }
  3. Copy HighestNumberFinder.v6 to HighestNumberFinderFinal and refactor the class so that it implements the interface above

  4. Code Block
    languagec#
    using System;
    using FindHighestNumberService;
    
    namespace FindHighestNumberServiceFinal
    {
        public class EmptyArrayException : Exception
        {
            public EmptyArrayException(string message):base(message)
            {
            }
        }
        public class HighestNumberFinder : IHighestNumberFinder
        {
            public int findHighestNumber(int[] values)
            {
                if (values.Length < 1)
                    throw new EmptyArrayException("Array is empty");
    
                int result = Int32.MinValue;
    
                for (int i = 0; i < values.Length; i++)
                {
                    if (values[i] > result)
                        result = values[i];
                }
                return result;
            }
        }
    }
  5. Refactor the tests so that the HighestNumberFinder is instantiated as follows

  6. Code Block
        IHighestNumberFinder hnf = new FindHighestNumberServiceFinal.HighestNumberFinder();
  7. Refactor TopicManager so it now works with the interface IHighestNumberFinder and not directly with the implementation class

  8. Rerun all your tests they should still be passing.

...

  1. Create a new called find_heighest_score_with_array_of_one_return_array_of_one_using_stub(), it's a copy of find_heighest_score_with_array_of_one_return_array_of_one()

  2. Code Block
    languagec#
            [Test]
            public void find_heighest_score_with_array_of_one_return_array_of_one_using_stub()
            {
                // Arrange
                int[] scores = { 56, 67, 45, 89 };
                string topicName = "Physics";
                TopicScores[] topicScores = { new TopicScores(topicName, scores) };
                IHighestNumberFinder hnf = new FindHighestNumberServiceFinal.HighestNumberFinder();
                TopicManager cut = new TopicManager(hnf);
                TopicTopScore[] expectedResult = { new TopicTopScore(topicName, 89) };
    
                // Act
                TopicTopScore[] result = cut.findTopicHighScores(topicScores);
    
                // Assert
                Assert.AreEqual(result[0].TopicName, expectedResult[0].TopicName);
                Assert.AreEqual(result[0].TopScore, expectedResult[0].TopScore);
            }
        }
  3. Modify line 8 to

  4. Code Block
    languagec#
        IHighestNumberFinder hnf = new TopicManagerService.HighestNumberFinder();
  5. The error is because of TopicManagerTests.HighestNumberFinder does not implement the IHighestNumberFinder interface. Modify it so that it does.

  6. Rerun the tests, they should all pass.

...