Skip to content

Commit

Permalink
Merge pull request #42 from bxparks/develop
Browse files Browse the repository at this point in the history
1.2 - overloaded 2-argument versions of test() and testing(); new gtest.h macros
  • Loading branch information
bxparks authored Dec 1, 2018
2 parents e56fd89 + f8e2e44 commit ca0c6d9
Show file tree
Hide file tree
Showing 122 changed files with 1,536 additions and 562 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@
*.exe
*.out
*.app

# Microsoft Visual Studio
/.vs
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

* 1.2 (2018-12-01)
* Add overloaded 2-argument versions of test() and testing() macros to match
the TEST() macro in Google Test which takes 2 arguments.
* Merge 4 new Google Test adapter macros into gtest.h.
* Add documentation of gtest.h to README.md.
* 1.1.1 (2018-10-18)
* Work around compiler crash on Arduino Core >= 1.6.22 (IDE >= 1.8.6)
due to
Expand Down
252 changes: 210 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ AVR, ESP8266, ESP32 and Teensy platforms. The sister AUniter project provides
command line tools to verify, upload and validate the unit tests. The AUniter
tools can be used in a continuous integration system like Jenkins.

Version: 1.1.1 (2018-10-18)
Version: 1.2 (2018-12-01)

[![AUniter Jenkins Badge](https://us-central1-xparks2018.cloudfunctions.net/badge?project=AUnit)](https://github.com/bxparks/AUniter)

Expand Down Expand Up @@ -99,6 +99,9 @@ Here are the features in AUnit which are not available in ArduinoUnit 2.2:
* Approximate comparisons:
* `assertNear()`
* `asssertNotNear()`
* `test()` and `testing()` macros support both 1 and 2 arguments
* `test(testName)` and `test(suiteName, testName)`
* `testing(testName)` and `testing(suiteName, testName)`
* Test fixtures using the "F" variations of existing macros:
* `testF()`
* `testingF()`
Expand All @@ -117,8 +120,8 @@ Here are the features in AUnit which are not available in ArduinoUnit 2.2:
* `TestRunner::include(testClass, name)`
* `TestRunner::exclude(testClass, name)`
* Terse and verbose modes:
* `#include <AUnit.h>` - terse messages uses less flash memory
* `#include <AUnitVerbose.h>` - verbose messages uses more flash memory
* `#include <AUnit.h>` - terse messages use less flash memory
* `#include <AUnitVerbose.h>` - verbose messages use more flash memory
* Tested on the following Arduino platforms:
* AVR (8-bit)
* Teensy ARM (32-bit)
Expand Down Expand Up @@ -239,21 +242,23 @@ macros are used to create a test:

* `test(name) {...}` - creates a subclass of `TestOnce`
* `testing(name) {...}` - creates a subclass of `TestAgain`
* `test(suiteName, name) {...}` - creates a subclass of `TestOnce`
* `testing(suiteName, name) {...}` - creates a subclass of `TestAgain`
* `testF(classname, name) {...}` - creates a subclass of `classname`
* `testingF(classname, name) {...}` - creates a subclass of `classname`

The code in `{ }` following these macros becomes the body of a method in a
subclass derived from the base class indicated above. The `test()` and `testF()`
macros place the code body into the `TestOnce::once()` method. The `testing()`
and `testingF()` macros place the code body into the `TestAgain::again()`
method. The name of the subclass is a concatenation of the string `"test_"` and
the `name` for `test()` and `testing()`, or the concatenation of
`classname` + `"_"` + `name` for `testF()` and `testing()`.
method.

The argument to these macros are the name of the test case, and is used to
generate a name for the subclass. (The name is available within the test code
using the `Test::getName()` method). The macros also generate code to create an
global instance of the subclass, which are static initialized by C++.
The `test()` and `testing()` macros support 1 or 2 arguments. The one-argument
version is inherited from ArduinoUnit. The two-argument version is
analogous to the `TEST()` macro in GoogleTest, where the `suiteName` can
be used to organize multiple tests into a collection of similar tests. The
grouping is purely in the naming scheme of the generated code, there is no
functional relationship between these tests.

During static initialization, the constructor of the object adds itself to an
internal list. The root of that list is given by `Test::getRoot()`. The
Expand All @@ -268,11 +273,17 @@ Here is a rough outline of an AUnit unit test sketch:
#include <AUnit.h>
using namespace aunit;

test(example_test) {
...assertXxx()...
test(example) {
...
assertXxx(...)
...
}

test(ExampleTest, example) {
...
}

testing(looping_test) {
testing(looping) {
...code...
if (...) {
pass();
Expand All @@ -283,6 +294,10 @@ testing(looping_test) {
}
}

testing(LoopingTest, looping) {
...
}

class CustomTestOnce: public TestOnce {
protected:
// optional
Expand Down Expand Up @@ -348,10 +363,39 @@ void loop() {
```
***ArduinoUnit Compatibility***: _The basic structure of the unit test is
identical to ArduinoUnit. AUnit adds the `testF()` and `testingF`() macros which
identical to ArduinoUnit. AUnit adds the `testF()` and `testingF`() macros,
and the two-argument versions of `test()` and `testing()` which
are not available in ArduinoUnit. The `Test` class in ArduinoUnit has been
replaced with the `TestAgain` class in AUnit._
### Generated Class and Instance Names
The arguments to the various `test*()` macros are used to generate the name for
the subclasses of `TestOnce` or `TestAgain`, and generate the names of the
instances of those classes. For reference, here are the rules:
* `test(name)`
* class: `"test_"` + name
* instance: `"test_"` + name + `"_instance"`
* `testing(name)`
* class: `"test_"` + name
* instance: `"test_"` + name + `"_instance"`
* `test(suiteName, name)`
* class: `suiteName` + `"_"` + name
* instance: `suiteName` + `"_"` + name + `"_instance"`
* `testing(suiteName, name)`
* class: `suiteName` + `"_"` + name
* instance: `suiteName` + `"_"` + name + `"_instance"`
* `testF(className, name)`
* class: `className` + `"_"` + name
* instance: `className` + `"_"` + name + `"_instance"`
* `testingF(className, name)`
* class: `className` + `"_"` + name
* instance: `className` + `"_"` + name + `"_instance"`
The instance name is available within the test code using the `Test::getName()`
method.
### Binary Assertions
Inside the `test()` and `testing()` macros, the following assertions
Expand Down Expand Up @@ -1152,6 +1196,41 @@ framework, but let me know if you truly need a timeout of greater than 4m15s).

***ArduinoUnit Compatibility***: _Only available in AUnit._

## GoogleTest Adapter

It may be possible to run simple unit tests written using
[Google Test](https://github.com/google/googletest/) API on an Arduino platform
by using the
[aunit/contrib/gtest.h](src/aunit/contrib/gtest.h) adapter. This
adapter layer provides a number of macros Google Test macros which map to
their equivalent macros in AUnit:

* `ASSERT_EQ(e, a)` - `assertEqual()`
* `ASSERT_NE(e, a)` - `assertNotEqual()`
* `ASSERT_LT(e, a)` - `assertLess()`
* `ASSERT_GT(e, a)` - `assertMore()`
* `ASSERT_LE(e, a)` - `assertLessOrEqual()`
* `ASSERT_GE(e, a)` - `assertMoreOrEqual()`
* `ASSERT_STREQ(e, a)` - `assertEqual()`
* `ASSERT_STRNE(e, a)` - `assertNotEqual()`
* `ASSERT_STRCASEEQ(e, a)` - `assertStringCaseEqual()`
* `ASSERT_STRCASENE(e, a)` - `assertStringCaseNotEqual()`
* `ASSERT_TRUE(x)` - `assertTrue()`
* `ASSERT_FALSE(x)` - `assertFalse()`

To use the `gtest.h` adapter, include the following headers:
```C++
#include <AUnit.h>
#include <aunit/contrib/gtest.h>
```

or

```C++
#include <AUnitVerbose.h>
#include <aunit/contrib/gtest.h>
```

## Commandline Tools and Continuous Integration

### AUniter
Expand All @@ -1161,29 +1240,33 @@ The command line tools have been moved into the
The `auniter.sh` script can compile, upload and validate multiple AUnit tests on
multiple Arduino boards. The script can monitor the serial port and determine if
the unit test passed or failed, and it will print out a summary of all unit
tests at the end.

Full details are given in the AUniter project, but here are some quick examples
of these tools using the [AceSegment](https://github.com/bxparks/AceSegment)
project.

The following compiles and verifies the given sketches:
```
$ AUniter/auniter.sh --verify \
--boards nano,leonardo,esp8266,esp32 AceSegment/tests/*Test
```

The following uploads to and runs all the unit tests on an Arduino Nano
(`/dev/ttyUSB0`), then an Arduion Leonardo (`/dev/ttyACM0`):
```
$ AUniter/auniter.sh --test \
--boards nano:/dev/ttyUSB1,leonardo:/dev/ttyACM0 AceSegment/tests/*Test
```

The list of available ports can be found by:
```
$ AUniter/auniter.sh --list_ports
```
tests at the end. Full details are given in the AUniter project, but here are
some quick examples copied from the `AUniter/README.md` file:

* `$ auniter envs`
* list the environments configured in the `auniter.ini` config file
* `$ auniter ports`
* list the available serial ports and devices
* `$ auniter verify nano Blink.ino`
* verify (compile) `Blink.ino` using the `env:nano` environment
* `$ auniter verify nano,esp8266,esp32 Blink.ino`
* verify `Blink.ino` on 3 target environments (`env:nano`, `env:esp8266`,
`env:esp32`)
* `$ auniter upload nano:/dev/ttyUSB0 Blink.ino`
* upload `Blink.ino` to the `env:nano` target environment connected to
`/dev/ttyUSB0`
* `$ auniter test nano:USB0 BlinkTest.ino`
* compile and upload `BlinkTest.ino` using the `env:nano` environment,
upload it to the board at `/dev/ttyUSB0`, then validate the output of the
[AUnit](https://github.com/bxparks/AUnit) unit test
* `$ auniter test nano:USB0,esp8266:USB1,esp32:USB2 BlinkTest/ ClockTest/`
* upload and verify the 2 unit tests (`BlinkTest/BlinkTest.ino`,
`ClockTest/ClockTest.ino`) on 3 target environments (`env:nano`,
`env:esp8266`, `env:esp32`) located at the 3 respective ports
(`/dev/ttyUSB0`, `/dev/ttyUSB1`, `/dev/ttyUSB2`)
* `$ auniter upmon nano:USB0 Blink.ino`
* upload the `Blink.ino` sketch and monitor the serial port using a
user-configurable terminal program (e.g. `picocom`) on `/dev/ttyUSB0`

### Continuous Integration

Expand Down Expand Up @@ -1269,20 +1352,103 @@ TestAgain TestOnce
::again() ::once()
```
Placing the `Assertion` and `MetaAssertion` classes inside the `Test` hierarchy
allows those assertion statements to have access to the internal states of the
`Test` instance, which makes certain functions (like the early return upon
delayed failure) slightly easier to implement.
Normally, deep inheritance hierarchies like this should be avoided. However,
placing the `Assertion` and `MetaAssertion` classes inside the `Test` hierarchy
allowed those assertion statements to have access to the internal states of the
`Test` instance. This made certain features (like the early return upon delayed
failure) slightly easier to implement. For the most part, the end-users can
ignore the existence of the `Assertion` and `MetaAssertion` classes and think of
this as a simple 2-level inheritance tree.
### Comparing Pointers
Currently the `assertEqual()` and other `assertXxx()` methods do not support
comparing arbitrary pointers (i.e. `(void*)`. This could change if
comparing arbitrary pointers (i.e. `(void*)`. This could change if
[Issue #34](https://github.com/bxparks/AUnit/issues/34) is
resolved. In the meantime, a workaround is to cast the pointer to a `uintptr_t`
integer type from `#include <stdint.h>` and then calling `assertEqual()` on the
integer type.
### Testing Private Helper Methods
There is a school of throught which says that unit tests should test only the
publically exposed methods of a class or library. I agree mostly with that
sentiment, but not rigidly. I think it is sometimes useful to write unit tests
for `protected` or `private` methods. For example, when creating a chain of
small helper methods, which build up to larger publically exposed methods, it is
extremely useful to write unit tests for the helper methods in isolation.
Normally those helper methods would be `private` because they are used
only within that class, and we don't want to expose them to the public API. One
option is to make them `public` but add a comment in the function to say that it
is exposed only for testing purposes. This does not seem satisfactory because
users will tend to ignore such comments if the helper functions are useful.
I think a better way is to keep the helper functions `private` but make
the unit tests a `friend class` of the target class. The syntax for doing this
can be tricky, it took me a number of attempts to get this right, especially if
you are also using namespaces for your target class:
```C++
//------------------- Target.h -------------
// Auto-generated test class names.
class Test_helper;
class TargetSuite_helper;
class TargetTest_helper;
namespace mylib {
class Target {
public:
void publicMethod() {
...
int a = helper();
...
}
private:
// Must have the global scope operator '::'
friend class ::Test_helper;
friend class ::TargetSuite_helper;
friend class ::TargetTest_helper;
static int helper() {...}
};
}
//------------------- TargetTest.ino -------------
#include <AUnit.h>
#include "Target.h"
using namespace aunit;
using namespace mylib;
test(helper) {
assertEqual(1, Target::helper(...));
}
test(TargetSuite, helper) {
assertEqual(1, Target::helper(...));
}
class TargetTest: public TestOnce {
...
};
testF(TargetTest, helper) {
assertEqual(1, Target::helper(...));
}
```

The tricky part is that in `Target.h` you need a forward declaration of the
various auto-generated AUnit test classes, and within the `Target` class itsef,
the `friend` declaration needs to have a global scope `::` specifier before the
name of the test class.

## Benchmarks

AUnit consumes as much as 65% less flash memory than ArduinoUnit 2.2 on an AVR
Expand Down Expand Up @@ -1365,6 +1531,8 @@ will incorporate everything, but I will give your ideas serious consideration.
## Authors

* Created by Brian T. Park ([email protected]).
* The Google Test adapter (`gtest.h`) was created by Chris Johnson
([email protected]).
* The design and syntax of many macros (e.g. `test()`, `assertXxx()`) were
borrowed from the [ArduinoUnit](https://github.com/mmurdoch/arduinounit)
project to allow AUnit to be almost a drop-in replacement. Many thanks to
Expand Down
2 changes: 1 addition & 1 deletion docs/doxygen.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PROJECT_NAME = "AUnit"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = 1.1.1
PROJECT_NUMBER = 1.2

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down
4 changes: 2 additions & 2 deletions docs/html/AUnitVerbose_8h.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<tr style="height: 56px;">
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">AUnit
&#160;<span id="projectnumber">1.1.1</span>
&#160;<span id="projectnumber">1.2</span>
</div>
<div id="projectbrief">Unit testing framework for Arduino platforms inspired by ArduinoUnit and Google Test.</div>
</td>
Expand Down Expand Up @@ -112,7 +112,7 @@
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="define-members"></a>
Macros</h2></td></tr>
<tr class="memitem:a87cbb10969eff63f8ae66afc4e9457eb"><td class="memItemLeft" align="right" valign="top"><a id="a87cbb10969eff63f8ae66afc4e9457eb"></a>
#define&#160;</td><td class="memItemRight" valign="bottom"><b>AUNIT_VERSION</b>&#160;&#160;&#160;010100</td></tr>
#define&#160;</td><td class="memItemRight" valign="bottom"><b>AUNIT_VERSION</b>&#160;&#160;&#160;010200</td></tr>
<tr class="separator:a87cbb10969eff63f8ae66afc4e9457eb"><td class="memSeparator" colspan="2">&#160;</td></tr>
</table>
<a name="details" id="details"></a><h2 class="groupheader">Detailed Description</h2>
Expand Down
Binary file modified docs/html/AUnitVerbose_8h__incl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ca0c6d9

Please sign in to comment.