Cross Platform Mobile and Web Development with C++ Explained

Part 3: Unit testing and other goodies

Stéphane Konstantaropoulos
The Startup
7 min readMay 14, 2020

--

The code for this part is available here https://github.com/skonstant/xptuto/tree/part3_gtest

Google Test Framework

It seems unit testing has become a religion these days. People often forget that writing tests is writing code: it increases the bug surface and the development time. I am not a unit test extremist but in our case, they come in handy.

Because I write graphical software, one feature could be a few screens and a dozen clicks away. So testing it in runtime can quickly become tedious. It is a little easier on Android where you have instant run or whatever they call it these days, and also perhaps on iOS if you enable app state preservation. I do it of course, but anyway, it is quicker to press a button in the IDE to try out a little piece of code.

We are lucky, the Google Test framework is there for us and is supported by our IDEs: QtCreator, pictured below, and CLion (and also Appcode).

GTests running in QtCreator

We have now added a folder to our codebase, we call it “test”, and we will put our tests here. Google Test is well documented. Easy to use and non intrusive. You do not need it in your repo, CMake downloads and compiles it for us. We have added a CMakeLists.txt.in file in the test folder and now we have gtest.

We defined a test suite that will test our code:

Our code has grown quite a bit by now :-) so what do we test here? First, we added a HttpClient parameter to our main class, this will do the Http GET calls only. We have not implemented it (it is in the next story). We have stubbed it.

This loads a text file and returns it in the response with HTTP code 200. In another thread because our Http client will be asynchronous all through.

So what do we test here? We test the get_users() method, it takes a callback. It makes an HTTP call, parses the reply and gives us a list of users.

We use promise / future to handle the ansynchronous-ness of the code.

We can see future waits “1s” guess what it means? This is part of std::chrono_literals.

At this point we have stayed in the C++, implemented the get_users() code, the parsing of the response and we have stubbed the Http client. All these interfaces are of course defined in our Djinni code:

Notice here something new, “+j” this means this can be implemented in Java, yes, the users callback will be used in the view layer. In the meantime we have implemented it in C++ so we can use it in tests, and we will use it in the iOS view too. Here it is:

Notice the functional stuff, this allows to use this close to the way we would use it in Java with anonymous concrete classes, thanks to the C++11 lambdas. Quick example:

It does not get more concise. Yes, I leave the std:: on purpose.

For parsing the JSON, I use the fantastic library by N. Lohmann, many thanks to him and the contributors:

It is a configure only library, if I can say, here is how you “parse” a user:

It’s all magic. And checked at compile time, if it does not know how to parse a user, it won’t compile, notice the lack of cast. It handles collections by itself too. There is support for JSON Schema validation too. I use that in my projects. Maybe in another article.

We could of course write tests to see what happens if the JSON is incorrect, mimic network errors and all that…Notice that my serializer above will crash if “login” does not exist or is not a string, e.g. There are ways to check values, check the library documentation if you want to go deeper. I was used to Gson and this is by no means inferior, and it is portable.

Run the tests

Use an IDE, you’ll go much faster, you can run the whole suite or one test at a time and you can debug them too.

Of course in this case, the tests run on your computer, you may want to run them on your target devices, I never went that route, it is complex to change the main() on iOS or Android to run what you want (it can be done), I do not see the added value. main() is not ours here, it comes from gtest, even worse.

However, on web… you can run main() directly 😁

Gtest test suite running in Chrome

How cool is that? And it’s easy (in the build folder of your tests):

emrun — browser chrome xptutotest.html

You have to load the emsdk environment first. In your emsdk folder:

source emsdk_env.sh

I load it in Chrome, you can try in Firefox too, my tests here do not run in node because they use threads, which are not supported by node yet. (Yes we have threads in the browser now, and not that worker ultra basic crap, real POSIX threads). Threads will be the topic of another article, it is complex, because we are in GUI software here.

One last goodie…

Use ccache

ccache is a cache for C compilers, it is some serious tool developped by SAMBA used by most large projects like the Linux Kernel and Android Open Source Project. It will store compiled objects so you don't have to compile them again if they are unchanged. This works much better than the XCode build folder that you have to continually delete because things stop working all of a sudden.

We add it in our CMakeLists.txt so it is used when we compile for the local machine, web and Android:

For Xcode, we need a little script that will choose ccache if it is available or switch to clang.

We then need to tell Xcode to use these commands as the C (and Objective-C) and C++ (and Objective-C++) compilers:

At the bottom of the Build settings.

And we need to tell Xcode not to use the clang modules, a non standard feature that is not supported by ccache.

You may not see a difference in the beginning but as your project grows to hundreds of files, you will feel a major difference: compilation is instant, only linking remains to be done.

ccache is available in Brew for Mac OS. It is par of all Linux distributions, it works on Windows too. And… it works with Objective-C, save time, stop watching your computer compile.

This is it for Unit testing JSON and basic threading, next we will make real ReST calls.

Also read:

Introduction:

We present our technical choice.

1. Project Setup

We will configure a project that compiles and runs on iOS/Xcode, Android Studio and Emscripten/CMake and show how to run and debug the 3 of them.

2. Pass objects around

In this one we will show how to pass objects around from the business logic layer to the various view layers.

4. ReST Client

We implement a minimal ReST client using the platform HTTP implementations.

5. Multi-Threading

Yes, you can use threads in all three environments in a portable way!

6. Offline data with SQLite

Adding SQLite database is simple as ever.

--

--

Stéphane Konstantaropoulos
The Startup

Highly skilled software engineer focussed on C++ on mobile and web.