The Subprocess Method Injection Test Framework
SmiteUnit is a unit-testing framework for use in environments where a traditional unit-testing framework cannot be used. Common use cases include:
- Testing plugins for applications
- Testing mods for video games
- Running automated integration tests
- Traditional unit-tests run portions of a program in a seperate test application. SmiteUnit differs by injecting tests into a target application instead.
- Defined injection points dictate when tests start and stop, providing a high level of control over when and where tests run within an application.
- SmiteUnit executes the target application and the injected tests in a subprocess, capturing the standard error and standard output.
- SmiteUnit treats the target application as a black box. The original application does not need to be modified extensively to inject SmiteUnit tests.
- Test writers have complete control over where injection points are placed, along with many other specifics about how tests are injected into the application.
- SmiteUnit is designed to work well alongside other testing frameworks.
- Attribute names are prefixed with "Smite" to avoid ambiguity with other attrubutes.
- A SmiteProcess can be created to run specific tests inside a unit test written with a different framework. This is useful when validating I/O.
To start using SmiteUnit, injection points must be added to the target application, and a seperate test project should be made to hold the tests.
Injection points need to be added to the application in which the tests must be run. To do this, the SmiteUnit.Injection
package will be used.
Injection points control when the tests are started and when the tests close the program.
Where these injection points go will be different depending on the application, but will have a similar idea to this example:
using SmiteUnit.Injection;
public static class Program
{
public static void Main()
{
// Near startup a SmiteInjection object should be created and it's EntryPoint() method called.
// Create it with the name of the assembly that holds the tests.
var smiteInjection = new SmiteInjection("MyTestAssembly");
// Call the entry point methods. Tests will start running here.
smiteInjection.EntryPoint();
// If the application is interactive, there is likely some sort of update loop.
bool updateLoop = true;
while (updateLoop)
{
// Inside of this update loop, UpdatePoint should be periodically called.
smiteInjection.UpdatePoint();
}
// Finally, before the application exits, ExitPoint() should be called.
smiteInjection.ExitPoint();
System.Environment.Exit(0);
}
}
Creating a test project follows similar steps to other C# testing frameworks.
It is recomended to follow the steps to set up tests for a popular framework like NUnit.
After setting up a test project for your IDE, open up the .csproj
file,
and remove references to the popular testing framework (e.g. if NUnit's instructions were followed, delete PackageReferences to NUnit) and replace them with package references to SmiteUnit.
The package references in the .csproj will probably end up looking like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- This could be any supported framework -->
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- These package references can stay -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" GeneratePathProperty="true" />
<!-- These package references should be removed -->
<!-- <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> -->
<!-- <PackageReference Include="NUnit" Version="3.14.*" /> -->
<!-- This package reference should be added -->
<PackageReference Include="Linkoid.SmiteUnit" Version="0.3.1-alpha.0" />
</ItemGroup>
</Project>
To start writing tests with SmiteUnit, create a new class and add a [SmiteProcess]
attribue to it,
then mark test methods with the [SmiteTest]
attribute.
// The SmiteProcessAttribute tells the test adapter which application to start
[SmiteProcess("MyExecutable.exe", "arguments")]
public static class MySmiteTests
{
// The SmiteSetUpAttribute marks methods that should run before any test methods
[SmiteSetUp]
public static void MySetUpMethod()
{
Console.WriteLine("Running my set up method!");
}
// The SmiteTestAttribute marks methods that can be run as tests
[SmiteTest]
public static void MyHelloWorldTest()
{
Console.WriteLine("Hello World!");
}
}
SmiteUnit does not provide any tools for running tests, but it is compatible with Microsoft.NET.Test.Sdk
via the SmiteUnit.TestAdapter
which is automatically included in the SmiteUnit
package.
Tests should be runnable from an IDE that supports Microsoft.NET.Test.Sdk
, or can be run from the commandline with dotnet test
.
SmiteUnit works by running a test in a sub process and capturing its input and output. Somewhere in this process there is a hook that checks for a specific test that the parent process is attempting to invoke. At the injection point if a valid test is found, the test is executed and the result is reported back to the parent process. What sets this library apart from other testing frameworks is the test writer has complete control over where the injection point is. This means that if the only way your code can possibly execute properly is as an injected dependency inside of another application that perhaps doesn't even have a proper debug mode, you will still be able to run these tests in an automated fashion.
SmiteUnit is also designed to function well with other testing frameworks and it is even possible to run SmiteUnit inside of unit test Written in a different framework. This would even be the ideal use case in situations where specific input and output of the application needs to be tested for instantce standard input and standard output.
- SmiteUnit should be usable in any application where the SmiteUnit assembly can be loaded and executed.
- The application in which the test is executed must be viewed as a black box.