3
\$\begingroup\$

I have some Python tests I have written, but these are for making assertions about data in infrastructure code (AWS CloudFormation templates). I am using the Unittest framework.

I have code like this:

class TestData(unittest.TestCase):

    def testConfigStackYaml(self):

        bad_list = []
        for stack in glob('*/config'):

            stack_name = os.path.dirname(stack)
            expected_file = "%s/config/%s.yaml" % (stack_name, stack_name)

            if not os.path.isfile(expected_file):
                bad_list.append(expected_file)

        assert not bad_list, "Expected config/<stack_name>.yaml files to exist: %s" % bad_list

    def testStacksetMk(self):

        bad_list = []
        for stack in glob('*/config'):

            stack_name = os.path.dirname(stack)
            expected_file = "%s/stackset.mk" % stack_name

            if not os.path.isfile(expected_file):
                bad_list.append(expected_file)

        assert not bad_list, "Expected stackset.mk file to exist: %s" % bad_list

These are just two of my test cases. I have plenty more.

As can be seen, there is a problem with this code. The for loop for each test case is repeated for each test case, resulting in code duplication and inefficient code.

I have done it this way, however, because I want each test case to yield a helpful, specific message about what is wrong.

Can anyone see a cleaner way to implement this, so that I have the best of both worlds: duplication refactored out, but I still get to have a separate test case for each logical test?

\$\endgroup\$
2
  • \$\begingroup\$ Title of the question doesn't convey your business requirement. Can you change it to something that describes what you are trying to do. How about Assertions about data in infrastructure code as unit tests \$\endgroup\$ Dec 22 '19 at 13:26
  • \$\begingroup\$ @bhathiya-perera ok, done. \$\endgroup\$ Dec 22 '19 at 13:27
2
\$\begingroup\$

This is an interesting code.


Criticism:

def testConfigStackYaml(self):
  • Can we rename these functions to snake_case such as test_config_stack_yaml maybe.
assert not bad_list, "Expected config/<stack_name>.yaml files to exist: %s" % bad_list
expected_file = "%s/config/%s.yaml" % (stack_name, stack_name)
  • I personally like using the new formatter. % is more unreadable compared to "{stack_name}/config/{stack_name}".format(stack_name=stack_name)
  • We can also use os.path.join to join sections of a path. This makes our intention clear.
if __name__ == "__main__":
    unittest.main()
  • If you add this to the end of the file you can run these test files individually as a simple script.

Creating a custom assert

  • I recommend creating a memeber function named assert_config_exists.
  • Parameters: path_format, message_format
def assert_config_exists(self, path_format: str, message_format: str):
    bad_list = []
    for stack in glob('*/config'):

        stack_name = os.path.dirname(stack)
        expected_file = path_format.format(stack_name=stack_name)

        if not os.path.isfile(expected_file):
            bad_list.append(expected_file)

    self.assertFalse(bad_list, message_format.format(bad_list=bad_list))
```
\$\endgroup\$
6
  • \$\begingroup\$ Thank you, note that camelCase is used for consistency with the Unittest framework itself, whose methods are named setUp, tearDown etc. \$\endgroup\$ Dec 22 '19 at 13:41
  • 2
    \$\begingroup\$ Oh my apologies, I see your point docs.python.org/3/library/unittest.html The examples in there use snake_case all the same. I guess the Python community really really likes snake_case. :) \$\endgroup\$ Dec 22 '19 at 13:43
  • \$\begingroup\$ This is very helpful. I could be missing something, but does that custom member function somehow allow me to address the duplication issue? \$\endgroup\$ Dec 22 '19 at 14:34
  • \$\begingroup\$ Oh...my bad. I chose these examples badly. There are other examples I have that couldn't be refactored so easily this way. Ok, this is correct. \$\endgroup\$ Dec 22 '19 at 14:37
  • 1
    \$\begingroup\$ @AMC It's not allowed to change the question after getting answers. It will invalidate the answer/effort I made. Alex you can create a new question 👍 \$\endgroup\$ Dec 22 '19 at 18:04

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.