Testing in programming is a best practice that all developers should undertand and utilize. A testing unit should focus on one tiny bit of functionality and prove it correct. Each test unit must be fully independent. Each test must be able to run alone, and also within the test suite, regardless of the order that they are called. The implication of this rule is that each test must be loaded with a fresh dataset and may have to do some cleanup afterwards. This is usually handled by setUp()
and tearDown()
methods.
Example
Here is tests.py
import unittest import functions class TestClass(unittest.TestCase): def test_multiplication(self): self.assertEqual(functions.multiply(3,3), 9, "should be 9") if __name__ == '__main__': unittest.main()
Here is functions.py
def add(num1, num2): return num1 + num2; def multiply(num1, num2): return num1 * num2;
setUp and testDown functions
Tests can be numerous, and their set-up can be repetitive. Luckily, we can factor out set-up code by implementing a method called setUp()
, which the testing framework will automatically call for every single test we run. Note that the order in which the various tests will be run is determined by sorting the test method names with respect to the built-in ordering for strings. If the setUp()
method raises an exception while the test is running, the framework will consider the test to have suffered an error, and the test method will not be executed. We can provide a tearDown()
method that tidies up after the test method has been run. If setUp()
succeeded, tearDown()
will be run whether the test method succeeded or not.
class TestClass(unittest.TestCase): def setUp(self): print("set up") def test_multiplication(self): self.assertEqual(functions.multiply(3,3), 9, "should be 9") def tearDown(self): print("tear down") if __name__ == '__main__': unittest.main()
Test Suite
It is recommended that we use TestCase implementations to group tests together according to the features we test.
import unittest import functions from tests import TestClass def suite(): suite = unittest.TestSuite() suite.addTest(TestClass('test_multiplication')) return suite if __name__ == '__main__': runner = unittest.TextTestRunner() runner.run(suite())
class TestClass(unittest.TestCase): def setUp(self): print("set up") def test_multiplication(self): self.assertEqual(functions.multiply(3,3), 9, "should be 9") def tearDown(self): print("tear down")
How to skip tests
Skipping a test is simply a matter of using the skip()
decorator or one of its conditional variants, calling TestCase.skipTest()
within a setUp()
or test method, or raising SkipTest
directly.
class TestClass(unittest.TestCase): def setUp(self): print("set up") @unittest.skip("demonstrating skipping") def test_multiplication(self): self.assertEqual(functions.multiply(3,3), 9, "should be 9") def tearDown(self): print("tear down") if __name__ == '__main__': unittest.main()
Testing with Pytest
Python allows for user input. That means we are able to ask the user for input either to interact with users and get data to provide some sort of result. Most programs today use a dialog box for that but for console we are using input.
input() function
The input from the user is read as a string and in most cases is assigned to a variable. After entering the value from the keyboard, you have to press the “Enter/Return” button. Then the input() function reads the value entered by the user.
Syntax
variable = input(message)
The message string is displayed on the console and the control is given to the user to enter the value. You should print some useful information to guide the user to enter the expected value.
Example
name = input("What is your name:") print("Hello " + name+"! Welcome to my Tutorial.")
Input numeric values
favoriteNumber = int(input("What is your favorite number? ")) print("Your favorite number is {}".format(favoriteNumber))
If you put in a non numeric value, you will get this error which means that you input an invalid value for int
ValueError: invalid literal for int() with base 10
What is OOP?
Object-oriented programming has some advantages over other design patterns. Development is faster and cheaper, with better software maintainability. This, in turn, leads to higher-quality software, which is also extensible with new methods and attributes. The learning curve is, however, steeper. The concept may be too complex for beginners. Computationally, OOP software is slower, and uses more memory since more lines of code have to be written.
Python is a multi-paradigm programming language. It supports different programming approaches including OOP.
An object has two characteristics:
OOP works with classes and objects. A class is a blueprint for the object. An object (instance) is an instantiation of a class. When class is defined, only the description for the object is defined. Therefore, no memory or storage is allocated.
class User(): name = str age = int admin_type = "ADMIN" user_type = "USER" def __init__(self, name, age): self.name = name self.age = age def get_name(self): return self.name
Inheritance
Inheritance is a way of creating a new class for using details of an existing class without modifying it. The newly formed class is a derived class (or child class). Similarly, the existing class is a base class (or parent class).
class User(): name = str age = int admin_type = "ADMIN" user_type = "USER" def __init__(self, name, age): self.name = name self.age = age class Manager(user.User): def __init__(self, name, age): # call super() function super().__init__(name, age) print("Manager is ready") #inheritance man = Manager("Laulau",23) print(man.get_name()) # output Laulau
Encapsulation
Using OOP in Python, we can restrict access to methods and variables. This prevents data from direct modification which is called encapsulation. In Python, we denote private attributes using underscore as the prefix i.e single _
or double __
.
class Manager(user.User): def __init__(self, name, age, active): # call super() function super().__init__(name, age) self._active = True print("Manager is ready") # OOP - inheritance, encapsulation man = Manager("Laulau",23, False) print(man.is_active() # output False
Polymorphism
Polymorphism is an ability (in OOP) to use a common interface for multiple forms (data types).
Suppose, we need to color a shape, there are multiple shape options (rectangle, square, circle). However we could use the same method to color any shape. This concept is called Polymorphism.
class Manager(user.User): def __init__(self, name, age, active): # call super() function super().__init__(name, age) self._active = True print("Manager is ready") def is_active(self): return self._active class CTO(user.User): def __init__(self, name, age, active): # call super() function super().__init__(name, age) self._active = True print("Manager is ready") def is_active(self): return self._active def print_user(man): print(man.__dict__) print(man.is_active()) man = Manager("Laulau",23, False) cto = CTO("Laulau",23, False) print_user(man) print_user(cto) # output {'name': 'Laulau', 'age': 23, '_active': True} True {'name': 'Laulau', 'age': 23, '_active': True} True
Key take aways