Understanding Unit Tests
00:00 In the previous lesson, I showed you how to get mock objects to behave even more like the code they’re replacing. In this lesson, I’m going to take a little tangent and show you Python’s unit test framework.
00:12
Keeping with Python’s batteries-included philosophy, the standard library comes with a unit test framework. Do note that there are also third-party libraries out there that take a different approach, pytest being the most popular, but for this course, I’m going to stick with the one that comes with Python.
00:28 To write a Python unit test, you declare a class known as a test case. Inside of this class, you declare one or more methods, which are the tests that you want to run.
00:39
Then on the command line, you use the -m flag with Python to execute the unittest module directly, which will then look for the tests to run. Your test case classes should go in files named with test_ as a prefix.
00:55 This is what tells Python that the code is for testing. You can also do much more complicated things than that, but I’m going to keep it simple in this course. Inside the test cases, you use assertion statements to show that your code is working.
01:09 If all the assertions pass, meaning the method returns without throwing an exception, then the test is considered passed. I’m going to show you some silly little code first, then a test case to go with it.
01:22
This is bork.py containing the function bork(). There are two parts to the function. The first part checks if the argument is a string.
01:30
If it isn’t, it raises a ValueError. Normally, you wouldn’t bother with this. You might just let the line throw an exception if you use it incorrectly.
01:38
But I want a case where an exception is explicit so that I can show a negative test case as well as a positive one. The other part of this code simply appends ", bork, bork bork”` on the end of whatever string was passed in.
01:51 Now that I’ve got some code to test, let’s look at a test to go with it.
01:57
This is test_bork.py. Remember, the name of the file is important, as that is how the framework knows it contains a test. As I mentioned, you group tests together by creating a TestCase class, so here I’m importing it so I can inherit from it.
02:14
On this line, I’m importing the code that I’m going to test. I’ll be calling the bork() function, and then checking the results to see if it’s working as expected.
02:23
This is my TestCase. I’m not sure how standard this is, but I usually name mine something Test..., where the something is a short version of what I’m testing: Bork in this case. Inside the class, I declare a method.
02:36
Like with the filename, the method name is important. The test harness only calls methods that begin with the word test. It’s done this way so that you can also create helper functions that the test harness doesn’t call directly.
02:49 Inside of the test method, I first declare what result I’m expecting back from my function. Then I call the function, and finally, I assert something.
02:59
The TestCase-based class has a dozen or so assertion methods that you use to confirm that your code did what it was supposed to. This stuff has been around so long, it doesn’t use the snake_case that more modern Python has come to use.
03:12 This can be a little disconcerting style-wise, and sometimes means you have to go back to the manual to look stuff up, but it is what it is. This particular assertion asserts that two values are equal.
03:24
If our code is working, result and expected will be the same, so this method will do nothing. If the code wasn’t working, this assertion would result in an exception, which would cause the test to fail and give you info on how the two values didn’t match.
03:39 Let me open a shell and run this.
03:42
The -m argument to Python tells it to execute the named module. When you run the unittest module, it looks for test files and then runs the tests.
03:52 The output shows you that one test got run, and that it passed. So far so good, but I haven’t actually tested everything in the code. You’ll recall that it’s supposed to raise an exception if you pass in something other than a string.
04:06 Since raising exceptions is how the framework knows a test has failed, you have to do something a little different to check that an exception got raised.
04:16 How much testing happens inside of each of your methods is up to you. Here I’ve separated the positive and negative test cases into two different methods.
04:24
In real life, I don’t always do this, but I wanted to show the idea of multiple tests in the lesson. Like before, I’ve defined a method beginning with the word test, but this time I want an exception to get raised. To check this, you use the .assertRaises() TestCase method as a context manager.
04:43
This block will now catch a ValueError exception, and in fact, if a ValueError doesn’t get raised by the end of the block, the assertion fails.
04:52 This is how you ensure that your code is throwing what and when it is supposed to.
04:57
Inside of the block, I’m calling bork() with 3, which isn’t a string, so it should fail. Let’s try this out. This time, when I called the framework, two tests got run, and both of them passed.
05:13
Notice the two periods. That’s one for each test. If there was a failure instead of a period, you’d see a letter, usually an E or an F, telling you the type of failure that happened.
05:24 The test framework is quite robust, and you can do some pretty powerful stuff with it. I’ve only given you a taste, but it should be enough to show you how mocks work when used in the test code, which we’ll cover in the next lesson.
05:37
A couple more tricks before I let you go. The -v flag to the unittest module is verbose mode.
05:48 With verbose mode on, you don’t just get the little dot that represents a test. You see the whole name of the test. It’s a combination of the test file, class, and method names.
05:59 You can use this information to run a specific test.
06:09 This time only the positive test case was run because I specified the full file, class, and method to execute. That’s a lot of typing, though. In fact, one of the open-source libraries I maintain has a modified test discovery function that allows fuzzy matching of these specifiers.
06:25 I’ll link to it in the course summary in case you’re interested.
06:30
Now that you know how to write a Python unit test, you’re ready to see how to add Mock into the mix.
Become a Member to join the conversation.
