So what are the most required features for unit testing support? I believe the most important feature is to mark a test as failed. I also want to stop executing this test in case of failure, like JUnit does. This is the
%macro fail 0 log msg_failed, msg_failed_len leave ret 0 ; test method never has any arguments %endmacro msg_failed: db 'FAILED' msg_failed_len equ $ - msg_failedIn fact it is a NASM macro which gets copied into each invocation. For simplicity, it does not support a message argument right now, hence zero macro parameters. It uses the
logmacro, which prints the given string to Standard Out, just like
_printGridof my Assembly Minesweeper. Then it resets the stack frame (
leave) and returns from the current function. I expect all test methods to have no arguments, and
fail's code gets copied into each test method, this skips further execution of the test method and returns immediately to the calling code, i.e. the test runner. Adding the count of failed tests in
failis straight forward but I will leave that for later.
Next I need assertions to check my expectations, at least an
%macro assert_equals 2 cmp %1, %2 je .%1_%2_end fail .%1_%2_end: call _show_progress %endmacroThis has to be a macro as well so
failworks correctly as explained above. But as the macro gets expanded into the calling code, the local label
.%1_%2_endhas to be unique. While different assertions are possible, a specific assertion like
assert_equals eax, 2can only be used once per test method. This is a weird constraint but I do not mind as my tests tend to have a single assertion per test method anyway. (Later I changed the macro to use a macro local label
%%_endwhich removed this problem.)
_show_progressjust prints a single dot to show the progress during test execution. It needs to preserve all registers because it is called in the middle of my test code.
_show_progress: push eax push ebx push ecx push edx push dot_len mov eax, dot push eax call _print pop edx pop ecx pop ebx pop eax ret dot: db '.' dot_len equ 1Test Cases
For my test cases I want descriptive names. Using long, regular labels for function names and the assertion macros I write my first test
_should_add_one_and_one_to_be_two: create_local_variables 0 mov eax, 1 add eax, 1 assert_equals eax, 2 leave retThe test succeeds and prints a single dot to Standard Out. The
create_local_variablesmacro creates the stack frame and local variables if needed. If a test fails, e.g.
_should_add_one_and_two_to_be_three: create_local_variables 0 mov eax, 1 add eax, 2 assert_equals eax, 2 ; (6) ; not reached hlt leave retit prints
FAILEDand stops execution in line 6.
Finally I want to run all my tests one by one. In this simple case I just call the test methods from the main entry point, followed by printing
DONEat the end.
global _main _main: ; welcome message log msg_hello, msg_hello_end - msg_hello call _should_add_one_and_one_to_be_two call _should_add_one_and_two_to_be_three ; (10) ; completion message log msg_done, msg_done_end - msg_done jmp _exit msg_hello: db 'HELLO asmUnit' msg_hello_end: msg_done: db 'DONE' msg_done_end:This gets the job done but I am not happy with it. I need to add new test method invocations by hand in line 10. By seeing the new test fail I cannot forget to add the call of the new test method, but automatic test case discovery would be more convenient.
Before and After
What else does a unit testing framework need? For complex cases, before and after test execution hooks might be necessary. A
beforemacro defines the proper label and the
begin_testmacro just calls it.
%macro before 0-1 0 %ifdef ctx_before %error "before used more than once" %endif %define ctx_before, 1 _before_hook: create_local_variables %1 %endmacro %macro begin_test 0-1 0 create_local_variables %1 %ifdef ctx_before call _before_hook %endif %endmacroThe
end_testmacros work accordingly, just for the test clean-up. The actual test code stays the same, the macro calls just changed.
_should_add_one_and_one_to_be_two: begin_test mov eax, 1 add eax, 1 assert_equals eax, 2 end_testWhile this works well, I am unhappy with all the (NASM specific) macros in the code. There remains little real Assembly, but I guess that is the way to go. There are also plenty of missing features but this minimal first version gets me started. I will see how it works out in my next Assembly kata. (The complete source of asmUnit is here.)