I had an opportunity recently to play with test cases and asked my colleague, “What do I need to test?”
He said, “Mate, this is a unit test, and you need to decide the test cases according to the request and response, which should cover all the scenarios.”
This presented a dilemma for me, so I decided to write this complete guide for test cases. Let’s begin with my first question.
What is a Test Case?
In their simplest form, test cases are the set of conditions under which a tester determines whether the software satisfies requirements and functions properly. In layman’s terms, these are predefined conditions to check that the output is correct.
What Do I Need to test?
There is usually a simple answer to this question, by using a coverage package that measures the code coverage that also works during test execution. You can learn more about this in its official documents. Unfortunately, this was not the case in my situation.
The second approach is fairly straightforward. Typically, test cases are written by the developer of the code – and if you are the developer of the code, you are well aware of the flow of the code. In this situation, you need to write your test cases around the request and expected response of the code.
For example, if you are writing test cases for the division of a number, you must think about the code's expected input and expected output.
Test-driven Development Definition: "Test-driven development (TDD) is a software development process relying on software requirements being converted to test cases before software is fully developed, and tracking all software development by repeatedly testing the software against all test cases."
Django’s unit tests use a Python standard library module called unit test. The below module shows tests using a class-based approach.
How to Start Writing Test Cases
Here, we have one example for the method and class where we can start the test cases.
Class ProfileTestCase(TestCase):
def setUp(self):
pass
def test_my_test_1(self):
self.assertTrue(False)
def test_my_test_2(self):
self.assertTrue(False)
def tearDown(self):
pass
The following is a general test template for writing test cases in Django python.
For this example, TestCase is one of the most important classes provided by the unit test module, and it provides the foundation for testing our functions.
Also, for this example, SetUp is the first method that we run in our testing of the code. Therefore, it helps us set up the standard code required for each method that we can use in our entire testing process inside our testing class.
A teardown test case always runs last, as it can delete all objects or tables made while testing. It will also clean the testing environment after completing a test.
Now, let’s write out the test case:
Class CourierServices(TestCase):
def setup(self):
self.courier_data = CourrierModel.objects.all()
self.url = ‘/courier/service/’ #This is the url which we are going to hit for the response
def test_route(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code,200) #here we are checking for the status 200
def test_zipcode(self):
test_courrier_zip_code(self):
zip_code = “110001”
query_param = {‘zip_code’: zip_code}
response = self.client.get(self.url, data=query_params) #(we are trying to hit the url(self.url) using #parameter(data=query_params) and collecting the response in (response))
self.assertEqual(200, response.status_code)
#here test the response code you get from the url and compare it with the 200
response_json = response.json()
results = response_json.get('results', [])
self.assertIsInstance(results, list)
self.assertEqual(results[0]['zip_code'], zip_code)
Here is another valuable code sequence, and here we are trying to test the most common code known as the Login Function:
Class loginTest(TestCase):
def setUp(self):
self.user = get_username_model().objects.create_user(username='test', password='test123', email='test@test.com',mobile_no=1234567890)
self.user.save()
def test_correct_user_pass(self):
user = authenticate(username='test', password='test123')
self.assertTrue((user is not None) and user.is_authenticated)
def test_wrong_username(self):
user = authenticate(username='fakeuser', password='test123')
self.assertFalse(user is not None and user.is_authenticated)
def test_wrong_password(self):
user = authenticate(username='test', password='fakepassword')
self.assertFalse(user is not None and user.is_authenticated)
def tearDown(self):
self.user.delete()
Note: A test method passes only if every assertion in the method passes. Now, you may be wondering, What do these assertions mean, and how do you know which ones are available? I will try to answer these questions as thoroughly as possible.
Here are some commonly used assertion methods:
Method |
Meaning |
assertEqual(a, b) |
a==b |
assertNotEqual(a, b) |
a != b |
assertTrue(x) |
bool(x) is True |
assertFalse(x) |
bool(x) is False |
assertIs(a, b) |
a is b |
assertIsNot(a, b) |
a is not b |
assertIsNone(x) |
x is None |
assertIsNotNone(x) |
x is not None |
assertIn(a, b) |
a in b |
assertNotIn(a, b) |
a not in b |
assertIsInstance(a, b) |
isinstance(a, b) |
assertNotIsInstance(a, b) |
not isinstance(a, b) |
These methods are empowering; sometimes when we use them, an exact match isn’t required.
For example, how do I test for x-y = almost zero? This is where assertion methods can help. I see it as the “lifesaver” method.
Method |
Meaning |
assertAlmostEqual(a, b) |
round(a-b,7)==0 |
assertNotAlmostEqual(a,b) |
round(a-b,7)!=0 |
assertGreater(a, b) |
a>b |
assertGreaterEqual(a,b) |
a>=b |
assertLess(a, b) |
a<b |
assertLessEqual(a, b) |
a<=b |
assertRegex(s, r) |
r.search(s) |
assertNotRegex(s, r) |
not r.search(s) |
assertCountEqual(a, b) |
a and b have the same elements in the same number, regardless of their order. |
assertListEqual(a, b) |
It compare two list |
assertTupleEqual(a, b) |
It compare two tuple |
assertSetEqual(a, b) |
It compare two set |
assertDictEqual(a, b) |
It compare two dictionary |
Now that we know how to write the test cases, let me show you how to run them. Running the test cases is easy in Django python.
Write your test cases in the module, then go to the terminal and Run this command:
Python –m unittest my_test_module_1 my_test_module_2
If you want to run the test class, then use:
Python –m unittest my_test_module_1.TestClass
If you want to test your method, run this:
Python –m unittest my_test_module_1.TestClass.my_test_method
You can also run this test case:
Python -m unittest tests/my_test_testcase.py
Sometimes, we want to run the test cases via docker. For that, you can use the following method.
- First, go inside your web container using exec:
docker exec -it my-own-services_web_1 \bin\bash
- Then you will get the cmd prompt like this:
runuser@123456789:/opt/project123$
Note: You need to check your docker-compose.yaml and see the volume path. It will look something like this - .:/opt/app and it may change in your case.
python3 manage.py test test_folder.sub_folder.test_views.YourTestCases --settings=docker.test_settings
I hope this blog inspires you to start coding with the TDD approach, which will help make your code bug-free and robust too.
Remember the Golden Rules of TDD
- Write production code only to pass a failing unit test.
- Write no more of a unit test than is sufficient to fail (compilation failures are failures).
- Write no more production code than is necessary to pass the one failing unit test.
Next blog will cover the same in detail…