Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Scuttle

Easy unit testing for C projects

v1.0.1

MIT License, © 2020 Scott Madin smadin@gmail.com

Overview

Scuttle ("Simple C Unit Testing Tool") is a small unit-testing facility written in C. Scuttle consists of a header file, scuttle.h, which defines the macros you use to write your tests, and a Bash script, scuttle.sh, which parses your test suites and generates the scaffolding to run them.

Installation

To install Scuttle systemwide, extract the archive, and from the root of the Scuttle directory run sudo make install. This will install scuttle.h in /usr/local/include/ and scuttle.sh in /usr/local/lib/scuttle/.

Scuttle does not need to be installed systemwide, however: you can incorporate it into your project directly (for example if you do not have root access). Simply copy include/scuttle.h and src/scuttle.sh into appropriate locations in your project directory.

Usage

Requirements and assumptions

Scuttle assumes what I believe to be a fairly standard structure for small to medium-sized C projects:

  • the entire project contained in an appropriately-named directory (<projname>)
  • source files in <projname>/src/
  • headers in <projname>/include/
  • a Makefile or other build system that places compiled object files in <projname>/obj/ and executable binaries in <projname>/bin/

Scuttle depends on Bash and GNU Make (you need not use Make to build your main project, but Scuttle will use it to build the test harness), and a modern C compiler. (The Makefile Scuttle generates will try to use clang or gcc, then fall back to the default. The compiler must support the flags -g -Wall -Wextra -Werror -std=c99.)

From here on, assume all directory names are relative to the project root <projname>/.

Adding Scuttle to your build

These instructions assume your project does use GNU Make to build, so adjust accordingly if you use a different build tool.

Add a test target to your main project Makefile, and call the scuttle.sh script:

.PHONY: test

test:
    bash /usr/local/lib/scuttle/scuttle.sh <testdir>
    $(MAKE) -C <testdir> test
    cat <testdir>/log/test_<projname>.log

Where <testdir> is the subdirectory in which you will place your test suites. If you use the default name test, you can omit the <testdir> parameter when calling scuttle.sh.

Scuttle will look in <testdir> for source files named test_<module>.c, where <module> is the name of a module of the main project (assumed to exist as an object file obj/<module>.o) to which the test suite corresponds. It will parse those test suites, and generate a header and a data file for each one, as well as a main test harness program and a Makefile in <testdir>.

Writing Scuttle test suites

Now it's time to write some tests, using the macros scuttle.h defines.

As a very simple example, suppose your project has the module foo, defined in include/foo.h:

#ifndef _FOO_H
#define _FOO_H

/* foo() should return 42 */
int foo();

/* foostr() should return 1 (success) and write "foo" into buf, or return 0 (failure) if buf is NULL or too small */
int foostr(char *buf, size_t bufsz);

#endif /* _FOO_H */

Which is compiled to obj/foo.o. To test these functions, write a suite <testdir>/test_foo.c:

#include "foo.h"
#include "test_foo.h" /* test_foo.h will be generated by scuttle.sh */

SSUITE_INIT(<projname>_foo)
    /* code here will run once, before any tests in the suite */
SSUITE_READY

STEST_SETUP
    /* code here will run before each test */
STEST_SETUP_END

STEST_TEARDOWN
    /* code here will run after each test */
STEST_TEARDOWN_END

STEST_START(foo_returns_fortytwo)
    int i = foo();
    SASSERT(i == 42) /* simple assertion */
    SREFUTE(i != 42) /* negative assertion */
    SASSERT_EQ(i, 42) /* equality assertion */
STEST_END

STEST_START(foostr_sets_buf)
    char buf[10];
    int i = foostr(buf, 10);
    SASSERT(i) /* did foostr() return 1 (success)? */
    const char *expected = "foo";
    SASSERT_STREQ(buf, expected) /* did foostr() put "foo" in buf? */
STEST_END

STEST_START(foostr_doesnt_set_buf)
    char buf[2];
    int i = foostr(buf, 2);
    SREFUTE(i) /* did foostr() return 0 (failure)? */
    i = foostr(NULL, 10);
    SREFUTE(i) /* did foostr() return 0 (failure)? */
STEST_END

Now simply run your make test target from your main project directory, and assuming the foo module does work correctly, you'll see some build output followed by the contents of <testdir>/log/test_<projname>.log:

Test suite <projname>_foo:
*** Suite passed: 3 / 3 tests passed.
*** 1 / 1 suites passed.

See the example/ subdirectory of the Scuttle archive for a complete, very simple, sample project including this foo module.

Reference

Suite macros

The suite definition macros are required in each Scuttle test suite, even if you don't need any special suite initialization or test setup code.

  • SSUITE_INIT(<projname>_<suitename>): Begins the suite initialization function, and declares the name of the test suite. <projname> must match the name of the project's root directory, and <suitename> must match the filename test_<suitename>.c. Code between SSUITE_INIT() and SSUITE_READY is run once, just before any tests in the suite are run.
  • SSUITE_READY: Ends the suite initialization function.
  • STEST_SETUP: Begins the per-test setup function. Code between STEST_SETUP and STEST_SETUP_END is run just before each test in the suite is run.
  • STEST_SETUP_END: Ends the per-test setup function.
  • STEST_TEARDOWN: Begins the per-test teardown function. Code between STEST_TEARDOWN and STEST_TEARDOWN_END is run just after each test in the suite is run.
  • STEST_TEARDOWN_END: Ends the per-test teardown function.

Test macros

Every Scuttle test suite must define at least one test.

  • STEST_START(<testname>): Begins the test case. Scuttle will extract <testname> when generating the test scaffolding. Between STEST_START() and STEST_END is where you write your test code.
  • STEST_END: Ends the test case.

Assertion macros

Use Scuttle's assertion macros in your tests, to check the output of the functions you're testing and raise errors if something isn't right. You can put more than one assertion in a test case, but any assertion failure will bail out of that case. (If a test case fails, the rest of the cases in the suite will still be run.)

  • SASSERT(x): asserts that the expression represented by x is true (non-zero/non-NULL).
  • SREFUTE(x): asserts that the expression is false (zero/NULL).
  • SASSERT_NULL(x): asserts specifically that the expression is NULL.
  • SREFUTE_NULL(x): asserts that the expression is not NULL.
  • SASSERT_EQ(x, y): asserts that x and y are equal at the level of variable equality (have the same value, are the same pointer, etc.) — note this is different from string equality.
  • SREFUTE_EQ(x, y): asserts that x and y are not equal.
  • SASSERT_STREQ(x, y): asserts that x and y are either both NULL, or point to the same string, or are pointers to equal (strncmp() returns 0) strings. Because SASSERT_STREQ() performs pointer comparison, do not pass a string literal or literal NULL to it; instead you must instantiate a char * variable in your test to hold that value.
  • SREFUTE_STREQ(x, y): asserts that x and y are not equal strings.

About

Simple C Unit Testing Tool

Resources

License

Releases

No releases published

Packages

No packages published