- Learn C The Hard Way
- Preface
- Introduction: The Cartesian Dream Of C
- Exercise 0: The Setup
- Exercise 1: Dust Off That Compiler
- Exercise 2: Make Is Your Python Now
- Exercise 3: Formatted Printing
- Exercise 4: Introducing Valgrind
- Exercise 5: The Structure Of A C Program
- Exercise 6: Types Of Variables
- Exercise 7: More Variables, Some Math
- Exercise 8: Sizes And Arrays
- Exercise 9: Arrays And Strings
- Exercise 10: Arrays Of Strings, Looping
- Exercise 11: While-Loop And Boolean Expressions
- Exercise 12: If, Else-If, Else
- Exercise 13: Switch Statement
- Exercise 14: Writing And Using Functions
- Exercise 15: Pointers Dreaded Pointers
- Exercise 16: Structs And Pointers To Them
- Exercise 17: Heap And Stack Memory Allocation
- Exercise 18: Pointers To Functions
- Exercise 19: A Simple Object System
- Exercise 20: Zed's Awesome Debug Macros
- Exercise 21: Advanced Data Types And Flow Control
- Exercise 22: The Stack, Scope, And Globals
- Exercise 23: Meet Duff's Device
- Exercise 24: Input, Output, Files
- Exercise 25: Variable Argument Functions
- Exercise 26: Write A First Real Program
- Exercise 27: Creative And Defensive Programming
- Exercise 28: Intermediate Makefiles
- Exercise 29: Libraries And Linking
- Exercise 30: Automated Testing
- Exercise 31: Debugging Code
- Exercise 32: Double Linked Lists
- Exercise 33: Linked List Algorithms
- Exercise 34: Dynamic Array
- Exercise 35: Sorting And Searching
- Exercise 36: Safer Strings
- Exercise 37: Hashmaps
- Exercise 38: Hashmap Algorithms
- Exercise 39: String Algorithms
- Exercise 40: Binary Search Trees
- Exercise 41: Using Cachegrind And Callgrind For Performance Tuning
- Exercise 42: Stacks and Queues
- Exercise 43: A Simple Statistics Engine
- Exercise 44: Ring Buffer
- Exercise 45: A Simple TCP/IP Client
- Exercise 46: Ternary Search Tree
- Exercise 47: A Fast URL Router
- Exercise 48: A Tiny Virtual Machine Part 1
- Exercise 48: A Tiny Virtual Machine Part 2
- Exercise 50: A Tiny Virtual Machine Part 3
- Exercise 51: A Tiny Virtual Machine Part 4
- Exercise 52: A Tiny Virtual Machine Part 5
- Next Steps
- Deconstructing K & RC Is Dead
Exercise 30: Automated Testing
Automated testing is used frequently in other languages like Python and Ruby, but rarely used in C. Part of the reason comes from the difficulty of automatically loading and testing pieces of C code. In this chapter we'll create a very small little testing "framework" and get your skeleton directory building an example test case.
The frameworks I'm going to use, and which you'll include in your c-skeleton
skeleton is called "minunit" which started with code from a tiny snippet of code by Jera Design . I then evolved it further, to be this:
#undef NDEBUG
#ifndef _minunit_h
#define _minunit_h
#include <stdio.h>
#include <dbg.h>
#include <stdlib.h>
#define mu_suite_start() char *message = NULL
#define mu_assert(test, message) if (!(test)) { log_err(message); return message; }
#define mu_run_test(test) debug("\n-----%s", " " #test); \
message = test(); tests_run++; if (message) return message;
#define RUN_TESTS(name) int main(int argc, char *argv[]) {\
argc = 1; \
debug("----- RUNNING: %s", argv[0]);\
printf("----\nRUNNING: %s\n", argv[0]);\
char *result = name();\
if (result != 0) {\
printf("FAILED: %s\n", result);\
}\
else {\
printf("ALL TESTS PASSED\n");\
}\
printf("Tests run: %d\n", tests_run);\
exit(result != 0);\
}
int tests_run;
#endif
There's mostly nothing left of the original, as now I'm using the dbg.h
macros and I've created a large macro at the end for the boilerplate test runner. Even with this tiny amount of code we'll create a fully functioning unit test system you can use in your C code once it's combined with a shell script to run the tests.
Wiring Up The Test Framework
To continue this exercise, you should have your src/libex29.c
working and that you completed the Exercise 29 extra credit where you got the ex29.c
loader program to properly run. In Exercise 29 I had an extra credit to make it work like a unit test, but I'm going to start over and show you how to do that with minunit.h
.
The first thing to do is create a simple empty unit test name tests/libex29_tests.c
with this in it:
#include "minunit.h"
char *test_dlopen()
{
return NULL;
}
char *test_functions()
{
return NULL;
}
char *test_failures()
{
return NULL;
}
char *test_dlclose()
{
return NULL;
}
char *all_tests() {
mu_suite_start();
mu_run_test(test_dlopen);
mu_run_test(test_functions);
mu_run_test(test_failures);
mu_run_test(test_dlclose);
return NULL;
}
RUN_TESTS(all_tests);
This code is demonstrating the RUN_TESTS
macro in tests/minunit.h
and how to use the other test runner macros. I have the actual test functions stubbed out so that you can see how to structure a unit test. I'll break this file down first:
libex29_tests.c:1
Include the minunit.h
framework.
libex29_tests.c:3-7
A first test. Tests are structured so they take no arguments and return a char *
which is NULL
on success . This is important because the other macros will be used to return an error message to the test runner.
libex29_tests.c:9-25
More tests that are the same as the first one.
libex29_tests.c:27
The runner function that will control all the other tests. It has the same form as any other test case, but it gets configured with some additional gear.
libex29_tests.c:28
Sets up some common stuff for a test with mu_suite_start
.
libex29_tests.c:30
This is how you say what test to run, using the mu_run_test
macro.
libex29_tests.c:35
After you say what tests to run, you then return NULL
just like a normal test function.
libex29_tests.c:38
Finally, you just use the big RUN_TESTS
macro to wire up the main
method with all the goodies and tell it to run the all_tests
starter.
That's all there is to running a test, now you should try getting just this to run within the project skeleton. Here's what it looks like when I do it:
not printable
I first did a make clean
and then I ran the build, which remade the template libYOUR_LIBRARY.a
and libYOUR_LIBRARY.so
files. Remember that you had to do this in the extra credit for Exercise 29, but just in case you didn't figure it out, here's the diff for the Makefile
I'm using now:
diff --git a/code/c-skeleton/Makefile b/code/c-skeleton/Makefile
index 135d538..21b92bf 100644
--- a/code/c-skeleton/Makefile
+++ b/code/c-skeleton/Makefile
@@ -9,9 +9,10 @@ TEST_SRC=$(wildcard tests/*_tests.c)
TESTS=$(patsubst %.c,%,$(TEST_SRC))
TARGET=build/libYOUR_LIBRARY.a
+SO_TARGET=$(patsubst %.a,%.so,$(TARGET))
# The Target Build
-all: $(TARGET) tests
+all: $(TARGET) $(SO_TARGET) tests
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all
@@ -21,6 +22,9 @@ $(TARGET): build $(OBJECTS)
ar rcs $@ $(OBJECTS)
ranlib $@
+$(SO_TARGET): $(TARGET) $(OBJECTS)
+ $(CC) -shared -o $@ $(OBJECTS)
+
build:
@mkdir -p build
@mkdir -p bin
With those changes you should be now building everything and you can finally fill in the remaining unit test functions:
#include "minunit.h"
#include <dlfcn.h>
typedef int (*lib_function)(const char *data);
char *lib_file = "build/libYOUR_LIBRARY.so";
void *lib = NULL;
int check_function(const char *func_to_run, const char *data, int expected)
{
lib_function func = dlsym(lib, func_to_run);
check(func != NULL, "Did not find %s function in the library %s: %s", func_to_run, lib_file, dlerror());
int rc = func(data);
check(rc == expected, "Function %s return %d for data: %s", func_to_run, rc, data);
return 1;
error:
return 0;
}
char *test_dlopen()
{
lib = dlopen(lib_file, RTLD_NOW);
mu_assert(lib != NULL, "Failed to open the library to test.");
return NULL;
}
char *test_functions()
{
mu_assert(check_function("print_a_message", "Hello", 0), "print_a_message failed.");
mu_assert(check_function("uppercase", "Hello", 0), "uppercase failed.");
mu_assert(check_function("lowercase", "Hello", 0), "lowercase failed.");
return NULL;
}
char *test_failures()
{
mu_assert(check_function("fail_on_purpose", "Hello", 1), "fail_on_purpose should fail.");
return NULL;
}
char *test_dlclose()
{
int rc = dlclose(lib);
mu_assert(rc == 0, "Failed to close lib.");
return NULL;
}
char *all_tests() {
mu_suite_start();
mu_run_test(test_dlopen);
mu_run_test(test_functions);
mu_run_test(test_failures);
mu_run_test(test_dlclose);
return NULL;
}
RUN_TESTS(all_tests);
Hopefully by now you can figure out what's going on, since there's nothing new in this except for the check_function
function. This is a common pattern where I see that I'll be doing a chunk of code repeatedly, and then simply automate it either by creating a function or a macro for it. In this case I'm going to run functions in the .so
I load so I just made a little function to do it.
Extra Credit
- This works but it's probably a bit messy. Clean the
c-skeleton
directory up so that it has all these files, but remove any of the code related to Exercise 29. You should be able to copy this directory over and kickstart new projects without much editing. - Study the
runtests.sh
and go read aboutbash
syntax so you know what it does. Think you could write a C version of this script?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论