Let’s build something Outrageous – Part 11: Testing

Helene has been testing software since we met in college back in 1999. Today she is the head of QA at S&P Market Intelligence managing hundreds of people. I don’t know how she does it but she is the kind of person that breaks every piece of software she touches… even when she doesn’t want to… like the in-flight entertainment system on a long flight to France. About 15 years ago, I made a terrible mistake and asked her to test a web application I had written. She ripped it apart in minutes. I felt like the worst developer in the world, and I probably wasn’t far off. I never wanted to feel that way again, so I got better at writing tests. Now I’m only ranked second worst, right after that dev who doesn’t test at all.

For RageDB, I started testing by writing some unit tests using Catch2. Let me show you one for the Node class. First we are going to create 3 nodes. A node that just gets initialized called “nothing”, an “empty” node with just a type and key, and a “with_properties” node with a bunch of properties:

#include <catch2/catch.hpp>
#include <sstream>
#include "../src/graph/Node.h"

SCENARIO( "Node can be created", "[node]" ) {
    GIVEN("Given Nothing") {
        ragedb::Node nothing;
        ragedb::Node empty(1, "User", "Helene");
        std::map<std::string, std::any>  properties;
        properties.emplace("name", std::string("max"));
        properties.emplace("age", int64_t(42));
        std::vector<bool> valid {true, true, false, true};
        properties.emplace("valid", valid[0]);
        properties.emplace("weight", 239.5);
        properties.emplace("sane", valid);
        std::vector<int64_t> hairs;
        hairs.emplace_back(0);
        hairs.emplace_back(5000);
        properties.emplace("hairs", hairs);
        std::vector<double> shoe_sizes;
        shoe_sizes.emplace_back(10);
        shoe_sizes.emplace_back(10.5);
        properties.emplace("shoe_sizes", shoe_sizes);
        std::vector<std::string> nicknames;
        nicknames.emplace_back("maxie");
        nicknames.emplace_back("doogie");
        properties.emplace("nicknames", nicknames);

        sol::state lua;

        ragedb::Node with_properties(2, "User", "Max", properties);

With that in place, now we can test a few things. Let’s start with the “nothing” node which should have nothing.

        WHEN("things are requested from a nothing node") {
            THEN("we get the right things back") {
                REQUIRE(nothing.getId() == 0);
                REQUIRE(nothing.getType().empty());
                REQUIRE(nothing.getKey().empty());
                REQUIRE(nothing.getProperties().empty());
                REQUIRE(nothing.getPropertiesLua(lua.lua_state()).empty());
            }
        }

That’s an easy one but not very interesting to be honest. Let’s see an empty node next:

        WHEN("things are requested from an empty node") {
            THEN("we get the right things back") {
                REQUIRE(empty.getId() == 1);
                REQUIRE(empty.getType() == "User");
                REQUIRE(empty.getKey() == "Helene");
                REQUIRE(empty.getProperties().empty());
                REQUIRE(empty.getPropertiesLua(lua.lua_state()).empty());
            }
        }

Nothing too crazy there either. Now we test the node with properties:

        WHEN("things are requested from a node with properties") {
            THEN("we get the right things back") {
                REQUIRE(with_properties.getId() == 2);
                REQUIRE(with_properties.getType() == "User");
                REQUIRE(with_properties.getKey() == "Max");
                REQUIRE(!with_properties.getProperties().empty());
                REQUIRE(!with_properties.getPropertiesLua(lua.lua_state()).empty());
                REQUIRE(std::any_cast<std::string>(with_properties.getProperty("name")) == "max");
                REQUIRE(std::any_cast<int64_t>(with_properties.getProperty("age")) == 42);
                REQUIRE(std::any_cast<double>(with_properties.getProperty("weight")) == 239.5);
                REQUIRE(std::any_cast<std::_Bit_reference>(with_properties.getProperty("valid")));
                REQUIRE(std::any_cast<std::vector<std::string>>(with_properties.getProperty("nicknames")) == nicknames);
                REQUIRE(!with_properties.getProperty("not_there").has_value());
            }
        }

Don’t worry I’m not going to copy and paste every single test I wrote. Do you see that weird any_cast to a _Bit_reference for what should have been a boolean? std::any keeps them as a std::vector<bool> which for some crazy reason get turned into that bit_reference thing. I don’t quite understand why that is, but vector<bool> seems to be some kind of performance optimization wackiness.

I kept going along and decided to see how far my code coverage got on coveralls:

https://coveralls.io/github/ragedb/ragedb

Only 19% Oh no, this is going to take a while. I soon realized I wasn’t really getting the most bang for my buck, so I decided to build an integration test suite before getting too far into unit tests. I thought I would have to go to Ruby and use a testing framework there, but I looked around and found “rest-assured” in Java and decided to just go with that. The tests look like this:

        given().
                spec(requestSpec).
        when().
                body("{ \"name\" : \"max\", \"age\" : 42 }").with().contentType(ContentType.JSON).
                post("/db/rage/node/User/Max").
        then().
                assertThat().
                statusCode(201).
                contentType(equalTo("application/json")).
                body("type", is("User")).
                body("key", is("Max")).
                body("properties.age", is(42)).
                body("properties.name", is("max"));

I wrote a bunch of tests last week and added them to a new github repository “rage-assured“. Both writing tests and fixing bugs found by those tests are sure to induce rage, so I think the name is fitting. This is what I have so far:

If you are a Java developer and don’t want to mess around with the C++ stuff but still want to help out on this project, now is your chance to shine by helping me write some integration tests! Doesn’t that sound like a fun thing to spend your nights and weekends on? By the way, the “nights” parts of that for side projects is a terrible idea. I spent way too much time late at night bashing my head against the wall trying to fix bugs that were painfully obvious early next morning.

But find bugs I did. Take a look at this commit history:

Specially that “fix node delete bug” followed by a “real fix to node deletion bug”. Oh yeah, fun times, let me tell you.

Anyway, I am starting to get comfortable enough to look at releasing a 0.0.1 version very soon, but you don’t have to wait, you can grab the latest from Docker Hub and play around today. Don’t forget to join me on Slack… and follow the project on twitter under the username @rage_database. Timeline of the project is below. Until next time!

Tagged , , , ,

Leave a comment