{
    "componentChunkName": "component---src-templates-post-jsx",
    "path": "/blog/testing-python",
    "result": {"data":{"post":{"id":"13dec5ab-cecf-5902-8fca-85c33051ac21","body":"var _excluded = [\"components\"];\nfunction _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n/* @jsxRuntime classic */\n/* @jsx mdx */\n\nvar _frontmatter = {\n  \"title\": \"Testing Python\",\n  \"slug\": \"/testing-python\",\n  \"description\": \"An introduction to writing unit-tests in Python with pytest\",\n  \"date\": \"2018-07-21T00:00:00.000Z\",\n  \"category\": \"Python\",\n  \"image\": \"./img/python-testing.jpg\",\n  \"tags\": [\"python\", \"tutorial\"],\n  \"published\": true\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n    props = _objectWithoutProperties(_ref, _excluded);\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Why?\"), mdx(\"p\", null, \"Though I\\u2019ve become accustomed to writing bash scripts to automate the testing of my command\", \"\\u2013\", \"line applications, upon being introduced to Ruby testing while reading Michael Hartl\", \"\\u2019\", \"s \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.railstutorial.org/book\",\n    \"target\": \"_blank\",\n    \"rel\": \"nofollow noopener noreferrer\"\n  }, \"The Ruby on Rails Tutorial\"), \", I was surprised by how much easier debugging is when you\", \"\\u2019\", \"re able to write tests for each component of the program, rather than just the input/output of my bash scripts. So given my proclivity for Python, I instantly became curious about the process of implementing similar tests in Python.  \"), mdx(\"h2\", null, \"Okay, How?\"), mdx(\"p\", null, \"After a bit of research into the available testing frameworks, I decided on \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/pytest-dev/pytest\",\n    \"target\": \"_blank\",\n    \"rel\": \"nofollow noopener noreferrer\"\n  }, \"pytest\"), \" due to the balance of capability and ease\", \"\\u2013\", \"of\", \"\\u2013\", \"use. Additionally, we\", \"\\u2019\", \"ll be writing a few tests for my \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/tterb/yt2mp3\",\n    \"target\": \"_blank\",\n    \"rel\": \"nofollow noopener noreferrer\"\n  }, \"yt2mp3\"), \" program, which I had previously been testing using one of the aforementioned bash scripts.  \"), mdx(\"h2\", null, \"Setup\"), mdx(\"p\", null, \"Before we start writing tests, we need to create a new file with the following convention, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"test_{filename}.py\"), \". This is so that \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/pytest-dev/pytest\",\n    \"target\": \"_blank\",\n    \"rel\": \"nofollow noopener noreferrer\"\n  }, \"pytest\"), \" can accurately identify the files that contain tests to execute, when we run from the command\", \"\\u2013\", \"line, as shown below:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"$ pytest\\n\")), mdx(\"p\", {\n    className: \"h-tip\"\n  }, \"I prefer running pytest with the `-v` or `--verbose` flag, as this prints the test that is being run and whether it passed/failed.\"), mdx(\"h2\", null, \"Basic Testing\"), mdx(\"p\", null, \"To get started, the first test we\", \"\\u2019\", \"re going to write is going to ensure that the program is able to accurately retrieve the title of a YouTube video when given a URL.  \"), mdx(\"p\", null, \"For this we\", \"\\u2019\", \"re going to:  \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Get the URL for a YouTube video with a known title\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"provide the URL to the yt2mp3.getVideoTitle() function\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Check that the function returns the expected video title\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"import os, pytest, yt2mp3\\n# We're not using 'os' in this test but we will later\\n\\ndef test_video_title():\\n    url = 'https://www.youtube.com/watch?v=C0DPdy98e4c'\\n    title = yt2mp3.getVideoTitle(url)\\n    assert title == 'TEST VIDEO'\\n\")), mdx(\"p\", null, \"You\", \"\\u2019\", \"ll notice that the only difference in this function is that pytest uses an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assert\"), \"\\u2013\", \"statement where you might usually expect a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"return\"), \"\\u2013\", \"statement.  \"), mdx(\"p\", null, \"Similarly, we can also write a test to check that the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yt2mp3.getVideoList()\"), \" function successfully retrieves the URLs for each video in the provided playlist. For this test, I\", \"\\u2019\", \"ve created a simple test playlist that features three videos to keep the number of URLs manageable and prevent modifications from changing the expected result.  \"), mdx(\"p\", null, \"So we need to:  \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Provide the URL for our test playlist\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Provide a list of the URLs for each video in the playlist\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Check that the function returns a list that matches the defined list\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"def test_get_playlist():\\n    url = 'https://www.youtube.com/playlist?list=PLGqB3S8f_uiLkCQziivGYI3zNtLJvfUWm'\\n    video_list = [\\n          'https://www.youtube.com/watch?v=_FrOQC-zEog',\\n          'https://www.youtube.com/watch?v=yvPr9YV7-Xw',\\n          'https://www.youtube.com/watch?v=-EzURpTF5c8'\\n    ]\\n    playlist = yt2mp3.getVideoList(url)\\n    assert playlist == video_list\\n\")), mdx(\"h2\", null, \"Using Fixtures\"), mdx(\"p\", null, \"To introduce the idea of fixtures, we\", \"\\u2019\", \"ll write a test that requires that we have a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Song\"), \" object, which stores the data necessary for downloading and setting the ID3 tags of the output mp3 file. Therefore, it\", \"\\u2019\", \"s understandable that a similar \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Song\"), \" object may be necessary to test multiple functionalities of the program. That\", \"\\u2019\", \"s where fixtures come in.  \"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"@pytest.fixture\\ndef test_song():\\n    data = yt2mp3.getSongData('Bold as Love', 'Jimi Hendrix')\\n    data['video_url'] = yt2mp3.getVideoURL(data['track_name'], data['artist_name'])\\n    yt2mp3.Song(data)\\n\")), mdx(\"p\", null, \"While, you can see that there\", \"\\u2019\", \"s not a lot of code that goes into creating the object, it\", \"\\u2019\", \"s best to follow the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself\",\n    \"target\": \"_blank\",\n    \"rel\": \"nofollow noopener noreferrer\"\n  }, \"DRY\"), \" principal and avoid redundancy.\", mdx(\"br\", {\n    parentName: \"p\"\n  }), \"\\n\", \"Now that our fixture is defined, we are going to use it to write two tests to check the program\", \"\\u2019\", \"s ability to download a video and convert the video to an mp3.\", mdx(\"br\", {\n    parentName: \"p\"\n  }), \"\\n\", \"To test the program\", \"\\u2019\", \"s download functionality, we\", \"\\u2019\", \"ll utilize the fact that the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yt2mp3.download()\"), \" function returns the filepath when the download is successful by asserting that the returned filepath exists.  \"), mdx(\"p\", {\n    className: \"h-note\"\n  }, \"Notice that we\\u2019ve provided our `test_song` fixture as a parameter of the test function.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"def test_video_download(test_song):\\n    video_path = yt2mp3.download(test_song.video_url)\\n    assert os.path.exists(video_path)\\n\")), mdx(\"p\", null, \"Once the program passes the above test, we now know that we have a video in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"~/Downloads/Music/temp/\"), \" directory that we can use to test the program\\u2019s conversion to mp3. Additionally, since the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yt2mp3.convertToMP3()\"), \" function also returns the output filepath we are able to use a similar method for validation as we saw in the previous test, by checking the existance of the output path.  \"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"def test_convert_mp3(test_song):\\n    temp_dir = os.path.expanduser('~/Downloads/Music/temp/')\\n    video_path = os.path.join(temp_dir, os.listdir(temp_dir)[0])\\n    song_path = yt2mp3.convertToMP3(video_path, test_song)\\n    assert os.path.exists(song_path)\\n\")), mdx(\"p\", null, \"Though, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yt2mp3.convertToMP3()\"), \" is also responsible for deleting the converted video file after the conversion. Luckily, we\", \"\\u2019\", \"re also able to validate this process within the same test by adding another condition and a bit of additional logging.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"def test_convert_mp3(test_song):\\n    errors = []\\n    temp_dir = os.path.expanduser('~/Downloads/Music/temp/')\\n    video_path = os.path.join(temp_dir, os.listdir(temp_dir)[0])\\n    song_path = yt2mp3.convertToMP3(video_path, test_song)\\n    # highlight-start\\n    if os.path.exists(video_path):\\n        errors.append('The video file wasn\\\\'t deleted after conversion')\\n    # highlight-end\\n    if not os.path.exists(song_path):\\n        errors.append('The output MP3 file doesn\\\\'t exist')\\n    assert not errors, 'errors occured:\\\\n{}'.format('\\\\n'.join(errors))\\n\")), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"Now we already have tests that cover a significant amount of the programs processes, without requiring a whole lot of work(\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"or code\"), \").\\nHopefully, these examples have provided you a launchpad for testing your own python programs. Though I expect that I\", \"\\u2019\", \"ll be updating this post or posting a follow\", \"\\u2013\", \"up with more helpful info as I become increasingly versed in Python testing.\"));\n}\n;\nMDXContent.isMDXComponent = true;","fields":{"slug":"/testing-python"},"frontmatter":{"title":"Testing Python","description":"An introduction to writing unit-tests in Python with pytest","date":"21 July 2018","category":"Python","image":{"childImageSharp":{"gatsbyImageData":{"layout":"fullWidth","backgroundColor":"#181818","images":{"fallback":{"src":"/static/ee0549a88840b706ae40c0b13fad25e2/8201c/python-testing.jpg","srcSet":"/static/ee0549a88840b706ae40c0b13fad25e2/20c4c/python-testing.jpg 750w,\n/static/ee0549a88840b706ae40c0b13fad25e2/33404/python-testing.jpg 1080w,\n/static/ee0549a88840b706ae40c0b13fad25e2/2ab6d/python-testing.jpg 1366w,\n/static/ee0549a88840b706ae40c0b13fad25e2/8201c/python-testing.jpg 1690w","sizes":"100vw"},"sources":[{"srcSet":"/static/ee0549a88840b706ae40c0b13fad25e2/0fc4b/python-testing.webp 750w,\n/static/ee0549a88840b706ae40c0b13fad25e2/eb745/python-testing.webp 1080w,\n/static/ee0549a88840b706ae40c0b13fad25e2/89397/python-testing.webp 1366w,\n/static/ee0549a88840b706ae40c0b13fad25e2/86677/python-testing.webp 1690w","type":"image/webp","sizes":"100vw"}]},"width":1,"height":0.40059171597633136}}}}},"avatar":{"childImageSharp":{"gatsbyImageData":{"layout":"constrained","backgroundColor":"#f8f8f8","images":{"fallback":{"src":"/static/b24b72d8f366da7af02542a735d82fa0/f9edd/me.jpg","srcSet":"/static/b24b72d8f366da7af02542a735d82fa0/93848/me.jpg 60w,\n/static/b24b72d8f366da7af02542a735d82fa0/73bb6/me.jpg 120w,\n/static/b24b72d8f366da7af02542a735d82fa0/f9edd/me.jpg 240w,\n/static/b24b72d8f366da7af02542a735d82fa0/97a19/me.jpg 480w","sizes":"(min-width: 240px) 240px, 100vw"},"sources":[{"srcSet":"/static/b24b72d8f366da7af02542a735d82fa0/927d1/me.webp 60w,\n/static/b24b72d8f366da7af02542a735d82fa0/507b0/me.webp 120w,\n/static/b24b72d8f366da7af02542a735d82fa0/8d565/me.webp 240w,\n/static/b24b72d8f366da7af02542a735d82fa0/21b1a/me.webp 480w","type":"image/webp","sizes":"(min-width: 240px) 240px, 100vw"}]},"width":240,"height":240}}}},"pageContext":{"slug":"/testing-python"}},
    "staticQueryHashes": []}