Skip to content

Commit

Permalink
Feat: unit testing (#3)
Browse files Browse the repository at this point in the history
* Extract services.
* Add unit testing project. 
  * Using Verify. With custom converter for DevToys UI elements.
  * 100% code coverage.
* Use `global.json` for .NET version.
* Run linting during CI.
* Extract packages to `Directory.Packages.props`.
  • Loading branch information
jerone authored Sep 23, 2024
1 parent 2723bc9 commit 7aea81d
Show file tree
Hide file tree
Showing 38 changed files with 4,376 additions and 153 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "0.29.1",
"version": "0.29.2",
"commands": [
"dotnet-csharpier"
],
Expand Down
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ indent_size = 4
indent_style = space
indent_size = 2

# Verify settings.
# https://github.com/VerifyTests/Verify/blob/main/docs/wiz/Windows_VisualStudioWithReSharper_Gui_Xunit_GitHubActions.md#editorconfig-settings
[*.{received,verified}.{txt,xml,json}]
charset = "utf-8-bom"
end_of_line = lf
indent_size = unset
indent_style = unset
insert_final_newline = false
tab_width = unset
trim_trailing_whitespace = false

[*.{cshtml,htm,html,razor}]
indent_style = tab
indent_size = tab
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Verify
# https://github.com/VerifyTests/Verify/blob/main/docs/wiz/Windows_VisualStudioWithReSharper_Gui_Xunit_GitHubActions.md#source-control-settings
*.verified.txt text eol=lf working-tree-encoding=UTF-8
*.verified.xml text eol=lf working-tree-encoding=UTF-8
*.verified.json text eol=lf working-tree-encoding=UTF-8
101 changes: 76 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "CI & CD: Build & Test .NET Solution, Create & Validate & Publish Nuget Package and Create Release"
name: "CI & CD: Build & Test & Lint .NET Solution, Create & Validate & Publish Nuget Package and Create Release"

on:
push:
Expand All @@ -17,7 +17,7 @@ env:
NuGetVersion: 0.0.0

jobs:
build_test:
job_build_test:
name: Build & Test
runs-on: ubuntu-latest
steps:
Expand All @@ -26,15 +26,41 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8"
global-json-file: "./global.json"

- name: Build solution
run: dotnet build

- name: Test solution
run: dotnet test --no-build
run: dotnet test --no-build --logger GitHubActions
# Logger: https://github.com/Tyrrrz/GitHubActionsTestLogger

analyze_codeql:
- name: Verify results
if: failure()
uses: actions/upload-artifact@v4
with:
name: verify-test-results
path: |
**/*.received.*
job_lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: "./global.json"

- name: Restore .NET tools
run: dotnet tool restore

- name: Linting
run: dotnet csharpier --check .

job_analyze_codeql:
name: Run CodeQL scanning
runs-on: ubuntu-latest
permissions:
Expand All @@ -55,7 +81,7 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

analyze_sonarcloud:
job_analyze_sonarcloud:
name: Run SonarCloud scanning
runs-on: windows-latest
steps:
Expand All @@ -66,7 +92,10 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8"
global-json-file: "./global.json"

- name: Install dotnet-coverage
run: dotnet tool install dotnet-coverage --global

- name: Cache SonarCloud packages
uses: actions/cache@v4
Expand Down Expand Up @@ -96,16 +125,21 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
.\.sonar\scanner\dotnet-sonarscanner begin `
/k:"jerone_Jvw.DevToys.SemverCalculator" `
/o:"jerone" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.host.url="https://sonarcloud.io" `
/d:sonar.exclusions="**/Pack/**/*.xml" `
/d:sonar.verbose=true
.\.sonar\scanner\dotnet-sonarscanner begin `
/k:"jerone_Jvw.DevToys.SemverCalculator" `
/o:"jerone" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.host.url="https://sonarcloud.io" `
/d:sonar.exclusions="**/Pack/**/*.xml" `
/d:sonar.verbose=true `
/d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml
- name: Build solution
run: dotnet build
run: dotnet build --no-incremental

- name: Test solution
run: dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml"
# https://docs.sonarsource.com/sonarcloud/enriching/test-coverage/dotnet-test-coverage/#dotnetcoverage

- name: End SonarCloud analyze
env:
Expand All @@ -115,7 +149,7 @@ jobs:
run: |
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
nuget_pack:
job_nuget_pack:
name: Pack NuGet package
runs-on: ubuntu-latest
steps:
Expand All @@ -124,7 +158,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8"
global-json-file: "./global.json"

- name: Set version variable
if: ${{ github.ref_type == 'tag' }}
Expand All @@ -146,15 +180,20 @@ jobs:
name: ${{ env.NuGetArtifactName }}
path: ${{ env.NuGetDirectory }}/*.nupkg

nuget_validate:
job_nuget_validate:
name: Validate NuGet package
runs-on: ubuntu-latest
needs: [nuget_pack]
needs: [job_nuget_pack]
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: "global.json" # Only need this file for this job.
sparse-checkout-cone-mode: false

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8"
global-json-file: "./global.json"

- name: Install nuget validator
run: dotnet tool install Meziantou.Framework.NuGetPackageValidation.Tool --global
Expand All @@ -168,16 +207,28 @@ jobs:
shell: pwsh
run: meziantou.validate-nuget-package (Get-ChildItem "${{ env.NuGetDirectory }}/*.nupkg")

nuget_publish:
job_nuget_publish:
name: Publish NuGet package
runs-on: ubuntu-latest
needs: [nuget_validate, build_test, analyze_codeql, analyze_sonarcloud]
needs:
[
job_build_test,
job_lint,
job_nuget_validate,
job_analyze_codeql,
job_analyze_sonarcloud,
]
if: github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: "global.json" # Only need this file for this job.
sparse-checkout-cone-mode: false

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8"
global-json-file: "./global.json"

- uses: actions/download-artifact@v4
with:
Expand All @@ -187,10 +238,10 @@ jobs:
- name: Publish NuGet package
run: dotnet nuget push ${{ env.NuGetDirectory }}/*.nupkg -k ${{ secrets.NUGET_APIKEY }} -s https://api.nuget.org/v3/index.json

release:
job_release:
name: Create release on GitHub
runs-on: ubuntu-latest
needs: [nuget_publish]
needs: [job_nuget_publish]
permissions:
contents: write # Needed to create a release.
steps:
Expand Down
20 changes: 20 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="CSharpier.MsBuild" Version="0.29.2" />
<PackageVersion Include="DevToys.Api" Version="2.0.5-preview" />
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.25" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Moq.Contrib.HttpClient" Version="1.4.0" />
<PackageVersion Include="Semver" Version="2.3.0" />
<PackageVersion Include="Verify.Xunit" Version="26.6.0" />
<PackageVersion Include="xunit" Version="2.9.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions Jvw.DevToys.SemverCalculator.Tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore Verify files.
# https://github.com/VerifyTests/Verify/blob/main/docs/wiz/Windows_VisualStudioWithReSharper_Gui_Xunit_GitHubActions.md#includesexcludes
*.received.*
*.received/
7 changes: 7 additions & 0 deletions Jvw.DevToys.SemverCalculator.Tests/Assembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage(
"Performance",
"CA1869",
Justification = "Do not cache JsonSerializerOptions in tests."
)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Reflection;
using DevToys.Api;

namespace Jvw.DevToys.SemverCalculator.Tests.Converters;

/// <summary>
/// DevToys element converter used for Verify tool.
/// </summary>
public class DevToysElementConverter : WriteOnlyJsonConverter<IUIElement>
{
public override void Write(VerifyJsonWriter writer, IUIElement element)
{
writer.WriteStartObject();

var type = element.GetType();

// Prepend type name.
writer.WriteMember(element, type.Name, "$type");

var props = type.GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var prop in props)
{
var name = prop.Name;
var val = prop.GetValue(element);

// Replace button `OnClickAction` value with a placeholder, instead of delegate details.
if (IsButtonClickAction(element, name, val))
{
writer.WriteMember(element, "{has click action}", prop.Name);
}
// Replace info-bar `OnCloseAction` value with a placeholder, instead of delegate details.
else if (IsInfoBarCloseAction(element, name, val))
{
writer.WriteMember(element, "{has close action}", prop.Name);
}
else
{
writer.WritePropertyName(name);
writer.Serializer.Serialize(writer, val);
}
}

writer.WriteEndObject();
}

/// <summary>
/// Detect if property is `OnClickAction` from a button.
/// </summary>
/// <param name="element">Element.</param>
/// <param name="name">Property name.</param>
/// <param name="val">Property value.</param>
/// <returns>Whether property is `OnClickAction` from a button.</returns>
private static bool IsButtonClickAction(IUIElement element, string name, object? val)
{
return element is IUIButton && name == nameof(IUIButton.OnClickAction) && val is not null;
}

/// <summary>
/// Detect if property is `OnCloseAction` from an info-bar.
/// </summary>
/// <param name="element">Element.</param>
/// <param name="name">Property name.</param>
/// <param name="val">Property value.</param>
/// <returns>Whether property is `OnCloseAction` from an info-bar.</returns>
private static bool IsInfoBarCloseAction(IUIElement element, string name, object? val)
{
return element is IUIInfoBar && name == nameof(IUIInfoBar.OnCloseAction) && val is not null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">

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

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

<ItemGroup>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CSharpier.MsBuild">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="GitHubActionsTestLogger">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="Moq.Contrib.HttpClient" />
<PackageReference Include="Verify.Xunit" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Jvw.DevToys.SemverCalculator\Jvw.DevToys.SemverCalculator.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
Loading

0 comments on commit 7aea81d

Please sign in to comment.