Python's pytest framework achieves this without macros. As I understand it, it disassembles the test function bytecode and inspects the AST nodes that have assertions in them.
Inspecting the AST ... That kind of sounds like what macros do. Just that pytest is probably forced to do it in way less elegant ways, due to not having a macro system. I mean, if it has to disassemble things, then it has already lost, basically, considering how much its introspection is lauded sometimes.