From 58302ddda0f1545a5abb0c7e6e3625e97f174b75 Mon Sep 17 00:00:00 2001 From: giacomelli Date: Sat, 3 Nov 2018 21:06:11 -0200 Subject: [PATCH] Splitting Sudoku unit tests into separate classes and improving code coverage --- .../Sudoku/SudokuBoardTest.cs | 69 ++++++++ .../Sudoku/SudokuCellsChromosomeTest.cs | 35 ++++ .../SudokuPermutationsChromosomeTest.cs | 34 ++++ .../SudokuRandomPermutationsChromosomeTest.cs | 37 ++++ .../Sudoku/SudokuTest.cs | 163 ------------------ .../Sudoku/SudokuTestHelper.cs | 46 +++++ .../Sudoku/SudokuPermutationsChromosome.cs | 2 +- 7 files changed, 222 insertions(+), 164 deletions(-) create mode 100644 src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuBoardTest.cs create mode 100644 src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuCellsChromosomeTest.cs create mode 100644 src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuPermutationsChromosomeTest.cs create mode 100644 src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuRandomPermutationsChromosomeTest.cs delete mode 100644 src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTest.cs create mode 100644 src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTestHelper.cs diff --git a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuBoardTest.cs b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuBoardTest.cs new file mode 100644 index 00000000..d92f7f46 --- /dev/null +++ b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuBoardTest.cs @@ -0,0 +1,69 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using GeneticSharp.Extensions.Sudoku; +using NUnit.Framework; + +namespace GeneticSharp.Extensions.UnitTests.Sudoku +{ + + + [TestFixture()] + [Category("Extensions")] + public class SudokuBoardTest + { + [Test()] + public void Constructor_TooManyCells_Exception() + { + var tooMuchCells = Enumerable.Repeat(0, 82); + var actual = Assert.Catch(() => + { + new SudokuBoard(tooMuchCells); + }); + Assert.AreEqual("cells", actual.ParamName); + } + + /// + /// The sample sudoku string should parse properly into corresponding cells + /// + [Test()] + public void Parse_SampleString_ConsistantCells() + { + + var sudoku = SudokuTestHelper.CreateBoard(); + + Assert.AreEqual(sudoku.Cells[0], 9); + Assert.AreEqual(sudoku.Cells[1], 0); + Assert.AreEqual(sudoku.Cells[2], 2); + Assert.AreEqual(sudoku.Cells[sudoku.Cells.Count - 2], 5); + Assert.AreEqual(sudoku.Cells[sudoku.Cells.Count - 1], 0); + + sudoku.SetCell(0,0,0); + Assert.AreEqual(sudoku.Cells[0], 0); + var stringExport = sudoku.ToString(); + var currentIndex = 0; + foreach (var sudokuCell in sudoku.Cells) + { + var newIndex = stringExport.IndexOf(sudokuCell.ToString(CultureInfo.InvariantCulture), currentIndex, StringComparison.Ordinal); + Assert.Greater(newIndex, currentIndex); + currentIndex = newIndex+1; + } + } + + + /// + /// The sample sudoku file should parse properly into corresponding individual Sudokus + /// + [Test()] + public void Parse_SampleFile_SudokusAreParsedFromFile() + { + + var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sudoku", "SudokuList.sdk"); + var sudokus = SudokuBoard.ParseFile(fileName); + Assert.AreEqual(sudokus.Count, 16002); + Assert.AreEqual(sudokus[0].Cells[2], 7); + Assert.AreEqual(sudokus[1].Cells[1], 2); + } + } +} \ No newline at end of file diff --git a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuCellsChromosomeTest.cs b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuCellsChromosomeTest.cs new file mode 100644 index 00000000..b15ac9fd --- /dev/null +++ b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuCellsChromosomeTest.cs @@ -0,0 +1,35 @@ +using GeneticSharp.Extensions.Sudoku; +using NUnit.Framework; + +namespace GeneticSharp.Extensions.UnitTests.Sudoku +{ + [TestFixture] + [Category("Extensions")] + public class SudokuCellsChromosomeTest + { + [Test] + public void Constructor_NoArgs_Length81() + { + var target = new SudokuCellsChromosome(); + Assert.AreEqual(81, target.Length); + + var genes = target.GetGenes(); + Assert.AreEqual(81, genes.Length); + } + + /// + /// The cells chromosome might need more individuals, so in order to keep execution time low, we only expect near completion + /// + [Test] + public void Evolve_SimpleSudokuCellsChromosome_NearlySolved() + { + var sudoku = SudokuTestHelper.CreateBoard(); + + //the cells chromosome should solve the sudoku or nearly in less than 50 generations with 500 chromosomes + var chromosome = new SudokuCellsChromosome(sudoku); + var fitness = SudokuTestHelper.Eval(chromosome, sudoku, 500, -20, 30); + Assert.GreaterOrEqual(fitness, -20); + + } + } +} \ No newline at end of file diff --git a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuPermutationsChromosomeTest.cs b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuPermutationsChromosomeTest.cs new file mode 100644 index 00000000..89ec4455 --- /dev/null +++ b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuPermutationsChromosomeTest.cs @@ -0,0 +1,34 @@ +using GeneticSharp.Domain.Chromosomes; +using GeneticSharp.Extensions.Sudoku; +using NUnit.Framework; + +namespace GeneticSharp.Extensions.UnitTests.Sudoku +{ + [TestFixture] + [Category("Extensions")] + public class SudokuPermutationsChromosomeTest + { + [Test] + public void Constructor_NoArgs_Length9() + { + var target = new SudokuPermutationsChromosome(); + Assert.AreEqual(9, target.Length); + + var genes = target.GetGenes(); + Assert.AreEqual(9, genes.Length); + } + + /// + /// The permutation chromosome should always solve the sudoku in a reasonable time with 1000 chromosomes + /// + [Test] + public void Evolve_SimpleSudokuPermutationsChromosome_Solved() + { + var sudoku = SudokuTestHelper.CreateBoard(); + + IChromosome chromosome = new SudokuPermutationsChromosome(sudoku); + var fitness = SudokuTestHelper.Eval(chromosome, sudoku, 1000, 0, 50); + Assert.AreEqual(fitness, 0); + } + } +} \ No newline at end of file diff --git a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuRandomPermutationsChromosomeTest.cs b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuRandomPermutationsChromosomeTest.cs new file mode 100644 index 00000000..896abafc --- /dev/null +++ b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuRandomPermutationsChromosomeTest.cs @@ -0,0 +1,37 @@ +using GeneticSharp.Extensions.Sudoku; +using NUnit.Framework; + +namespace GeneticSharp.Extensions.UnitTests.Sudoku +{ + [TestFixture] + [Category("Extensions")] + public class SudokuRandomPermutationsChromosomeTest + { + [Test] + public void Constructor_NoArgs_Length9() + { + var target = new SudokuRandomPermutationsChromosome(); + Assert.AreEqual(9, target.Length); + + var genes = target.GetGenes(); + Assert.AreEqual(9, genes.Length); + } + + /// + /// The random permutations chromosome require more individuals and generations, so we only test for significant progresses + /// + [Test] + public void Evolve_SimpleSudokuRandomPermutationsChromosome_Progressed() + { + var sudoku = SudokuTestHelper.CreateBoard(); + + //the Random permutations chromosome should make significant progresses over 3 generations with 5 individuals + + var chromosome = new SudokuRandomPermutationsChromosome(sudoku, 2, 3); + var fitness1 = new SudokuFitness(sudoku).Evaluate((ISudokuChromosome)chromosome); + var fitness2 = SudokuTestHelper.Eval(chromosome, sudoku, 5, fitness1 + 20, 3); + Assert.GreaterOrEqual(fitness2, fitness1 + 20); + + } + } +} \ No newline at end of file diff --git a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTest.cs b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTest.cs deleted file mode 100644 index e3366de8..00000000 --- a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTest.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using GeneticSharp.Domain; -using GeneticSharp.Domain.Chromosomes; -using GeneticSharp.Domain.Crossovers; -using GeneticSharp.Domain.Mutations; -using GeneticSharp.Domain.Populations; -using GeneticSharp.Domain.Selections; -using GeneticSharp.Domain.Terminations; -using GeneticSharp.Extensions.Sudoku; -using NUnit.Framework; - -namespace GeneticSharp.Extensions.UnitTests.Sudoku -{ - - - [TestFixture()] - [Category("Extensions")] - public class SudokuTest - { - - private readonly string _easySudokuString = "9.2..54.31...63.255.84.7.6..263.9..1.57.1.29..9.67.53.24.53.6..7.52..3.4.8..4195."; - - - [Test()] - public void Constructor_TooManyCells_Exception() - { - var tooMuchCells = Enumerable.Repeat(0, 82); - var actual = Assert.Catch(() => - { - new SudokuBoard(tooMuchCells); - }); - Assert.AreEqual("cells", actual.ParamName); - } - - /// - /// The sample sudoku string should parse properly into corresponding cells - /// - [Test()] - public void Parse_SampleString_ConsistantCells() - { - - var sudoku = SudokuBoard.Parse(_easySudokuString); - - Assert.AreEqual(sudoku.Cells[0], 9); - Assert.AreEqual(sudoku.Cells[1], 0); - Assert.AreEqual(sudoku.Cells[2], 2); - Assert.AreEqual(sudoku.Cells[sudoku.Cells.Count - 2], 5); - Assert.AreEqual(sudoku.Cells[sudoku.Cells.Count - 1], 0); - - sudoku.SetCell(0,0,0); - Assert.AreEqual(sudoku.Cells[0], 0); - var stringExport = sudoku.ToString(); - var currentIndex = 0; - foreach (var sudokuCell in sudoku.Cells) - { - var newIndex = stringExport.IndexOf(sudokuCell.ToString(CultureInfo.InvariantCulture), currentIndex, StringComparison.Ordinal); - Assert.Greater(newIndex, currentIndex); - currentIndex = newIndex+1; - } - } - - - - /// - /// The sample sudoku file should parse properly into corresponding individual Sudokus - /// - [Test()] - public void Parse_SampleFile_SudokusAreParsedFromFile() - { - - var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sudoku", "SudokuList.sdk"); - var sudokus = SudokuBoard.ParseFile(fileName); - Assert.AreEqual(sudokus.Count, 16002); - Assert.AreEqual(sudokus[0].Cells[2], 7); - Assert.AreEqual(sudokus[1].Cells[1], 2); - } - - - - /// - /// The permutation chromosome should always solve the sudoku in a reasonable time with 1000 chromosomes - /// - [Test()] - public void Evolve_SimpleSudokuPermutationsChromosome_Solved() - { - var sudoku = Extensions.Sudoku.SudokuBoard.Parse(_easySudokuString); - - IChromosome chromosome = new SudokuPermutationsChromosome(sudoku); - var fitness = EvaluatesSudokuChromosome(chromosome, sudoku, 1000, 0, 50); - Assert.AreEqual(fitness, 0); - - } - - /// - /// The cells chromosome might need more individuals, so in order to keep execution time low, we only expect near completion - /// - [Test()] - public void Evolve_SimpleSudokuCellsChromosome_NearlySolved() - { - var sudoku = Extensions.Sudoku.SudokuBoard.Parse(_easySudokuString); - - //the cells chromosome should solve the sudoku or nearly in less than 50 generations with 500 chromosomes - var chromosome = new SudokuCellsChromosome(sudoku); - var fitness = EvaluatesSudokuChromosome(chromosome, sudoku, 500, -20, 30); - Assert.GreaterOrEqual(fitness, -20); - - } - - /// - /// The random permutations chromosome require more individuals and generations, so we only test for significant progresses - /// - [Test()] - public void Evolve_SimpleSudokuRandomPermutationsChromosome_Progressed() - { - var sudoku = Extensions.Sudoku.SudokuBoard.Parse(_easySudokuString); - - - //the Random permutations chromosome should make significant progresses over 3 generations with 5 individuals - - var chromosome = new SudokuRandomPermutationsChromosome(sudoku, 2, 3); - var fitness1 = new SudokuFitness(sudoku).Evaluate((ISudokuChromosome)chromosome); - var fitness2 = EvaluatesSudokuChromosome(chromosome, sudoku, 5, fitness1 + 20, 3); - Assert.GreaterOrEqual(fitness2, fitness1 + 20); - - } - - - - private double EvaluatesSudokuChromosome(IChromosome sudokuChromosome, Extensions.Sudoku.SudokuBoard sudokuBoard, int populationSize, double fitnessThreshold, int generationNb) - { - var fitness = new SudokuFitness(sudokuBoard); - var selection = new EliteSelection(); - var crossover = new UniformCrossover(); - var mutation = new UniformMutation(); - - var population = new Population(populationSize, populationSize, sudokuChromosome); - var ga = new GeneticAlgorithm(population, fitness, selection, crossover, mutation) - { - Termination = new OrTermination(new ITermination[] - { - new FitnessThresholdTermination(fitnessThreshold), - new GenerationNumberTermination(generationNb) - }) - }; - - ga.Start(); - - var bestIndividual = ((ISudokuChromosome)ga.Population.BestChromosome); - var solutions = bestIndividual.GetSudokus(); - return solutions.Max(solutionSudoku => fitness.Evaluate(solutionSudoku)); - } - - } - - - - -} diff --git a/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTestHelper.cs b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTestHelper.cs new file mode 100644 index 00000000..8d8bac69 --- /dev/null +++ b/src/GeneticSharp.Extensions.UnitTests/Sudoku/SudokuTestHelper.cs @@ -0,0 +1,46 @@ +using System.Linq; +using GeneticSharp.Domain; +using GeneticSharp.Domain.Chromosomes; +using GeneticSharp.Domain.Crossovers; +using GeneticSharp.Domain.Mutations; +using GeneticSharp.Domain.Populations; +using GeneticSharp.Domain.Selections; +using GeneticSharp.Domain.Terminations; +using GeneticSharp.Extensions.Sudoku; + +namespace GeneticSharp.Extensions.UnitTests.Sudoku +{ + public static class SudokuTestHelper + { + private static readonly string _easySudokuString = "9.2..54.31...63.255.84.7.6..263.9..1.57.1.29..9.67.53.24.53.6..7.52..3.4.8..4195."; + + public static SudokuBoard CreateBoard() + { + return SudokuBoard.Parse(_easySudokuString); + } + + public static double Eval(IChromosome sudokuChromosome, SudokuBoard sudokuBoard, int populationSize, double fitnessThreshold, int generationNb) + { + var fitness = new SudokuFitness(sudokuBoard); + var selection = new EliteSelection(); + var crossover = new UniformCrossover(); + var mutation = new UniformMutation(); + + var population = new Population(populationSize, populationSize, sudokuChromosome); + var ga = new GeneticAlgorithm(population, fitness, selection, crossover, mutation) + { + Termination = new OrTermination(new ITermination[] + { + new FitnessThresholdTermination(fitnessThreshold), + new GenerationNumberTermination(generationNb) + }) + }; + + ga.Start(); + + var bestIndividual = ((ISudokuChromosome)ga.Population.BestChromosome); + var solutions = bestIndividual.GetSudokus(); + return solutions.Max(solutionSudoku => fitness.Evaluate(solutionSudoku)); + } + } +} diff --git a/src/GeneticSharp.Extensions/Sudoku/SudokuPermutationsChromosome.cs b/src/GeneticSharp.Extensions/Sudoku/SudokuPermutationsChromosome.cs index a2f5b78b..e822e5f9 100644 --- a/src/GeneticSharp.Extensions/Sudoku/SudokuPermutationsChromosome.cs +++ b/src/GeneticSharp.Extensions/Sudoku/SudokuPermutationsChromosome.cs @@ -104,7 +104,6 @@ protected virtual int GetPermutationIndex(int rowIndex) } - /// /// This method computes for each row the list of digit permutations that respect the target mask, that is the list of valid rows discarding columns and boxes /// @@ -116,6 +115,7 @@ public IList>> GetRowsPermutations(SudokuBoard sudokuBoard) { return UnfilteredPermutations; } + // we store permutations to compute them once only for each target Sudoku if (!_rowsPermutations.TryGetValue(sudokuBoard, out var toReturn)) {