Empirical++: Power Tools Go Brrr...
Thanks to the software contributions of our WAVES participants and mentors, we’ve got an exciting slew of shiny new tools to show off. And you’ve got an exciting slew of shiny new things to learn about!
🔗 Prefab
Contributed by TODO.
🔗 D3
Contributed by TODO.
🔗 Interactive Tutorials
Contributed by TODO.
🔗 Installing Test Dependencies
Given the size and complexity of Empirical’s code base, how do we make sure that everything stays in working order? Tests! To get started working on an Empirical project, you’ll want to be familiar with how to run existing tests and how to write new ones.
First thing you’ve got to do is install a few dependencies
Luckily for you, we’ve got Empirical’s dependency install process automated as a make
rule in the base directory of the repo that should just work.
make install-test-dependencies
If that doesn’t just work, you can try hopping into our pre-built Docker container.
You should have better luck with make install-test-dependencies
in there.
sudo docker run -it devosoft/empirical:latest
🔗 Run All the Tests
To run our existing battery of native tests, you’ll just
cd tests
make
To run the tests for a particular source subdirectory, just cd
into the corresponding subdirectory in tests
and make
.
For example, to run the tests for base
cd tests/base
make
Those options probably take too long, though.
You’re more likely to find running the tests for a particular source file more useful.
To do that, cd
into its subdirectory and make test-BasenameWithoutExtension
.
For example, to test base/vector.h
you’d
cd tests/base
make test-vector
🔗 Anatomy of a Native Test
What’s actually going on inside these tests? Getting you to Catch the drift will REQUIRE popping one open for a closer look.
Hopefully, those puns will be very funny to you in a minute.
We use the Catch framework for our native tests.
#define CATCH_CONFIG_MAIN // Catch magic
#include "third-party/Catch/single_include/catch2/catch.hpp"
#include "tools/info_theory.h"
#include <sstream>
#include <string>
// Catch uses macro magic to string these TEST_CASE's together.
// You can provide several TEST_CASE's within the same file.
TEST_CASE("Test info_theory", "[tools]")
{
emp::vector<int> weights = { 100, 100, 200 };
// REQUIRE is a special Catch macro that must evaluate to true
// or the executable exits with an error code
REQUIRE( emp::Entropy(weights) == 1.5 );
}
🔗 Run the Web Tests
To run our existing battery of web tests, you’d
cd tests/web
make
To run the tests for a particular header, for example Widget
, just
cd tests/web
make test-Widget
There’s also DOM tests for
prefab
in addition to web
.
🔗 Anatomy of a Web Test
Includes… gotta have ‘em!
#include <functional>
#include <unordered_map>
#include "base/assert.h"
#include "web/_MochaTestRunner.h"
#include "web/Document.h"
#include "web/Element.h"
#include "web/web.h"
Today, we’re going to be
paintingcoding a happy littletreestruct. There’s nothing wrong with having atreestruct as a friend.—
Bob RossMatthew Andres Moreno
This struct will inherit from BaseTest
, which handles much of the boilerplate for us behind the scenes.
struct Test_WidgetWrapWith : emp::web::BaseTest {
Step the first: set up some DOM state in our constructor.
// Construct the following HTML structure:
// <div id="wrapper2"> <!-- inserted with WrapWith -->
// <p id="parent">
// parent
// <div id="wrapper"> <!-- inserted with WrapWith -->
// wrapper
// <button id="child"></button>
// </div>
// </p>
// <br/><br/>
// </div>
Test_WidgetWrapWith()
: BaseTest({"emp_test_container"})
{
emp::web::Element parent("p", "parent");
parent << "parent";
Doc("emp_test_container") << parent;
emp::web::Button child(
[]() {
EM_ASM({
$("#child_button").attr("clicked", "yes");
});
},
"child"
);
child.SetAttr("clicked", "no");
child.SetAttr("id", "child_button");
parent << child;
emp::web::Div wrapper("wrapper");
wrapper << "wrapper";
child.WrapWith(wrapper);
parent.WrapWith(
emp::web::Div("wrapper2").SetCSS("background-color", "red")
).SetCSS("background-color", "blue");
Doc("emp_test_container").Div("wrapper2") << "<br/><br/>";
}
Step the second: make sure the DOM state ended up the way we expected it to.
Everything inside the
EM_ASM
is actually JavaScript, not C++.
It’s built using the Mocha test framework and Chai BDD/TDD assertion library, so if you’re curious about the syntax kicking around or you want to learn even more fancy footwork take a peek over there.
void Describe() override {
// Test that the HTML components created in constructor are correct.
EM_ASM({
describe("Widget::WrapWith", function() {
describe("#wrapper2", function() {
it('should have parent #emp_test_container', function() {
const parent_id = $("#wrapper2").parent().attr("id");
chai.assert.equal(parent_id, "emp_test_container");
});
it('should have child #parent', function() {
var children = $("#wrapper2").children();
// Get ids of child
var child_ids = [];
for (i = 0; i < children.length; i++) {
child_ids.push(children[i].getAttribute("id"));
}
chai.assert.include(child_ids, "parent");
chai.assert.equal($("#wrapper2").children("#parent").length, 1);
});
});
describe("#parent", function() {
it('should have parent #wrapper2', function() {
const parent_id = $("#parent").parent().attr("id");
chai.assert.equal(parent_id, "wrapper2");
});
it('should have child #wrapper', function() {
chai.assert.equal($("#parent").children("#wrapper").length, 1);
});
});
describe("#wrapper", function() {
it('should have parent #parent', function() {
const parent_id = $("#wrapper").parent().attr("id");
chai.assert.equal(parent_id, "parent");
});
it('should have child #child_button', function() {
chai.assert.equal($("#wrapper").children("#child_button").length, 1);
});
});
describe("#child_button", function() {
it('should have parent #wrapper', function() {
const parent_id = $("#child_button").parent().attr("id");
chai.assert.equal(parent_id, "wrapper");
});
});
describe("button#child_button", function() {
it('should do stuff when clicked', function() {
const before = $("#child_button").attr("clicked");
chai.assert.equal(before, "no", "check initial clicked value");
$("#child_button").trigger( "click" );
const after = $("#child_button").attr("clicked");
chai.assert.equal(after, "yes", "check post-click clicked value");
});
});
});
});
}
Tie up our happy little struct.
};
All that’s left is to
- instantiate a
emp::web::MochaTestRunner
(ahoy, more boilerplate-hiding magics), - give it a vector of
div
’s that need to be in our HTML document before our happy little struct runs, - register our happy little struct with the test runner, and
- hit the go button.
emp::web::MochaTestRunner test_runner;
int main() {
// put a div with id emp_test_container into our HTML document
test_runner.Initialize({"emp_test_container"});
test_runner.AddTest<Test_WidgetWrapWith>("Test Widget::WrapWith");
test_runner.Run();
}
🔗 Layin’ Down the Law
Travis CI yells at us when tests don’t pass.
CodeCov yells at us when we don’t write tests. You can use their web interface to check which code lines of your pull request haven’t been tested yet.