使用教材
《“笨办法” 学C语言(Learn C The Hard Way)》
https://www.jianshu.com/p/b0631208a794
- 完整源码 : learn-c-the-hard-way-lectures/ex28/
https://github.com/zedshaw/learn-c-the-hard-way-lectures/tree/master/ex28
- 说明:
ex28
文件夹里面的文件是一直用到ex30
的;
一、查看文件结构(执行 make install 之前)
$ ls *
LICENSE Makefile Makefile~ README.md
bin:
src:
dbg.h libex29.c
tests:
libex29_tests.c minunit.h runtests.sh
二、完整源码
Makefile
- 直接用配书代码仓库里的
Makefile
编译会出错的话,Ubuntu
可以用下面这个:
CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)
LDFLAGS=$(OPTLIBS)
PREFIX?=/usr/local
SOURCES=$(wildcard src/**/*.c src/*.c)
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))
TEST_SRC=$(wildcard tests/*_tests.c)
TESTS=$(patsubst %.c,%,$(TEST_SRC))
TARGET=build/libex29.a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))
LDLIBS=-ldl
# The Target Build
all: $(TARGET) $(SO_TARGET) tests
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all
$(TARGET): CFLAGS += -fPIC
$(TARGET): build $(OBJECTS)
ar rcs $@ $(OBJECTS)
ranlib $@
$(SO_TARGET): $(TARGET) $(OBJECTS)
$(CC) -shared -o $@ $(OBJECTS)
build:
@mkdir -p build
@mkdir -p bin
# The Unit Tests
.PHONY: tests
tests: LDLIBS += $(TARGET)
tests: $(TESTS)
sh ./tests/runtests.sh
valgrind:
VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE)
# The Cleaner
clean:
rm -rf build $(OBJECTS) $(TESTS)
rm -f tests/tests.log
find . -name "*.gc*" -exec rm {} \;
rm -rf `find . -name "*.dSYM" -print`
# The Install
install: all
install -d $(DESTDIR)/$(PREFIX)/lib/
install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/
# The Checker
check:
@echo Files with potentially dangerous functions.
@egrep '[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)' $(SOURCES) || true
/tests/libex29_tests.c
- 代码修改,修改
lib_file
变量的值;
代码仓库下载下来的 libex29_tests.c 文件里
char *lib_file = "build/libYOUR_LIBRARY.so";
要修改成
char *lib_file = "build/libex29.so";
三、执行 make install 操作
命令行操作
$ make install
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -fPIC -c -o
src/libex29.o src/libex29.c
ar rcs build/libex29.a src/libex29.o
ranlib build/libex29.a
cc -shared -o build/libex29.so src/libex29.o
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG
tests/libex29_tests.c -ldl build/libex29.a -o tests/libex29_tests
sh ./tests/runtests.sh
Running unit tests:
----
RUNNING: ./tests/libex29_tests
A STRING: Hello
HELLO
hello
ALL TESTS PASSED
Tests run: 4
tests/libex29_tests PASS
对输出信息的说明
- 生成
.o
的命令是
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -fPIC -c -o src/libex29.o src/libex29.c
- 如果要查看
.o
里面有什么函数,可以使用nm
命令
$ nm ./src/libex29.o
U __ctype_tolower_loc
U __ctype_toupper_loc
00000000000000d0 T fail_on_purpose
U _GLOBAL_OFFSET_TABLE_
0000000000000000 r .LC0
0000000000000080 T lowercase
0000000000000000 T print_a_message
U __printf_chk
U putchar
0000000000000030 T uppercase
- 生成
.so
的命令是
cc -shared -o build/libex29.so src/libex29.o
- 生成
loder program
的命令是
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG tests/libex29_tests.c -ldl build/libex29.a -o tests/libex29_tests
- 跑单元测试用例的命令是
sh ./tests/runtests.sh
四、再次查看,文件结构(执行 make install 之后)
$ ls *
LICENSE Makefile Makefile~ README.md
bin:
build:
libex29.a libex29.so
src:
dbg.h libex29.c libex29.o
tests:
libex29_tests libex29_tests.c minunit.h runtests.sh tests.log
- 可以看到生成了一个新的文件夹
build
等等;
五、单元测试
1、 单元测试的代码就是写在 /tests/libex29_tests.c
里面的
/***** /tests/libex29_tests.c *******/
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;
}
2、可以和手工进行测试的过程进行对比
3、 单元测试代码 xxxx_tests.c
文件一贯的结构
- 函数名会变,每一个函数
XXXX
,都是先有一个char *test_XXXX()
,后面再用mu_run_test(test_XXXX);
的结构;
#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);
4、 单元测试的框架
- 本质就是一堆宏替换
- 文件路径 :
learn-c-the-hard-way-lectures/ex28/c-skeleton/tests/minunit.h
#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
- 以
mu_run_test(test_dlopen);
举例
(位于 minunit.h) 首先是宏替换的定义 :
#define mu_run_test(test) debug("\n-----%s", " " #test); \
message = test(); tests_run++; if (message) return message;
经过替换之后,会变成:
debug("\n-----%s", " " #test_dlopen);
message = test_dlopen();
if (message) return message;
debug 是配书自带的调试库里的函数,就是一个输出函数;
重点是这一句 message = test_dlopen();
这是什么?这可是调用函数 test_dlopen()啊!
并且将函数的结果返回给 message!
这就是为什么明明 mu_run_test(test_dlopen) 里面只是写了函数名,
不带()括号,却也能使得测试函数被调用, 因为在宏替换里面加上了();
5、自动测试,运行一个脚本文件 runtests.sh
learn-c-the-hard-way-lectures/ex28/c-skeleton/tests/runtests.sh
echo "Running unit tests:"
for i in tests/*_tests
do
if test -f $i
then
if $VALGRIND ./$i 2>> tests/tests.log
then
echo $i PASS
else
echo "ERROR in test $i: here's tests/tests.log"
echo "------"
tail tests/tests.log
exit 1
fi
fi
done
echo ""
网友评论