This fails due to the typical auto-reset-on-open serial port configuration interacting with a problematic assumption made by the PC code.
The first issue means that it will typically take a few seconds between when the port is opened (unless the port is configured not to manipulate modem control signals on open) and when the usual bootloader times out, yields to the custom sketch loaded in the Arduino, and the latter becomes able to accept input and potentially respond.
The second issue is with the PC side code:
for i in range(0,4):
str = "test string\n"
import serial refers to pySerial, then because you did not specify a timeout when opening the port, the
ser.readline() is an eternally blocking read. To quote the documentation:
Be carefully when using readline(). Do specify a timeout when opening the serial port otherwise it could block forever if no newline character is received.
If we look at what happens with your python program and a typical serial port configuration where modem status lines change on open, then the first thing your program does is implicitly reset the Arduino. Next, it writes "test string\n" - but because of the reset, it writes this at the bootloader, not the sketch which is not running yet. Finally your program eternally waits for a newline terminated reply - which it will never receive, because your sketch will not send that until it receives something from the PC, which will not send anything else until it receives the newline terminated response to its first message.
Your programs are thus stuck in a "no, after you" deadlock.
There are many potential ways to work around this. For example, you might open the port with a timeout, wait a few seconds, and conduct this test write and attempt to read a reasonable number of times, but declare an error to the user if success is not achieved in ten or so seconds of those attempts.
Note also that the sketch may start running midway through transmission of the test string, so you will have to handle the possibility of a message with the beginning missing, either by declaring an error or discarding it and waiting for a subsequent intact one.