0

I have a python class that has methods to perform the CRUD operations via REST api:

class my_class():
    def get_obj(self,...) -> requests.Response:
        res  = requests.get(...);
        return res;

    def create_obj(self, ...) -> requests.Response:
        res  = requests.post(...);
        return res;

    def modify_obj(self, ...) -> requests.Response:
        res  = requests.put(...);
        return res;


    def delete_obj(self, ...) -> requests.Response:
        res  = requests.delete(...)
        return res;

I want to test these functionalities and to do that I can think of 3 ways:

  1. a single test that tests in cascade all the functionalities, something like:

    class test_myclass(unittest.TestCase):
        def setUp(self):
            self.api_class = my_class();
    
        def test_all(self):
            res = self.api_class.create_obj();
            self.assertTrue(res.ok);
            res = self.api_class.get_obj();
            self.assertTrue(res.ok);
            res = self.api_class.modify_obj();
            self.assertTrue(res.ok);
            res = self.api_class.modify_obj();
            self.assertTrue(res.ok);
    

    This has the advantage of being very compact and when the test succeed, everything is clean: all the created stuff is deleted. On the contrary, if one of the middle tests fails, then problems arise so maybe a tearDown() is needed.

  2. The second way is to test each functionality with a method:

    class test_myclass(unittest.TestCase):
        def setUp(self):
            self.api_class = my_class();
    
        def test_create(self):
            res = self.api_class.create_obj();
            if res.ok:
                self.api_class.delete_obj();
            self.assertTrue(res.ok);
    
        def test_get(self):
            res0 = self.api_class.create_obj();
            res = self.api_class.get_obj();
            if res0.ok:
                self.api_class.delete_obj();
            self.assertTrue(res.ok);
        def test_modify():
            res0 = self.api_class.create_obj();
            res = self.api_class.modify_obj();
            if res0.ok:
                self.api_class.delete_obj();
            self.assertTrue(res.ok);
    
         def test_delete
            self.api_class.create_obj();
            res = self.api_class.delete_obj();
            self.assertTrue(res.ok);
    

    This way is much more redundant but in my opinion tests are cleaner if a problem arises.

    1. The third way is to write a test class for each functionality: each one with a setUp and tearDown method:

      class test_myclass_create(unittest.TestCase):
          def tearDown(self):
               self.api_class.delete_obj();
      
          def test_create(self):
               res = self.api_class.create_obj();
               self.assertTrue(res.ok);
      
      class test_myclass_get(unittest.TestCase):
          def tearDown(self):
              self.api_class.delete_obj();
          def setUp(self):
              self.api_class.create_obj();
          def test_get(self):
              res = self.api_class.get_obj();
              self.assertTrue(res.ok);
      
      class test_myclass_modify(unittest.TestCase):
          def tearDown(self):
              self.api_class.delete_obj();
          def setUp(self):
              self.api_class.create_obj();            
          def test_modify():
              res = self.api_class.modify_obj();
              self.assertTrue(res.ok);
      
      class test_myclass_get(unittest.TestCase):
          def tearDown(self):
               self.api_class.delete_obj();
          def setUp(self):
               self.api_class.create_obj();                
          def test_delete:
               res = self.api_class.delete_obj();
               self.assertTrue(res.ok);
      

Does a best practice exist for such a case or is it just up to to the programmer?

0
2

The general best practice is to have one assertion per test. The idea driving this guideline is to limit the number of tests impacted by any given code change. There's nothing worse than changing one line of code only to discover 50 broken tests.

When a test breaks frequently people begin to think it is "flaky" or brittle. When people think this they tend to blame the test for every failure instead of the application.

Option #2 or #3 is the better way to go, depending on how much of the test setup is shared amongst the test cases and if there is a significant enough difference in the setup to warrant maintaining a whole separate test class.

Option #1 is definitely not preferable because one test has too many reasons to fail, making it harder to track down the defect. This will lead to people distrusting the test instead of the application. A good test seeds distrust of the application when the test fails, but reinforces trust in the application when the test passes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.