Skip to content

Commit

Permalink
Added test project
Browse files Browse the repository at this point in the history
 - Added tests for the BasicUnscrambler
 - Added a fixture for sharing the list of words between tests
  • Loading branch information
Mnjongen committed Feb 1, 2024
1 parent 34f0f61 commit e4e5e14
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
59 changes: 59 additions & 0 deletions Unscrambler.Tests/BasicUnscramblerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Diagnostics;
using Xunit.Abstractions;

namespace Unscrambler.Tests;

[Collection("FindWords")]
public class BasicUnscramblerTests
{
private readonly ITestOutputHelper _output;
private readonly WordListFixture _fixture;

// Load words
public BasicUnscramblerTests(ITestOutputHelper output, WordListFixture fixture)
{
_output = output;
_fixture = fixture;
}

[Theory]
[InlineData("letters", 7)]
[InlineData("resources", 10)]
[InlineData("unscrambler", 12)]
[InlineData("computer", 10)]
[InlineData("programming", 6)]
[InlineData("csharp", 4)]
[InlineData("dotnet", 2)]
[InlineData("visualstudio", 5)]
[InlineData("test", 4)]

public void CheckForWords(string letters, int maxLength)
{
// Find all words using brute force
var startTime = Stopwatch.GetTimestamp();
var checkList = _fixture.FindAllWordsUsingBruteForce(letters.ToCharArray(), maxLength);
var bruteForceTimespan = Stopwatch.GetElapsedTime(startTime);

// Find all words using the Unscrambler
startTime = Stopwatch.GetTimestamp();
var unscrambled = _fixture._unscrambler.Unscramble(letters.ToCharArray(), maxLength);
var unscrambleTimespan = Stopwatch.GetElapsedTime(startTime);

// Output how long each method took

_output.WriteLine($"Brute force took {bruteForceTimespan.TotalMicroseconds} us");
_output.WriteLine($"Unscramble took {unscrambleTimespan.TotalMicroseconds} us");

// Output how many times faster unscramble is than brute force
_output.WriteLine($"Unscramble is {Math.Round(bruteForceTimespan.TotalMicroseconds / unscrambleTimespan.TotalMicroseconds, 3)} times faster than brute force");

// Check that the unscrambler found all the words
Assert.Equal(checkList.Count, unscrambled.Count);

foreach(var word in checkList)
{
Assert.Contains(word, unscrambled);
_output.WriteLine("Found: " + word);
}
}
}
1 change: 1 addition & 0 deletions Unscrambler.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
29 changes: 29 additions & 0 deletions Unscrambler.Tests/Unscrambler.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Unscrambler\Unscrambler.csproj" />
</ItemGroup>

</Project>
76 changes: 76 additions & 0 deletions Unscrambler.Tests/WordListFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Unscrambler.Basic;

namespace Unscrambler.Tests
{
[CollectionDefinition("FindWords")]
public class FindWordsCollection : ICollectionFixture<WordListFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}

/// <summary>
/// Use this class to load the word list and unscrambler once for all tests. This will speed up the tests.<br/>
/// It also moves the code for loading words, from the test file to this fixture.
/// </summary>
public class WordListFixture
{
// Url to the word list. You can update the word list by changing this url to a different word list, and removing the local file.
public const string WordListUrl = "https://github.com/dolph/dictionary/raw/master/popular.txt";
// Path to the word list. This is where the word list will be downloaded to.
public const string WordListPath = "popular.txt";

public readonly string[] _words;
public readonly BasicUnscrambler _unscrambler = new();

public WordListFixture()
{
// Check if the word list exists. If it doesn't, download it.
if (!File.Exists(WordListPath))
{
using var client = new HttpClient();
var bytes = client.GetByteArrayAsync(WordListUrl).Result;
File.WriteAllBytes(WordListPath, bytes);
}

_words = File.ReadAllLines(WordListPath);

// Add words to unscrambler
foreach (var word in _words)
{
_unscrambler.AddWord(word);
}
}

/// <summary>
/// Finds all words using brute force.
/// </summary>
public HashSet<string> FindAllWordsUsingBruteForce(char[] letters, int maxLength)
{
var words = new HashSet<string>(100);
foreach (var word in _words)
{
if (word.Length > maxLength)
{
continue;
}
var lettersCopy = letters.ToList();
var isMatch = true;
foreach (var letter in word)
{
if (!lettersCopy.Remove(letter))
{
isMatch = false;
break;
}
}
if (isMatch)
{
words.Add(word);
}
}
return words;
}
}
}
6 changes: 6 additions & 0 deletions Unscrambler.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unscrambler", "Unscrambler\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{1520FA33-A1F0-41A4-B6EE-4F24C478A422}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unscrambler.Tests", "Unscrambler.Tests\Unscrambler.Tests.csproj", "{993E5B97-E360-4F60-8AFD-E8D89C5755CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{1520FA33-A1F0-41A4-B6EE-4F24C478A422}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1520FA33-A1F0-41A4-B6EE-4F24C478A422}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1520FA33-A1F0-41A4-B6EE-4F24C478A422}.Release|Any CPU.Build.0 = Release|Any CPU
{993E5B97-E360-4F60-8AFD-E8D89C5755CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{993E5B97-E360-4F60-8AFD-E8D89C5755CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{993E5B97-E360-4F60-8AFD-E8D89C5755CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{993E5B97-E360-4F60-8AFD-E8D89C5755CD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down

0 comments on commit e4e5e14

Please sign in to comment.