Skip to content

Unit tests

KnitPkg encourages the use of unit tests to validate packages.

Unit tests are small, repeatable checks that verify specific behaviors of your package code. They help you catch regressions early, and—when well named—act as executable documentation for how your public API is supposed to behave.

This is especially valuable for packages because packages are meant to be reused as dependencies: when you publish a new package version, you want confidence that core behaviors still work as expected.


Where unit tests live

Unit tests should be placed under the project directory:

  • tests/

And they must be listed in your manifest under compile: so they get compiled automatically:

compile:
  - tests/UnitTests.mq5

How KnitPkg unit tests work in practice

In practice, a KnitPkg unit test is a MetaTrader Script (.mq5):

  1. Compile the test script (for example using kp compile).
  2. Run the compiled script inside MetaTrader by attaching it to a chart.
  3. Read the results in the MetaTrader console output (the Experts tab) to see which tests passed or failed.

This approach is intentionally simple: it relies on MQL + printing to the Experts log.

Current state of test support

Unit tests in KnitPkg do not yet have rich “test runner” features such as automatic discovery, structured reports, or IDE integration. Better support is planned, but today the workflow is manual and lightweight.


Test skeleton generated by kp init

The command kp init can generate a basic test scaffold. Below is an example of how a unit test script can be structured:

ExampleUnitTest.mq5
//+------------------------------------------------------------------+
//|                                                    UnitTests.mq5 |
//|                                 Unit Tests for Package autoctest |
//|                                      Organization: douglasrechia |
//+------------------------------------------------------------------+
#property copyright   "<Add copyright here>"
#property link        "<Add link here>"
#property description ""
#property description "Version: 1.0.0"
#property description ""
#property description "Description: Unit tests for package autoctest"
#property description "Organization: douglasrechia"
#property description "Author: Doug"
#property description "License: MIT"
#property description ""
#property description "Powered by KnitPkg for MetaTrader"
#property description "https://knitpkg.dev"

// Include the headers under test
#include "../knitpkg/include/douglasrechia/autoctest/Header.mqh"

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestName()
  {
// Add your test code here. Rename this function to a descriptive name.
// Create more test functions like this as needed.
   return false;
  }
//+------------------------------------------------------------------+
//| DoTests                                                          |
//+------------------------------------------------------------------+
void DoTests(int &testsPerformed,int &testsPassed)
  {
   string testName="";

//--- TestName
   testsPerformed++;
   testName="TestName";
   if(TestName())
     {
      testsPassed++;
      PrintFormat("%s pass",testName);
     }
   else
      PrintFormat("%s failed",testName);

//---
// Add more tests here as needed
  }
//+------------------------------------------------------------------+
//| UnitTests()                                                      |
//+------------------------------------------------------------------+
void UnitTests(const string packageName)
  {
   PrintFormat("Unit tests for Package %s\n",packageName);
//--- initial values
   int testsPerformed=0;
   int testsPassed=0;
//--- test distributions
   DoTests(testsPerformed,testsPassed);
//--- print statistics
   PrintFormat("\n%d of %d passed",testsPassed,testsPerformed);
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   UnitTests("autoctest");
  }
//+------------------------------------------------------------------+

The three key parts are highlighted:

  • Code under test (line 20): include the header under test with the standard MQL #include directive.
  • Test function (lines 25–30): a bool function that returns true for pass, false for fail.
  • Test invocation (lines 38–47): the code that calls the test, updates counters, and prints the result.

Each new test you add must follow the same pattern: define a new bool test function, then add a corresponding call inside DoTests(...).


How to write good unit tests for packages

Even with a simple harness, you can write effective tests by following a few principles:

  • Test behavior, not implementation details Focus on public functions/types and observable results. This keeps tests more stable when you refactor.

  • Keep tests readable A common structure is “Arrange, Act, Assert” (AAA): set up inputs, call the code, verify the outcome.

  • Name tests as executable documentation Prefer descriptive names that express: what is tested, under which scenario, and what is expected.

  • Prefer small, focused tests When a test is trying to prove too many things at once, failures become harder to interpret and maintenance gets unpleasant.


Real examples

Examples of unit tests in the bar package:

Example of unit tests in the calc package:

About current repository layouts

In calc and bar, unit tests currently live under src/, but this is considered a mistake. In a future version, those tests will be moved to tests/ to match the recommended layout described in this document.