Unit tests for classes in audio plugin

I want to start adding unit tests for classes in my plugin. For example, here is a UnitTest class I've created:

 


#include "../JuceLibraryCode/JuceHeader.h"
#include "EnumFloatParameter.h"
class EnumFloatParameterTest : public UnitTest
{
public:
    EnumFloatParameterTest() : UnitTest("EnumFloatParameterTest") {}
    void runTest()
    {
        beginTest("Test enumerated parameters");
        const String percussion[] = { "Off", "Bass drum", "Snare", "Tom", "Cymbal", "Hi-hat" };
        int n = sizeof(percussion) / sizeof(String);
        EnumFloatParameter* efp = new EnumFloatParameter("Percussion Mode", StringArray(percussion, n));
        expect(true);
        for (int i = 0; i < n; i++) {
            efp->setParameterIndex(i);
            expect(i == efp->getParameterIndex());
        }
    }
};
static EnumFloatParameterTest enumTest;

The test class is built along with the rest of the plugin. But how do I run it? As a VST, my code is built into a .dll file. Is there some way in the Introjucer to specify a Unit Test target or something like that?

If you only want to run the tests occasionally, just add a #if to your code that you can enable when you want to test them. Or if the tests are quick, maybe just run them as soon as the plugin loads in a debug build?

(Your code looks a bit odd, BTW, can't really see what you're trying to achieve by testing the parameter index like that)

Thanks for the reply Jules.

What I should have asked was, how do I use the UnitTestRunner class? And if the test is run at plugin load, how do I see the results?

About the test - the class converts the floating point values used in VST to/from an enum value. I found a bug in my code related to rounding, and this unit test covers that.

Thanks again.

Maybe have a look at the way the juce demo app runs its unit tests?

I've just gotten started with JUCE and was wondering the same. I think it should be pretty simple to add a build target that just compiles a command-line binary with a main that runs your unit tests - I figure this is a lot easier than actually having to build test functionality into a VST target.

The problem is, I tried adding such a target in XCode, but then as soon as I re-ran the Introjucer it deleted my new unit test target. So I guess that's not much of an option :-/

 

3 Likes

Yeah, I tried the same thing, except my target was configured in the introjucer. That way it will be generated as part of the project. The problem is though, you can’t make a different “type” of build. It is still an audio plugin target.

I just figured out a way around this: I made a 2nd Introjucer project that's a command line target, but it points to the same files as my plugin target except with its own main. It's not as elegant as if you could make a command line target in a plugin project, but it seems to work well enough.

I got unit tests working by adding my custom code that I want to test in .h and .cpp files to the UnitTestRunner project, using Projucer. I put my subclassed UnitTest code to a .h file and also added to UnitTestRunner project via Projucer; code is as per the comments in juce_UnitTest.h

In my UnitTest subclass have to point to where the actual code to test is, for me:

#include “./…/…/…/…/Documents/dev/juce/TailGunnerSr/Source/MixerDSP.h”

NOTE: This is ugly, I still have UnitTestRunner in the ~/JUCE folder
Now I have it working I’ll copy this project to a folder closer to my actual plugin code.

Need to add appropriate include files i.e. these are my 2 UnitTest classes (so far), so I include these in Main.cpp

#include “./MixerDSP-UnitTests.h”
#include “./More-UnitTests.h”

Just need to change the variable name at the end of the UnitTest class i.e.
static MyTestMixerDSP testDSP; // in file: MixerDSP-UnitTests.h
… and…
static MyTest2 testMore; // in file: More-UnitTests.h

Then to stop UnitTestRunner from running all the internal Juce tests I changed this in Main.cpp

if (args.size() == 0)
{

// runner.runAllTests();
runner.runTestsInCategory (“myDSP”);
runner.runTestsInCategory (“myOther”);
}

And set the actual category inside the UnitTest constructor of your subclassed UnitTest:

class MyTestMixerDSP : public UnitTest
{
public:
MyTestMixerDSP() : UnitTest (“Unit Tests: DSP - Tail Gunner Sr”, “myDSP”) {}

And since the expect() call doesn’t really tell you much when it fails I create a custom msg string, like this:

Declare msg string.

std::string msg = “”;

Can reuse ‘msg’ for all my tests.

msg = "Test 6: mixWetDry() is 70%: output = " + std::to_string(output) + ", result = " + std::to_string(result);
expect (output == result, msg);

It’s a fugly string. I was thinking I’ll make a util function that looks like:

testMsg(“Test 6: more text here”, output, result);

That spits out something like:

“Test 6: more text here. Expected X, Actual = Y”

BTW - This took me a few hours to figure out. I think I learned a few things in the process, however it was painful considering how quickly I got Jest working with a ReactJS project. Hope this helps. Feed back is welcome.

I managed to get the PluginEditor.cpp / .h and PluginProcessor.cpp / .h files added to UnitTestRunner. Had to use some #ifdef #else macros to include the JuceHeader.h that is local to UnitTestRunner from within the plugin files.

Now this causes an assertion in MyUnitTest.runTest()

TailGunnerSrAudioProcessor myProcessor;

I really just want to test the editor class, but can’t figure out how to instantiate that, since it requires a processor in the constructor.

Why? So I can do basic tests, i.e. make sure any class properties that I set are correct. i.e. myPluginScreenWidth = 600;

Hey,

That sounds good but how can I tell JUCE to run my tests as I can’t create my own main() ?


In details, I would

  1. have a test.cpp file that I add in the .jucer project sources
  2. wrap its entire content under #if JUCE_DEBUG
  3. put all my tests there (maybe include as well a test framework to ease test writing, in my case catch2)

Then I would normally define (or let catch2) declare a main() that executes the tests. But here, main() is already created for the entire (standalone) plugin. Only hacky way I see is to call it from the constructor of the plugin editor class but meh.

Thanks

PS: comments on the 3 first points are also very welcome