Creating C# assignments in CodeGrade is easy, either using mono, or by actually using .NET Core, which is available for Linux and MacOS as well nowadays. The thing is, C#projects require students to create a complete project, which makes it harder to focus on the important things: the actual code that they write. It is much easier and less error prone, especially for basic assignments, if students can just hand in the .cs files they have created instead of the whole Visual Studio project.
Now comes the interesting part: using those code files to automatically create the project structure around them. The reason we want to create the project files, is so that we can use the .NET tools to test the code and write unit tests using xUnit, one of the most popular unit testing frameworks for C# and pre-installed on CodeGrade. The .NET Core framework provides the "dotnet" command to run projects and tests. But, for the "dotnet" command to work it has to be a valid project. Since we do not want students to hand in full projects, we will have to craft the project and insert the tests automatically. This might sound difficult, but it is actually not that hard and quite fast to set up.
Before we start setting up the assignment in CodeGrade, it is usually a good idea to start locally on your own computer. There we can easily figure out how we can create the structure, test if it works, and then port it to CodeGrade when we have a clear and clean idea.
For this particular example, we will be following most of the structure from this excellent guide from Microsoft. In this example, students will create a small program that will check if a number is prime. Following the earlier guideline, students will only hand in the PrimeService.cs file, which is a simple function to determine if a number is a prime. To make sure students only hand in this file, we can use CodeGrade's hand-in instructions. The Microsoft guide walks you through setting up a whole solution, but for our purposes, that's not necessary. In the end, the simple structure we would like to end up with when running our students’ code in AutoTest is as follows:
This gives us the following tasks:
Generate the project files:
- PrimeService.csproj
- PrimeService.Tests.csproj
- Other files in the obj/ directory.
Write the unit test file:
- PrimeService_IsPrimeShould.cs
Zip the project files and upload it as a fixture to CodeGrade.
Create a simple script that generates the folder structure:
- setup.sh
Port it to CodeGrade.
Keep in mind that `PrimeService.cs` is submitted by our students and inserted into this folder structure in our AutoTest setup. So, make sure not to upload this file as a fixture.
Generating the .csproj files
Let's start with the first step, and that's creating the project files. To reiterate what was said above: We need to generate the full .NET project using our solution file, `PrimeService.cs`, so that we can run .NET commands on our students’ submissions in CodeGrade. Firstly, we can create the project files for our solution using a simple dotnet command:
-!- CODE language-console -!-dotnet new classlib -o PrimeService
This command will create the PrimeService.csproj file and insert it into a new directory called “PrimeService”. Within this directory dotnet will also create a number of other files which it needs to be recognized as a complete project. However, the command also generates its own cs file within this folder called Class1.cs which we don’t need and can be deleted.
Write the unit tests
Now let's quickly take a look at the solution file that students will hand in:
-!- CODE language-cs -!-using System;
namespace Prime.Services
{
public class PrimeService
{
public bool IsPrime(int candidate)
{
if (candidate < 2)
{
return false;
}
for (var divisor = 2; divisor <= Math.Sqrt(candidate); divisor++)
{
if (candidate % divisor == 0)
{
return false;
}
}
return true;
}
}
}
Then we can start writing some unit tests for this. First, we'll have to create the tests project and link them to the solution:
-!- CODE language-console -!-dotnet new xunit -o PrimeService.Tests
dotnet add ./PrimeService.Tests/PrimeService.Tests.csproj reference ./PrimeService/PrimeService.csproj
The first command will generate a new xUnit project folder for our tests. In it will be a PrimeService.Tests.csproj file, an obj/ directory with other necessary project files, and an empty UnitTest1.cs file which we can populate with our tests. The second command adds a reference between PrimeService.Tests.csproj and PrimeService.csproj so that our unit tests know what class to look for when run.
Now we can rename `UnitTest1.cs` to `PrimeService_IsPrimeShould.cs` and start writing our tests. For this example let's just create one simple test:
-!- CODE language-cs -!-using Xunit;
using Prime.Services;
namespace Prime.UnitTests.Services
{
public class PrimeService_IsPrimeShould
{
[Fact]
public void IsPrime_InputIs1_ReturnFalse()
{
var primeService = new PrimeService();
bool result = primeService.IsPrime(1);
Assert.False(result, "1 should not be prime");
}
}
}
We can already test this on our solution locally by simply running the "dotnet test" command.