{"id":469,"date":"2016-04-03T12:45:30","date_gmt":"2016-04-03T04:45:30","guid":{"rendered":"https:\/\/www.techcoil.com\/blog\/?p=469"},"modified":"2018-09-04T22:59:27","modified_gmt":"2018-09-04T14:59:27","slug":"how-to-look-for-unittest-testcase-subclasses-defined-in-random-python-scripts-and-run-them-in-one-shot","status":"publish","type":"post","link":"https:\/\/www.techcoil.com\/blog\/how-to-look-for-unittest-testcase-subclasses-defined-in-random-python-scripts-and-run-them-in-one-shot\/","title":{"rendered":"How to look for unittest.TestCase subclasses defined in random Python scripts and run them in one shot"},"content":{"rendered":"<p>To ensure robustness of our Python application, members of the team had written unit test cases to assert that the various code units of the application are working as intended. Unit test cases that are meant to test the same program component are placed together as functions of the same class and contained in a Python script. <\/p>\n<p>As the Python application grows, the number of test scripts grew as well. In an attempt to perform automated unit testing with Jenkins, the first thing that I did was to write a Python script that will look for all the test cases in the application directory and run them at one go. <\/p>\n<p>This post describes the Python script in detail.<\/p>\n<h3>Gathering the requirements for the script<\/h3>\n<p>I first started off with a brief outline of what the script should achieve:<\/p>\n<ol>\n<li>The script should <a href=\"http:\/\/www.techcoil.com\/blog\/how-to-dynamically-look-for-classes-defined-in-python-3-file\/\" title=\"How to look for classes defined in Python 3 files dynamically\" target=\"_blank\">look into some predetermined folder(s) for Python files that contain classes<\/a> that extends from the <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TestCase\" title=\"Python api reference for unittest.TestCase\" target=\"_blank\"><code>unittest.TestCase<\/code><\/a> class. In my case, I will want to look into some sibling folders of my Python script.<\/li>\n<li>The script should be able to run new test scripts without any modifications.<\/li>\n<li>If any of the test case fails, the script shall exit with error so as to make Jenkins send an email to the developers.<\/li>\n<\/ol>\n<h3>Building the Python script that looks for <code>unittest.TestCase<\/code> subclasses defined in random Python scripts and run the test cases within the classes in one shot<\/h3>\n<p>With the requirements in mind, I went ahead to build the Python script. There are two main elements in the Python script:<\/p>\n<ol>\n<li>The Python class that is responsible for finding the unit tests that are located in directories that are siblings to the Python script.<\/li>\n<li>The code execution block that will run the unit tests that the Python class had gathered with an instance of <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TextTestRunner\" title=\"Python api reference for unittest.TextTestRunner\" target=\"_blank\"><code>unittest.TextTestRunner<\/code><\/a>.<\/li>\n<\/ol>\n<h4>Defining the Python class that is responsible for finding the unit tests that are located in directories that are siblings to the Python script<\/h4>\n<p>I first define a Python class that I can use for finding test cases inside some specific directories.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport importlib\r\nimport os\r\nimport re\r\nimport unittest\r\n\r\nclass UnitTestFinder:\r\n    \r\n    # Stores the list of directory paths that unittest.TestCase subclasses\r\n    # would reside in\r\n    _directoryPathList = &#x5B;]\r\n    \r\n    # Stores the subclasses of unittest.TestCase \r\n    _unitTestsList = &#x5B;]\r\n    \r\n    # Defines a regular expression that whitelist files that may contain unittest.TestCase\r\n    _testFilePathPattern = re.compile('.*&#x5B;Tt]est.*\\.py$')\r\n    \r\n    def addLookupDirectoryPath(self, directoryPath):\r\n        self._directoryPathList.append(directoryPath)\r\n    \r\n    def gatherUnitTests(self):\r\n        \r\n        # For each indicated directory path\r\n        for directoryPath in self._directoryPathList:\r\n            \r\n            if os.path.isdir(directoryPath):\r\n                # Walk through the contents \r\n                for subFolderRoot, foldersWithinSubFolder, files in os.walk(directoryPath):\r\n                \r\n                   # look at the files for subclasses of unittest.TestCase\r\n                   for file in files:\r\n                       \r\n                       fileBasename = os.path.basename(file)\r\n                       if self._testFilePathPattern.match(fileBasename) :\r\n        \r\n                           # Import the relevant module (note: a module does not end with .py)\r\n                           moduleDirectory = os.path.join(subFolderRoot, os.path.splitext(file)&#x5B;0])\r\n                           moduleStr = moduleDirectory.replace(os.path.sep, '.')\r\n                           module = importlib.import_module(moduleStr)\r\n        \r\n                           # Look for classes that implements unittest.TestCase\r\n                           for name in dir(module) :\r\n                               moduleAttribute = getattr(module, name)\r\n                               if isinstance(moduleAttribute, type) and issubclass(moduleAttribute, unittest.TestCase):\r\n                                   \r\n                                   # Use testLoader to load the tests that are defined in the unittest.TestCase class\r\n                                   self._unitTestsList.append(unittest.defaultTestLoader.loadTestsFromTestCase(moduleAttribute))\r\n\r\n            \r\n    def getUnitTestsList(self) : \r\n        return self._unitTestsList\r\n<\/pre>\n<p>There are three functions in <code>UnitTestFinder<\/code>:<\/p>\n<ol>\n<li>The <code>addLookupDirectoryPath<\/code> function.<\/li>\n<li>The <code>gatherUnitTests<\/code> function.<\/li>\n<li>The <code>getUnitTestsList<\/code> function.<\/li>\n<\/ol>\n<h5>What the <code>addLookupDirectoryPath<\/code> function does<\/h5>\n<p>The <code>addLookupDirectoryPath<\/code> function remembers the specific sibling directories that we want our <code>UnitTestFinder<\/code> to look into. The path to these directories are stored as list items via the <code>_directoryPathList<\/code> variable.<\/p>\n<h5>What the <code>gatherUnitTests<\/code> function does<\/h5>\n<p>The <code>gatherUnitTests<\/code> function then loop through the directory paths and begin searching for definition of subclasses of <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TestCase\" title=\"Python api reference for unittest.TestCase\" target=\"_blank\"><code>unittest.TestCase<\/code><\/a> inside each of the sibling directories. <\/p>\n<p>Using the <a href=\"https:\/\/docs.python.org\/3\/library\/os.html#os.walk\" title=\"Python api reference for os.walk\" target=\"_blank\"><code>os.walk <\/code><\/a> function on each of the directory path that we added via the <code>addLookupDirectoryPath<\/code> function, we check for files that start with the word 'test' or 'Test' and end with the '.py' extension via regular expression.<\/p>\n<p>For Python files that match our regular expression, we use the <a href=\"https:\/\/docs.python.org\/3.1\/library\/importlib.html#importlib.import_module\" title=\"Python 3 reference for import lib.import_module function\" target=\"_blank\">importlib.import_module()<\/a> function to import the Python file and make it accessible via the <code>module<\/code> variable.<\/p>\n<p>We then loop through the attribute names in the module via the <a href=\"https:\/\/docs.python.org\/3\/library\/functions.html#dir\" title=\"Python 3 reference for dir() function\" target=\"_blank\">dir()<\/a> function. With the <a href=\"https:\/\/docs.python.org\/3\/library\/functions.html#getattr\" title=\"Python 3 reference for getattr function\" target=\"_blank\">getattr()<\/a> function, we check for subclasses of the <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TestCase\" title=\"Python api reference for unittest.TestCase\" target=\"_blank\"><code>unittest.TestCase<\/code><\/a> class.<\/p>\n<p>If there are subclasses of <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TestCase\" title=\"Python api reference for unittest.TestCase\" target=\"_blank\"><code>unittest.TestCase<\/code><\/a>, we use <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.defaultTestLoader\" title=\"Python api reference for unittest.defaultTestLoader\" target=\"_blank\"><code>unittest.defaultTestLoader<\/code><\/a> to help us load the test cases within the subclasses. We then append whatever <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.defaultTestLoader\" title=\"Python api reference for unittest.defaultTestLoader\" target=\"_blank\"><code>unittest.defaultTestLoader<\/code><\/a> returns us to <code>self._unitTestsList<\/code>. <\/p>\n<h5>What the <code>getUnitTestsList<\/code> function does<\/h5>\n<p>The <code>getUnitTestsList<\/code> functions returns the unit tests that the <code>gatherUnitTests<\/code> function finds.<\/p>\n<h4>Building the code execution block that will run the unit tests that the Python class had gathered with an instance of <code>unittest.TextTestRunner<\/code><\/h4>\n<p>With <code>UnitTestFinder<\/code> defined , I then proceed to writing the code block that will utilise <code>UnitTestFinder<\/code> and run the test cases in one shot:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nif __name__ == &quot;__main__&quot; :\r\n\r\n    unitTestFinder = UnitTestFinder()\r\n    unitTestFinder.addLookupDirectoryPath('folderWithTestCases')  \r\n    unitTestFinder.gatherUnitTests()\r\n    \r\n    # Execute the test cases that had been collected by UnitTestFinder\r\n    testSuite = unittest.TestSuite(unitTestFinder.getUnitTestsList())\r\n    testResult = unittest.TextTestRunner(verbosity=2).run(testSuite) \r\n\r\n    # Throw an error to notify error condition.\r\n    if not testResult.wasSuccessful() :\r\n        raise ValueError('There were test failures.') \r\n<\/pre>\n<p>The code block starts with a test on whether the Python binary had ran our script first. If the script was ran first, we then:<\/p>\n<ol>\n<li>Create an instance of <code>UnitTestFinder<\/code>.<\/li>\n<li>Specify the directory to look for test cases, which in our case is just the directory 'folderWithTestCases'.<\/li>\n<li>Get the instance of <code>UnitTestFinder<\/code> to gather the test cases that are located inside of 'folderWithTestCases'.<\/li>\n<li>Create a <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TestSuite\" title=\"Python api reference for unittest.TestSuite\" target=\"_blank\"><code>unittest.TestSuite<\/code><\/a> instance from the test cases that <code>UnitTestFinder<\/code> could gather and make it available via the <code>testSuite<\/code> variable.<\/li>\n<li>Get the test results by creating an instance of <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TextTestRunner\" title=\"Python api reference for unittest.TextTestRunner\" target=\"_blank\"><code>unittest.TextTestRunner<\/code><\/a> and using it to run our test suite. We capture the result with the <code>testResult<\/code> variable.<\/li>\n<li>Finally we check whether the test run was successful via a call to <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html#unittest.TestResult.wasSuccessful\" title=\"Python api reference for unittest.TestResult.wasSuccessful\" target=\"_blank\"><code>testResult.wasSuccessful()<\/code><\/a>. If not, we throw an instance of <a href=\"https:\/\/docs.python.org\/3\/library\/exceptions.html#ValueError\" title=\"Python api reference for ValueError\" target=\"_blank\"><code>ValueError<\/code><\/a> to get Jenkins to send an email to all the developers in the team.<\/li>\n<\/ol>\n<h3>Putting everything together<\/h3>\n<p>Putting the codes together, we will yield the following script:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport importlib\r\nimport os\r\nimport re\r\nimport unittest\r\n\r\nclass UnitTestFinder:\r\n    \r\n    # Stores the list of directory paths that unittest.TestCase subclasses\r\n    # would reside in\r\n    _directoryPathList = &#x5B;]\r\n    \r\n    # Stores the subclasses of unittest.TestCase \r\n    _unitTestsList = &#x5B;]\r\n    \r\n    # Defines a regular expression that whitelist files that may contain unittest.TestCase\r\n    _testFilePathPattern = re.compile('.*&#x5B;Tt]est.*\\.py$')\r\n    \r\n    def addLookupDirectoryPath(self, directoryPath):\r\n        self._directoryPathList.append(directoryPath)\r\n    \r\n    def gatherUnitTests(self):\r\n        \r\n        # For each indicated directory path\r\n        for directoryPath in self._directoryPathList:\r\n            \r\n            if os.path.isdir(directoryPath):\r\n                # Walk through the contents \r\n                for subFolderRoot, foldersWithinSubFolder, files in os.walk(directoryPath):\r\n                \r\n                   # look at the files for subclasses of unittest.TestCase\r\n                   for file in files:\r\n                       \r\n                       fileBasename = os.path.basename(file)\r\n                       if self._testFilePathPattern.match(fileBasename) :\r\n        \r\n                           # Import the relevant module (note: a module does not end with .py)\r\n                           moduleDirectory = os.path.join(subFolderRoot, os.path.splitext(file)&#x5B;0])\r\n                           moduleStr = moduleDirectory.replace(os.path.sep, '.')\r\n                           module = importlib.import_module(moduleStr)\r\n        \r\n                           # Look for classes that implements unittest.TestCase\r\n                           for name in dir(module) :\r\n                               moduleAttribute = getattr(module, name)\r\n                               if isinstance(moduleAttribute, type) and issubclass(moduleAttribute, unittest.TestCase):\r\n                                   \r\n                                   # Use testLoader to load the tests that are defined in the unittest.TestCase class\r\n                                   self._unitTestsList.append(unittest.defaultTestLoader.loadTestsFromTestCase(moduleAttribute))\r\n\r\n            \r\n    def getUnitTestsList(self) : \r\n        return self._unitTestsList\r\n\r\nif __name__ == &quot;__main__&quot; :\r\n\r\n    unitTestFinder = UnitTestFinder()\r\n    unitTestFinder.addLookupDirectoryPath('folderWithTestCases')  \r\n    unitTestFinder.gatherUnitTests()\r\n    \r\n    # Execute the test cases that had been collected by UnitTestFinder\r\n    testSuite = unittest.TestSuite(unitTestFinder.getUnitTestsList())\r\n    testResult = unittest.TextTestRunner(verbosity=2).run(testSuite) \r\n\r\n    # Throw an error to notify error condition.\r\n    if not testResult.wasSuccessful() :\r\n        raise ValueError('There were test failures.')\r\n<\/pre>\n<h5>Trying out the script<\/h5>\n<p>To try out the script, I created a sample Python script, 'test_arithmetic_operations_a.py', and place it inside the directory, 'folderWithTestCases':<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport unittest\r\n\r\nclass ArithmeticTestCases(unittest.TestCase) :\r\n\r\n    def test_addition(self):\r\n        self.assertEqual(1+1, 2)\r\n        \r\n    def test_multiplication(self):\r\n        self.assertEqual(2*3, 6)\r\n<\/pre>\n<p>And another Python script, 'test_arithmetic_operations_b.py' and place it inside the directory, 'folderWithTestCases\/anotherFolder':<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport unittest\r\n\r\nclass ArithmeticTestCasesB(unittest.TestCase) :\r\n\r\n    def test_addition(self):\r\n        self.assertEqual(1+1, 2)\r\n        \r\n    def test_multiplication(self):\r\n        self.assertEqual(2*3, 6)\r\n<\/pre>\n<p>I then run the following command inside of the directory that contains the Python script that I had written earlier:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\npython3 run_unit_tests_in_one_shot.py \r\n<\/pre>\n<p>Which produced the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\ntest_addition (folderWithTestCases.test_arithmetic_operations_a.ArithmeticTestCasesA) ... ok\r\ntest_multiplication (folderWithTestCases.test_arithmetic_operations_a.ArithmeticTestCasesA) ... ok\r\ntest_division (folderWithTestCases.anotherFolder.test_arithmetic_operations_b.ArithmeticTestCasesB) ... ok\r\ntest_subtraction (folderWithTestCases.anotherFolder.test_arithmetic_operations_b.ArithmeticTestCasesB) ... ok\r\n\r\n----------------------------------------------------------------------\r\nRan 4 tests in 0.001s\r\n\r\nOK\r\n<\/pre>\n<p>I then change the test_addition test case by asserting that 1+1 equals 3 in order to create a test failure:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport unittest\r\n\r\nclass ArithmeticTestCasesA(unittest.TestCase) :\r\n\r\n    def test_addition(self):\r\n        self.assertEqual(1+1, 3)\r\n        \r\n    def test_multiplication(self):\r\n        self.assertEqual(2*3, 6)\r\n<\/pre>\n<p>This time, I got the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\ntest_addition (folderWithTestCases.test_arithmetic_operations_a.ArithmeticTestCasesA) ... FAIL\r\ntest_multiplication (folderWithTestCases.test_arithmetic_operations_a.ArithmeticTestCasesA) ... ok\r\ntest_division (folderWithTestCases.anotherFolder.test_arithmetic_operations_b.ArithmeticTestCasesB) ... ok\r\ntest_subtraction (folderWithTestCases.anotherFolder.test_arithmetic_operations_b.ArithmeticTestCasesB) ... ok\r\n\r\n======================================================================\r\nFAIL: test_addition (folderWithTestCases.test_arithmetic_operations_a.ArithmeticTestCasesA)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File &quot;\/techcoil\/poc\/FindAndRunUnitTestsAtOneGoProject\/folderWithTestCases\/test_arithmetic_operations_a.py&quot;, line 6, in test_addition\r\n    self.assertEqual(1+1, 3)\r\nAssertionError: 2 != 3\r\n\r\n----------------------------------------------------------------------\r\nRan 4 tests in 0.001s\r\n\r\nFAILED (failures=1)\r\nTraceback (most recent call last):\r\n  File &quot;run_unit_tests_in_one_shot.py&quot;, line 65, in &lt;module&gt;\r\n    raise ValueError('There were test failures.')    \r\nValueError: There were test failures.\r\n<\/pre>\n\n      <ul id=\"social-sharing-buttons-list\">\n        <li class=\"facebook\">\n          <a href=\"https:\/\/www.facebook.com\/sharer\/sharer.php?u=https%3A%2F%2Fwp.me%2Fp245TQ-7z\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n            <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Facebook.png\" alt=\"Facebook icon\"> Share\n          <\/a>\n        <\/li>\n        <li class=\"twitter\">\n          <a href=\"https:\/\/twitter.com\/intent\/tweet?text=&url=https%3A%2F%2Fwp.me%2Fp245TQ-7z&via=Techcoil_com\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Twitter.png\" alt=\"Twitter icon\"> Tweet\n          <\/a>\n        <\/li>\n        <li class=\"linkedin\">\n          <a href=\"https:\/\/www.linkedin.com\/shareArticle?mini=1&title=&url=https%3A%2F%2Fwp.me%2Fp245TQ-7z&source=https:\/\/www.techcoil.com\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/linkedin.png\" alt=\"Linkedin icon\"> Share\n          <\/a>\n        <\/li>\n        <li class=\"pinterest\">\n          <a href=\"https:\/\/pinterest.com\/pin\/create\/button\/?url=https%3A%2F%2Fwww.techcoil.com%2Fblog%2Fwp-json%2Fwp%2Fv2%2Fposts%2F469&description=\" class=\"pin-it-button\" target=\"_blank\" role=\"button\" rel=\"nofollow\" count-layout=\"horizontal\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Pinterest.png\" alt=\"Pinterest icon\"> Save\n          <\/a>\n        <\/li>\n      <\/ul>\n    ","protected":false},"excerpt":{"rendered":"<p>To ensure robustness of our Python application, members of the team had written unit test cases to assert that the various code units of the application are working as intended. Unit test cases that are meant to test the same program component are placed together as functions of the same class and contained in a Python script. <\/p>\n<p>As the Python application grows, the number of test scripts grew as well. In an attempt to perform automated unit testing with Jenkins, the first thing that I did was to write a Python script that will look for all the test cases in the application directory and run them at one go. <\/p>\n<p>This post describes the Python script in detail.<\/p>\n","protected":false},"author":1,"featured_media":1244,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"footnotes":""},"categories":[375],"tags":[226,195,239,238],"jetpack_featured_media_url":"https:\/\/www.techcoil.com\/blog\/wp-content\/uploads\/Python-Logo.gif","jetpack_shortlink":"https:\/\/wp.me\/p245TQ-7z","jetpack-related-posts":[],"jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts\/469"}],"collection":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/comments?post=469"}],"version-history":[{"count":0,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts\/469\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/media\/1244"}],"wp:attachment":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/media?parent=469"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/categories?post=469"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/tags?post=469"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}