When python compiles a function, it tracks all of the variables used on the left side of an equal sign (that is, all variables that have something assigned to them) and uses those as the set of local variables. Variables that are referenced but not assigned to are obviously not local variables and must be in the enclosing scope.
If you want to assign a value to a global variable, you have to tell python to break its local variables rule. You do that with the global keyword in a function. It tells python that for that one function only, the named variables are global and should not be compiled as local.
The reason i is a problem but test and j are not, is that i is the only one that is assigned to.
EDIT
Digging a little deeper, python keeps a list of local variables and calls those entries "slots". When the function is compiled, the variable name is converted into an index into the slot table. A name lookup turns into a simple integer lookup, making the function run faster. When the function is called, python creates a new slot table for the known local variables, initialized to "not assigned". When a variable is used, python checks its slot, sees its marked "not assigned" and raises the error.