Any better way to write unit tests in JUCE


#1

I was trying to create unit tests for the classes I’m writing. Looking at the UnitTest and UnitTestRunner classes this is what I’ve figured out. Wanted to know if this is the normal way of doing it in JUCE or is there a better way.

Initially I was thinking I’ll create a separate project for unit tests. But that didn’t work since the classes to test was in another project which used one ‘…/JuceLibraryCode/JuceHeader.h’ and the tests used another which caused conflits while using the classes in test project.

So I’ve modified the main application project itself as below.

  • Added a new macro JUCEApplication implementation file
  • Use the macro in JUCEApplication::initialise
  • Run the tests if its set to 1
  • Else create the MainWindow and start the application.

But the problem with this is, it forces me to add the macro in all the class files. Is there a way to setup the macro once which can be used across different class files? May be there is altogether a better way to do this. But I haven’t figured that out yet.

I wanted to run the test only when I wanted and not whenever I compile and run the application.


#2

that is called googletest


#3

Use Catch. I use it a lot and highly recommend it. Some example tests here.

Main advantages:

  • single header file (googletest requires compiling and linking against a library)
  • Instead of writing things like expectGreaterThan(a, b), you just write REQUIRE(a > b). When the test fails, it puts the actual values in the expression and shows that in the output, something like: REQUIRE(5 > 9) failed.
  • Instead of writing expectWithinAbsoluteError(1.0/3, 0.3333333, 0.00001), you just write REQUIRE(1.0/3 == Approx(0.3333333)).

#4

I was thinking not to add any more dependencies. Also UnitTestRunner would be sufficient for my needs.
But the problem is how to control the execution of tests. Wanted them to run only when I require. Using these test frameworks (googletest or Catch) doesn’t seems to solve the above problem.

May be I should just add a universal file with those macro definitions and use it across the different classes.

I wanted to know if there is a way to do this with Projucer may be add that in the AppConfig.h such that it doesn’t get removed when project settings are changed later using Projucer. That can save the creation of universal file and including it everywhere.


#5

I liked the method that @gbevin showed in his recent ADC talk - https://www.youtube.com/watch?v=jq7zUKEcyzI

from memory, something like:

void MyApp::initialise(const String& commandLine)
{
   bool runTests = false;
   
#ifdef JUCE_DEBUG
   // default to running tests in debug builds; disable on cmdline 
   runTests = (! commandLine.contains("--disable-tests"));
#else
   // in release mode, default to not running tests.
   runTests = commandLine.contains("--enable-tests");
#endif

   if (runTests)
   {
      UnitTestRunner testRunner;
      testRunner.runAllTests();
   }
   // ...rest of the init method...

}

Tests are always available, automatically run in debug builds unless you turn them off on the command line, and have the opposite behavior in release builds.


#6

yeah Catch is good too (but lacks support for death tests).

And you need to run tests both in debug and release, don’t do the mistake to only run tests in debug please. You will pay that sooner or later, i in fact did.


#7

I use catch as well. Have your main program compile to a static lib, and then have a test target with your tests, and a plugin/program target. Link these against your static lib, you can now run either your tests in whatever configuration (debug, x64/x32) or just compile your main program.

Compile and iteration times are also substantially reduced, whether you test out the program or just run tests.


#8

This is why I can’t wait for @McMartin to finish the CMake support.
It’s nice that JUCE is producing CMakeLists files, but they change all the time, so difficult to add something. With his project, then we can have a native CMake project on which you can easilly add a test project.


#9

That sounds good. Adding command line is an option. Simplifies automation/CI.

But that is going to add all the tests to the final application which I’m going to ship.
Adding macro is the only way to remove the tests from final application IMO. Problem with adding the macro is you will have to include that in all the files.
Now I’ve change two settings to enable tests, the command line argument and the macro. Or I’ll have to depend solely on macros to enable tests.

I guess if Projucer has a way to define macros to be added in to one of the header files like AppConfig.h, that would simplify the configuration. There is one which adds the definition into to the project configuration which makes it difficult to modify. If its was in a header file, it would be easier to modify.

I don’t need tests to be enabled always.


#10

Mine is a standalone application for both Mac and Windows (may be for Linux later). I’m using Projucer to create the different projects for Xcode and VS.

I’m yet to figure out how to have a main project with sub-projects, like main application project and static lib project, to be created in Projucer. And a third project, the test project, which uses the static lib created by the other project to be linked and used.


#11

In AppConfig.h, there is a section delimited with // [BEGIN_USER_CODE_SECTION] and // [END_USER_CODE_SECTION] where you can put such macros. Projucer will keep that section when you save your project.