I have been using Visual Studio professionally for about a decade now. Jenkins for CI/CD for the last 5 years. While the latest iterations of both are excellent tools (Jenkins pipeline, in particular), my development environment feels stagnant.
I use OSX at home, and with our migration to Github now seemed like the perfect time to try something different. What follows is a brain-dump of stuff I’ve done with a recent github project, nng.NETCore- .Net bindings for nng.
dotnet CLI
I’d previously used MonoDevelop on OSX. Post Microsoft acquisition it was rebranded Visual Studio for Mac. While more comfortable than Apple’s Xcode, it always feels… clunky. Enter the dotnet CLI:
mkdir l0core
cd l0core
# Create l0core.sln
dotnet new sln
# Create l0core/l0core.csproj
dotnet new console --name l0core
# Add l0core.csproj to l0core.sln
dotnet sln add l0core
# Add a bunch of existing projects
dotnet add l0core package <package_name>
One thing that surprised me was although you can add an existing project to the sln with:
dotnet sln add ../layer0/Layer0Shared/
You can’t easily add a project reference, for example:
# NB: This doesn't work
# In l0core.csproj add reference to Layer0Shared.csproj
dotnet add l0core reference Layer0Shared
Instead, you have to refer to the actual project:
dotnet add l0core reference ../layer0/Layer0Shared/
In Visual Studio I’m used to adding a project to the solution, then adding references to that project.
Visual Studio Code
Visual Studio Code has been my go-to text editor for over a year now. Especially for editing markdown.
After creating the project, VS Code C# guide mentions debugging and stuff that didn’t work. Down at the bottom, the FAQ mentions .Net: Generate Assets for Build and Debug
. That failed with:
Could not locate .NET Core project. Assets were not generated.
Seems to be this bug. In any case, closing and restarting VS Code seemed to fix the issue.
Start Debugging and immediately failing was great:
Starting with 1.26 (now on 1.27.1), after a few weeks using it as my main C# IDE, my gripes are:
- Intellisense stops working frequently (need to Cmd+Shift+p >
restart omnisharp
) - No “tasks” window
- Starting debugger or running tests get stuck. End up
killall dotnet
and restarting VS Code - No XML doc assistance (this extension is all there is). In regular Visual Studio:
-
///
starts comment block, and hitting return indents and inserts///
-
XmlDoc tags like
<see>
auto-complete and use intellisense
-
- Non-trivial yet common types (e.g.
System.Assembly
) take debugger a while to evaluate - Lacks some source code analysis/linting; unneeded
using
statements, namespace scoping, etc.
Appveyor
Setup Appveyor for both Windows and Linux builds of nng.NETCore.
After adding the project via the web interface, either commiting an empty appveyor.yml
or clicking NEW BUILD will trigger a build and tests.
Failed with:
msbuild "C:\projects\nng-netcore\nng.NETCore.sln" /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
Microsoft (R) Build Engine version 14.0.25420.1
Copyright (C) Microsoft Corporation. All rights reserved.
C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj(1,1): error MSB4041: The default XML namespace of the project must be the MSBuild XML namespace. If the project is authored in the MSBuild 2003 format, please add xmlns="http://schemas.microsoft.com/developer/msbuild/2003" to the <Project> element. If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.
Appveyor defaults to using msbuild 14.0 which doesn’t support the latest project format. Change appveyor.yml
to:
image: Visual Studio 2017
git push
(or NEW BUILD ):
C:\Program Files\dotnet\sdk\2.1.401\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(198,5): error NETSDK1004: Assets file 'C:\projects\nng-netcore\nng.NETCore\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj]
Done Building Project "C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj" (default targets) -- FAILED.
Need to restore packages:
before_build:
- dotnet restore
To reduce the amout of build log-spam:
build:
verbosity: minimal
Testing with xUnit
Our current project uses NUnit so I’m pretty comfortable with that. xUnit seems to be the only recommended testing framework that isn’t MSTest.
I like using parameterized tests to run variants and cover multiple code paths. A handful of blog posts cover how to do that:
class TransportsClassData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { "ws://localhost:23123" };
yield return new object[] { "tcp://localhost:23124" };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Theory]
[ClassData(typeof(TransportsClassData))]
public async Task Basic(string url)
{
// ...
}
Needed the equivalent of Nunit’s SetupFixtureAttribute to perform one-time setup like loading the nng native/unmanaged library:
public class NngCollectionFixture
{
public NngCollectionFixture()
{
// ...
}
}
[CollectionDefinition("nng")]
public class NngCollection : ICollectionFixture<NngCollectionFixture>
{
}
[Collection("nng")]
public class BusTests
{
public BusTests(NngCollectionFixture collectionFixture)
{
// ...
}
}
Appveyor automatically picked up all the tests without any additional configuration. See Running tests for more information.
Code Coverage
Using codecov for code coverage. The following references cover everything:
- https://www.appveyor.com/blog/2017/03/17/codecov/
- https://github.com/codecov/example-csharp
- https://github.com/OpenCover/opencover/wiki/Usage
In all projects you want to enable code coverage add:
<PropertyGroup>
<DebugType>full</DebugType>
</PropertyGroup>
In appveyor.yml
:
before_build:
- dotnet restore
- choco install opencover.portable
- choco install codecov
test_script:
- OpenCover.Console.exe -register:user -target:dotnet.exe -targetargs:test -output:".\coverage.xml" -oldstyle
after_test:
- codecov -f "coverage.xml" -t 2a07cd3d-8620-4495-8c14-3252b10b90bd
- Use Chocolatey to install OpenCover
- Run
dotnet test
with OpenCover - Upload results to codecov
Linux
Round out the CI with a Linux build and both debug/release configurations:
image:
- Visual Studio 2017
- Ubuntu
configuration:
- Debug
- Release
Commands can be prefixed with cmd:
or sh:
for Windows-only and Linux-only commands, respectively. With neither runs on both. The Chocolately stuff slows down builds quite a bit (adds a few minutes), so only run code coverage on Windows:
before_build:
- dotnet restore
- cmd: choco install opencover.portable
- cmd: choco install codecov
test_script:
- cmd: OpenCover.Console.exe -register:user -target:dotnet.exe -targetargs:"test --filter platform!=posix" -output:".\coverage.xml" -oldstyle
- sh: dotnet test --filter "platform!=windows"
Avoid platform-specific tests with --filter platform!=XYZ
. In the test code:
[Fact]
[Trait("platform", "windows")]
public void WindowsOnlyTest()
{
// ...
}
See Filter Option Detailsand Running Selective Unit Tests for additional options.
Deployment
Branches shows how to do branch dependent build configuration without creating multiple appveyor.yml
files. Deployment covers general deployment topics.
Wanted to limit deployment to specifc branches when there was a git tag. Pushing artifacts to Github releases shows the appveyor configuration which I then adapted to Nuget:
deploy:
provider: NuGet
api_key:
secure: OSKjxq8SQmVX8UaVkgaq1aUeGnuXHiTzNZoIi2VR0OMCp/WypCkBY7JbkmoKz497
artifact: /.*\.nupkg/
on:
branch: master
appveyor_repo_tag: true
The api_key
value comes from encrypting my nuget upload token.
The obvious problem being I can’t nuget sign
without my private key. Might have to settle for deploying to Github releases for now.
Flair
No github project is complete without flair.
[![NuGet](https://img.shields.io/nuget/v/PACKAGE.svg?colorB=COLOR)](https://www.nuget.org/packages/PACKAGE)
from the shields.io examples:
[![Build status](https://img.shields.io/appveyor/tests/USERNAME/PROJECT/BRANCH.svg)](https://ci.appveyor.com/project/USERNAME/PROJECT/branch/BRANCH)
from Codecov.io repo Settings > Badge :
[![codecov](https://codecov.io/gh/GITHUB_OWNER/PROJECT/branch/BRANCH/graph/badge.svg)](https://codecov.io/gh/GITHUB_OWNER/PROJECT)
Docker
While working on OSX it would be convenient to be able to build/test on Windows and Linux without having to push and wait for Appveyor. Enter Docker.
Create Dockerfile
:
FROM microsoft/dotnet:2.1-sdk
WORKDIR /app
COPY . ./
RUN dotnet restore
RUN dotnet test tests --filter platform!=windows
Run:
docker build -t dotnetapp-dev .
Have experimented with Docker in the past. While it was certainly cool, never really went “all in” and made it part of our development pipeline. Now that Windows is supported as a first-class citizen I’ll be giving it another, more serious look.
Top comments (4)
Are those your production codecov and nuget keys/tokens? If so, you'd better expire them since they are now public.
Have to admit, I was morbidly curious if anyone would say something about tokens that looked real.
Last time I used something obviously fake. Appveyor (and seemingly every other github et al CI/CD service) provides a way to encrypt sensitive strings like API keys. Not sure how secure it really is, so after I tried it out I changed the token. ;)
For codecov, it's actually the real report upload token. But for public projects such as this you can upload reports without it. Guess they figure there's not much harm that can come from people maliciously uploading fake reports. But that way it doesn't really matter if it gets commited to a public repository.
Cool. You seem like an experienced dev so I figured you knew better but we all make mistakes. Good post.
I'm certainly due for a mistake (assuming I haven't made one already).
Thanks for the comments.