Versions Compared

Key

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

...

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. In FindHighestNumber project create a new package that ends with .topicmanager

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

  5. Code Block
    languagec#
    namespace TopicManagerTests
    {
        package xx.yy.zz.topicmanagerservice
    
    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));
            }
    
       }
    }
  6. Create a new class called TopicManager in the FindHighestNumberService project

  7. Clean the code up so that the class is in a namespace package called TopicManagerService topicmanagerservice

  8. 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 top 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.

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

  11. Code Block
    languagec#
    class TopicManager
    {
       ArrayList<TopicTopScore> findTopicHighScores(ArrayList<TopicScores> array)
       {
          return new ArrayList<>();
       }
    }
  12. Now we need to complete the first piece of implementation code to pass the test

  13. So you will also need the following pieces of code

  14. Code Block
    publicpackage com.s2s.demos.topicmanager;
    
    class TopicTopScoreTopic
    {
       private  }
    Code Block
    public class TopicScores
    {
    String topicName;
       int    }

Second test

  1. Implement this second test

  2. Code Block
    languagejava
      score;
    @Test   
    public void find_heighest_score_with_array_of_one_return_array_of_one()
     public   {Topic( String topicName, int score)
       //{
    Arrange      this.topicName  int[] scores = { 56, 67, 45, 89 };
     = topicName;
          this.score = score;
       }
    
       public String topicNamegetTopicName()
    = "Physics";  {
         ArrayList<TopicScores> topicScoresreturn =topicName;
    new ArrayList<>();  }
    
       public topicScores.add(new TopicScores(topicName, scores));int getScore()
       {
          return score;
    TopicManager   cut = new TopicManager();
    }
    }
  3. Code Block
    public class TopicTopScore
    {
       
    }
  4. Code Block
    public class ArrayList<TopicTopScore>TopicScores
    expectedResult{
    = new ArrayList<>(); 
    }

Second test

  1. Implement this second test

  2. Code Block
    languagejava
       @Test
      expectedResult.add(new TopicTopScore(topicName, 89));
    
     public void find_heighest_score_with_array_of_one_return_array_of_one()
       {
           // ActArrange
           ArrayList<TopicTopScore>int[] resultscores = cut.findTopicHighScores(topicScores);{ 56, 67, 45, 89 };
           String  assertEquals(result.get(0).getTopicName(), expectedResult.get(0).getTopicName())topicName = "Physics";
           ArrayList<TopicScores> topicScores = new ArrayList<>();
           assertEquals(resulttopicScores.get(0).getTopScore(), expectedResult.get(0).getTopScore())add(new TopicScores(topicName, scores));
       }
  3. And here is the implementation class

  4. Code Block
    languagec#
    class
    TopicManager {    private final HighestNumberFinderTopicManager highestNumberFindercut = new HighestNumberFinderTopicManager();
    
       ArrayList<TopicTopScore> findTopicHighScores(ArrayList<TopicScores> array)    {
          ArrayList<TopicTopScore> topScoresexpectedResult = new ArrayList<>();
           if(arrayexpectedResult.sizeadd() == 1)new TopicTopScore(topicName, 89));
    
         {  // Act
           TopicScoresArrayList<TopicTopScore> tsresult = arraycut.getfindTopicHighScores(0topicScores);
    
             int topScore = highestNumberFinder.findHighestNumber(ts.getScoresassertEquals(expectedResult.get(0).getTopicName(), result.get(0).getTopicName());
               topScores.add(new TopicTopScore(ts.getTopicNameassertEquals(expectedResult.get(0).getTopScore(), topScore))result.get(0).getTopScore() );
       }
  5. And here is the implementation class

  6. Code Block
    languagejava
    class TopicManager
    {
     }  private final HighestNumberFinder highestNumberFinder = returnnew topScoresHighestNumberFinder();
    
      }
    }
  7. Updated TopicScores.java

  8. Code Block
    package com.s2s.demos.topicmanager;
    
    public class TopicScores
    { ArrayList<TopicTopScore> findTopicHighScores(ArrayList<TopicScores> array)
       {
        private String topicName;ArrayList<TopicTopScore> topScores =  private int[] scoresnew ArrayList<>();
    
          public TopicScores(String topicName, int[] scoresif(array.size() == 1)
       {   {
       this.topicName = topicName;     TopicScores  this.scorests = scoresarray.get(0);
       }       int publictopScore String   getTopicName()= highestNumberFinder.findHighestNumber(ts.getScores());
    
      {       return topicName;
       } topScores.add(new TopicTopScore(ts.getTopicName(), topScore));
          }
      public   int[] getScores()
       {
          return scorestopScores;
       }
    }
  9. Updated TopicTopScoresTopicScores.java

  10. Code Block
    languagejava
    public class TopicTopScore
    package com.s2s.demos.topicmanager;
    
    public class TopicScores
    {
       private  String topicName;
       private  int[] topScorescores;
       
       public TopicTopScoreTopicScores(String topicName, int[] scorescores)
       {
          this.topScoretopicName = scoretopicName;
          this.topicNamescores = topicNamescores;
       }
       
       public String   getTopicName()
       {
          return topicName;
       }
       
       public   int[] getTopScoregetScores()
       {
          return topScorescores;
       }
    }
  11. Looking back at TopicManager, 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.

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

  13. Code Block
    languagec#
    public class TopicManager { private HighestNumberFinder highestNumberFinder; public TopicManager( HighestNumberFinder hnf ) { highestNumberFinder = hnf;

    Updated TopicTopScores.java

  14. Code Block
    languagejava
    public class TopicTopScore
    {
       private  String topicName;
       private  int topScore;
    
       public TopicTopScore(String topicName, int score)
       {
          this.topScore = score;
          this.topicName = topicName;
       }
       
       public String getTopicName()
       {
          return topicName;
       }
    
        public TopicTopScore[]int findTopicHighScoresgetTopScore(TopicScores[] array)
    
       {
          return topScore;
    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>();
        }
    }
  15. Refactor the tests so that they run. You should not have to change any of the test data.

Tip

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

Info

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

...

  1. }
    }
  2. Looking back at TopicManager, 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.

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

  4. Code Block
    languagejava
    public class TopicManager
    {
        private HighestNumberFinder highestNumberFinder;
    
        public  

...

  1. TopicManager( HighestNumberFinder hnf )
        

...

  1. {
            highestNumberFinder = hnf;
        }
    
        public TopicTopScore[] findTopicHighScores(TopicScores[] array)
       

...

  1.  {
           

...

  1.  

...

  1. if(array.Length == 

...

  1. 1)

...

  1. 
           

...

  1.  

...

  1. {
    

...

  1.  

...

  1.  

...

  1.         

...

  1.  

...

  1.  List<TopicTopScore> topScores = new List<TopicTopScore>();
    
       

...

  1.  

...

  1.         

...

  1. TopicScores 

...

  1. ts = 

...

  1. array[0];
                int topScore = 

...

  1. highestNumberFinder.findHighestNumber(ts.Scores);
    
          

...

  1.       topScores.Add(new TopicTopScore(ts.TopicName, topScore));
    
      

...

Here is the Stub you will use (create it in the test folder - it’s not production code)

...

languagec#

...

  1.           return topScores.ToArray();
            }
        

...

  1.  

...

  1.  

...

  1.  

...

  1.  else
       

...

  1.          return 

...

  1. Array.Empty<TopicTopScore>();
        

...

The stub is substituted in line 13.

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, is typed against com.s2s.demos.findhighestnumber.fin.HighestNumberFinder. So namespaces do not offer us the solution we are looking for.

We need to use interfaces. 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 production code, and create it in the com.s2s.demos.findhighestnumber package

  2. Code Block
    languagec#
    package com.s2s.demos.findhighestnumber;
    
    public interface IHighestNumberFinder
    {
       int findHighestNumber(int[] values);
    }
  3. Modify the production version and test version of HighestNumberFinder, so that they both implement the interface IHighestNumberFinder

  4. Here is the stub version

  5. Code Block
    package com.s2s.demos.topicmanager;
    
    import com.s2s.demos.findhighestnumber.IHighestNumberFinder;
    
    // The STUB version
    public class HighestNumberFinder implements IHighestNumberFinder
    {
       @Override
        public int findHighestNumber(int[] array)
        {
            return 89;
        }
    }
    
  6. Here is the production version

  7. Code Block
    package com.s2s.demos.findhighestnumber.fin;
    
    import com.s2s.demos.findhighestnumber.IHighestNumberFinder;
    
    public  class HighestNumberFinder implements IHighestNumberFinder
    {
       @Override
        public int findHighestNumber(int[] array)
        {
            int highestSoFar = Integer.MIN_VALUE;
     }
    }
  8. Refactor the tests so that they run. You should not have to change any of the test data.

Tip

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

Info

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

Code Block
languagejava
   @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";
       ArrayList<TopicScores> topicScores = new ArrayList<>();
       topicScores.add(new TopicScores(topicName, scores));
   
       // Use a stub version of HighestNumberFinder
       com.s2s.demos.topicmanager.HighestNumberFinder hnf = 
              

...

 

...

 

...

 

...

 

...

 

...

     new com.s2s.demos.topicmanager.HighestNumberFinder();
  

...

     TopicManager cut = new TopicManager(hnf);
    

...

 

...

 

...

 

...

ArrayList<TopicTopScore> 

...

expectedResult = new ArrayList<>();
       expectedResult.add(new TopicTopScore(topicName, 89));

  

...

 

...

 

...

   // Act
    

...

   ArrayList<TopicTopScore> result = cut.findTopicHighScores(topicScores);

 

...

 

...

     

...


    

...

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

...

  

...

 

...

assertEquals(expectedResult.get(0).getTopicName(), result.get(0).getTopicName());
       assertEquals(expectedResult.get(0).getTopScore(), result.get(0).getTopScore() );
   }

Here is the Stub you will use (create it in the test folder - it’s not production code)

Code Block
languagejava
package com.s2s.demos.topicmanager;

public class HighestNumberFinder
{
    public

...

 

...

int findHighestNumber(int[] array)
    {
       

...

 

...

return 

...

89;
    }

...

You should find that the test is no longer in an error state

...

Rerun all your tests they should still be passing.

Tip

The tests have given us the confidence to refactor the code and begin to think more clearly about our implementation.

Now let’s substitute a stub in place of the real implementation of HighestNumberFinder.

...

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()

...

languagec#

...

}

The stub is substituted in line 11.

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, is typed against com.s2s.demos.findhighestnumber.fin.HighestNumberFinder. So packages do not offer us the solution we are looking for.

We need to use interfaces. 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.

  1. Create a new interface that is part of the production code, and create it in the com.s2s.demos.findhighestnumber package

  2. Code Block
    languagejava
    package com.s2s.demos.findhighestnumber;
    
    public interface IHighestNumberFinder
    {
       int findHighestNumber(int[] values);
    }
  3. Modify the production version and test version of HighestNumberFinder, so that they both implement the interface IHighestNumberFinder

  4. Here is the stub version

  5. Code Block
    languagejava
    package com.s2s.demos.topicmanager;
    
    import com.s2s.demos.findhighestnumber.IHighestNumberFinder;
    
    // The STUB version
    public class HighestNumberFinder implements IHighestNumberFinder
    {
       @Override
        public int findHighestNumber(int[] array)
        {
            return 89;
        }
    }
    
  6. Here is the production version

  7. Code Block
    languagejava
    package com.s2s.demos.findhighestnumber.fin;
    
    import com.s2s.demos.findhighestnumber.IHighestNumberFinder;
    
    public  class HighestNumberFinder implements IHighestNumberFinder
    {
       @Override
        public int findHighestNumber(int[] array)
        {
            stringint topicNamehighestSoFar = "Physics"Integer.MIN_VALUE;
            
       TopicScores[] topicScores = { new TopicScoresfor(topicName, scores)int };val : array )
            {
    IHighestNumberFinder hnf = new FindHighestNumberServiceFinal.HighestNumberFinder();        if( val > highestSoFar  TopicManager cut)
    = new TopicManager(hnf);             TopicTopScore[] expectedResulthighestSoFar = {val;
    new TopicTopScore(topicName, 89) };     }
            //return ActhighestSoFar;
        }    
    }
  8. Refactor TopicManager so it now works with the interface IHighestNumberFinder and not directly with the implementation class

  9. Code Block
    languagejava
    package com.s2s.demos.topicmanager;
    
     TopicTopScore[] result = cut.findTopicHighScores(topicScores)import com.s2s.demos.findhighestnumber.IHighestNumberFinder;
    import java.util.ArrayList;
    
    class TopicManager
    {
       private final IHighestNumberFinder highestNumberFinder;
    
     // Assert public  TopicManager()
       {
         Assert.AreEqual(result[0].TopicName, expectedResult[0].TopicName); this.highestNumberFinder = null;
       }
       
       public  Assert.AreEqual(result[0].TopScore, expectedResult[0].TopScore);TopicManager( IHighestNumberFinder hnf )
       {
        }   highestNumberFinder  }
  10. Modify line 8 to

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

  13. Rerun the tests, they should all pass
     ...
  14. You should find that the test is no longer in an error state

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

Tip

The tests have given us the confidence to refactor the code and begin to think more clearly about our implementation.

Info

We’ve now created a controlled environment for our tests

...

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

One Only one of the results will failtests passes. Why?

Sample solution

QLC-2.4 Mocks, complete the last TopicManager requirement

...