Since I can't publicly share 99.999% of my code on GitHub, here's a simple Hello World program in C# to show you how not to code.
Realistically, this is a MarkDown project about problem solving with snippets of C#, exposeing how bloody silly and frustrating coding can be.
Technically its the default project template for a C# Console App, but we can't 💯% test that. A crying shame, because this is ostensibly a 1 line program thanks to Top Level Statements. But the whole thing unravels quickly if you have the audacity to test it.
#TLDR Probably don't.
So to make it testable, we add a test project.
- Add an
XUnit
test project. - Add a reference the
HelloWorld
project.Error: Main is inaccessible, your tests can't see it.
This is frustrating, but by default Top Level Statements gives you this implementation under the hood:
internal class Program
{
static void Main(string[] args)
{
// This is a simple C# program that prints "Hello, World!" to the console.
Console.WriteLine("Hello, World!");
}
}
The internal
for Program
looks promising, but sadly Main
defaults to private
. That's our problem.
So we lose our beautiful 1-liner, just so we can set Program
and Main
to public
.
Cool, now we can add tests and they'll pass.
Pro Tip:
If you're doing TDD, remember to pretend you wrote the failing tests before you wrote the code to make them pass.
namespace HelloWorld.Tests;
public class HelloWorldTests
{
[Fact]
public void HelloWorld_Prints_HelloWorld()
{
// Arrange
var writer = new StringWriter();
Console.SetOut(writer);
// Act
Program.Main(default!);
// Assert
Assert.Equal("Hello, World!", writer.ToString().Trim());
}
[Fact]
public void HelloWorld_Never_Wawaweewah()
{
// Arrange
var writer = new StringWriter();
Console.SetOut(writer);
// Act
Program.Main(default!);
// Assert
Assert.NotEqual("Wawaweewah!", writer.ToString().Trim());
}
}
Great! Now everything is public
. 🤦♂️ Inconceivable!
We'll fix that later.
The easy, but very expensive way to do this is to buy a Visual Studio Enterprise license and switch Code Coverage on. If you prefer to save a few grand a year, you're not going to do that.
Instead, we'll use Coverlet
to generate code coverage metrics when we run our tests and use ReportGenerator
to show us the results.
-
Add the
Coverlet.MSBuild
NuGet package to the both projects.dotnet add package coverlet.msbuild
-
Install the
ReportGenerator
tool:dotnet tool install -g dotnet-reportgenerator-globaltool
-
Run the tests and collect coverage using the dotnet test command with an absolute path.
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput="$(pwd)/coverage/"
-
Run the
ReportGenerator
tool:dotnet reportgenerator "-reports:HelloWorld.Tests/coverage.opencover.xml" "-targetdir:coveragereport" "-reporttypes:Html"
Amazing, 💯% code coverage!
Pro Tip:
If it doesn't generate coverage.opencover.xml
, you've successfully reproduced my results. Somethings broken and needs fixing. Good luck!
Congratulations, you're a real Software Engineer now!
Technically, (nobody cares, but...) public
is bad. Okay, so is chasing 💯% code coverage. But it's ONE LINE of code, so 100% isn't crazy!
Anyway, since public
compromised encapsulation and security, we should really make them both internal
, and give exclusive access only to our tests.
Applying some compiler wizardry 🧙♂️ in an AssemblyInfo.cs
, we specify InternalsVisibleTo
to make it so.
[assembly: InternalsVisibleTo("HelloWorld.Tests")]
Problem Solved! But adding AssemblyInfo.cs
has really screwed up our 1-liner and dropped the code coverage below 💯%. Inconceivable!
Ironically, adding AssemblyInfo.cs
to facillitate testing the code to 💯%, adds a bit of code we can't bloody test!
This impacts the code coverage, so we exclude that from code coverage.
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[HelloWorldCSharp]*AssemblyInfo.cs"
Much of this sillyness can be avoided, by:
- Creating an
internal
HelloWorld class - Not chasing minimalism and 💯% code coverage
- Buy a Visual Studio Enterprise license
Technically, Main
is the entrypoint of Program
, so testing it is technically an End-to-End test anyway!
Of course, all of this is inconceviable! for a Hello World app and all of this is very VERY silly.
If you don't like sillyness, probably don't write code. It's sillyness all the way down!