QLC-2) Find the Highest Number Extension

An organisation delivers several topics (subjects).  Students are graded against each topic.  You are required to store the top score for each topic. 

We’ve designed the application so that it comprises of three core classes:

  • A class to find the highest number from an array of integers.

  • A class to find the highest score for a topic.

  • A class to write the topic and score to a file on the disk.

You are going to follow a TDD approach to find the highest score for a series of topics

Given the following specification

  • If the input is [{“Physics”, {56, 67, 45, 89}}], the result should be [{“Physics”, 89}]

  • If the input is [] the result should be []

  • If the input is [{“Physics”, {56, 67, 45, 89}}, {“Art”, {87, 66, 78}], the result should be [{“Physics”, 89}, {“Art”, 87}]

  • If the input is [{“Physics”, {56, 67, 45, 89}}, {“Art”, {87, 66, 78}}, {“Comp Sci”, {45, 88, 97, 56}}], the result should be [{“Physics”, 89}, {“Art”, 87}, {“Comp Sci”, 97}]

QLC-2.1) Setup and first test

  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. 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 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. 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. 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 will also need the following pieces of code

Second test

  1. Implement this second test

  2. And here is the implementation class

  3. 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.

  4. 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

  5. Refactor the tests so that they run. You should not have to change any of the test data.

Tests are a great way of identifying code smells. Highly coupled code leads to untestable code.

You want to test one class and one class only. The previous version of the TopicManager dragged in HighestNumberFinder. So inadvertently you were testing that class as well. This will become clearer as we continue to work through the TopicManager tests.

QLC-2.2) Working with Stubs

A Stub is part of the family of Test Doubles. They are used to ensure that tests focus on the behaviour of the CUT and not its dependents. Test environments should be controlled and predictable. Test Doubles give you that measure of stability and predictability.

A Stub method is one that returns canned results. A canned result is a predefined result. The result can be specific, a range of values, or any value. Also, the parameters into the method can be specific value, a range of values, or any value.

The HighestNumberFinder is designed to return an integer representing the number in a group of numbers. This can easily be stubbed out.

Begin by creating a new test

Here is the Stub. We have put it in a different namespace in the test project

But when you try and use this class in the tests, you should see type errors in all the tests. 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.

Here are the updated tests. Alternatively, you could modify TopicManager so it has a default constructor, but you will need to think about different issues and more tests for if TopicManager default constructor is used.

To give us more flexibility, we need to use an interface. And the interface needs to be in a place that is available to both production code and tests.

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

  2. Copy HighestNumberFinder.v6 to HighestNumberFinderFinal

  3. Refactor the class so that it implements the interface above

  4. Modify the Stub version so it also implements IHighestNumberFinder

  5. Refactor TopicManager so it now works with the interface IHighestNumberFinder and not directly with the implementation class

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

QLC-2.3 Limitations of Stubs, complete this TopicManager requirement

Add more tests to handle the last two requirements

•If the input is [{“Physics”, {56, 67, 45, 89}}, {“Art”, {87, 66, 78}], the result should be [{“Physics”, 89}, {“Art”, 87}]

Only one of the tests passes. Why?

Sample Solution

QLC-2.4 Mocks, complete the last TopicManager requirement

•If the input is [{“Physics”, {56, 67, 45, 89}}, {“Art”, {87, 66, 78}}, {“Comp Sci”, {45, 88, 97, 56}}], the result should be [{“Physics”, 89}, {“Art”, 87}, {“Comp Sci”, 97}]

Sample Solution