0

I will try to give you some context before presenting the issue I am facing. I have a component called Actuator which relies on the module pymodbus. When I tested this component, I did it in the easiest possible way using a modbus TCP server based on pymodbus. Basically, I run the server as a python script (which is exactly this one: https://pymodbus.readthedocs.io/en/latest/source/example/synchronous_server.html) in a new shell and my application, which includes the Actuator, in another shell. Everything works like a charm.

What I want to do now, is to write a unit test for the Actuator, using python unittest and pymodbus and try to automate what I was doing before. My idea was to run the modbus server as a subprocess (consider that I do not need the output from the server) in the setUp method, use it in my test suite and then terminate it in the tearDown method, like this:

class TestActuator(unittest.TestCase):
    """Class to test the actuator module"""

    def setUp(self):
        """
        Setup a real Actuator object with a modbus server running
        in background for tests
        """
        modbus_server_path = "path/to/modbus_server.py"
        cmd = "python3 {}".format(modbus_server_path)
        self.modbus_server = Popen(cmd.split(), shell=False, stdout=DEVNULL, stderr=DEVNULL)

        # other stuff

    def test1(self):
 
    def test2(self):

    def tearDown(self):
        """Cleanup everything before exiting"""

        self.modbus_server.terminate()


if __name__ == '__main__':
    unittest.main()

Unfortunately, it seems more challenging than I expected. Everything I tried from other topics on stackoverflow failed:

  • Use Popen.kill() instead of terminate. I tried also to "del" after kill or terminate or to use os.kill(self.modbus_server.pid, SIGTERM) instead.
  • Add or change args to the Popen command, such as shell=True instead of shell=False and close_fds=True.
  • Use the other variants of subprocess Popen, like check_output, run, call, etc...
  • Use os.spawnl instead of subprocess Popen.

None of these worked. What happens most of the times is that the server fails to start properly, so all the other tests fail and the modbus_server process is not terminated (I have to kill it manually).

Do you have any idea? Thank you.

Rick
  • 1
  • 1

1 Answers1

0

I will share how I solved my issue and what I learned so far:

About the attempt to make the modbus TCP server running as a subprocess while running tests, you need to change a few things to make it work decently.

The first thing is to use "setUpClass" and "tearDownClass" instead of setUp and tearDown, (you can use also both of them but in my case I needed the first two) so that you can run the setup of your server just once for all the test suite and not before and after each test case. Unfortunately, the syntax for setUpClass and tearDownClass is not so straightforward. This answer helped me a lot: Run setUp only once for a set of automated tests

The second thing is about the TCP socket. Basically, I wanted to be sure that my tests worked fine and run them a few times in a row. So, sometimes everything worked fine, other times they failed. After a while I found that if I run the tests without waiting at least one minute before trying again, they will fail. This is due to the TIME_WAIT (set to 60 seconds by default) of the TCP socket bound by the modbus server on the local address and port 5020 in my case. If you want to re-bind a socket on the same TCP address and port you used before, you will have to wait 60 seconds. This answer was useful to understand why this happens: Setting TIME_WAIT TCP

After I figure out how to make all that stuff work together, I decided to use mocks because my solution seemed too hacky for my tastes.

Rick
  • 1
  • 1