Code testing is a very essential part of software development, testing your code while developing is referred to as TDD(Test Driven Development). Unfortunately, a lot of developers neglect its usefulness and go on to just label it time-consuming, irrelevant, and cumbersome. However, the advantages of testing your code before deployment cannot be overemphasized. Below are some of the advantages of testing your code:
Reasons to start testing your code
- Testing of code will help prevent you from deploying sub-standard code and in time help improve your coding skills. Keeping your code clean and efficient is a very important skill when working on large projects in a large company with a large team of developers.
- Testing of code will definitely improve your code stability. When every piece of code for each version or commit is properly tested, a stable build is a result of these processes and there will be some certainty your code will not end up crashing or develop issues on some devices.
- Testing when developing a project or software that is going to be open-source greatly helps to familiarize your contributors with all the test cases they should keep in mind when making changes to the software. This way you ensure everyone is on the same page.
- Testing can also help in discovering potential security threats early. Every year tech companies around the world lose a lot of money from cyber attacks
Now that we know the advantages of testing our code, we will now look at the two methods for testing our code during development.
Methods of testing code
There are two methods of analyzing and testing our code; dynamic code analysis and static code analysis. Dynamic code analysis simply involves testing our code by running it and evaluating the errors that may arise. This method is popular among developers and beginners.
The second method is static code analysis which simply put is the opposite of the first method; dynamic code analysis. Static code analysis involves evaluating our code for errors without running it. This is method is called “white box testing” because the code is available to the testers to see unlike in “black box testing”. Major software testing involves static code analysis, where the developers look for bugs. We are going to be focusing on the second method; Static Code Analysis, while also applying it with a tool called “Prospector”.
The Prospector Tool
Prospector is a tool that analyzes Python code, outputs information about the errors, potential problems, convention violation, and complexity of the program. It possesses the functionalities of other python code analysis tools such as Pylint, Pep 8, and McCabe complexity. All these other tools are amazing on their possessing unique functionalities, prospector equips you with all their functions, just at the tip of your finger. We have been doing a lot of explaining lets finally get to do the dirty work.
Writing and Analyzing our Program
We will be writing an API to collect names and emails of users with Flask and storing them in the database.
Prerequisites:
Python, Flask, SQLAlchemy, and Prospector.
Installing our dependencies with our command shell
# run this on your command line interface $ pip install prospector $ pip install flask $ pip install flask-sqlalchemy $ pip install prospector # to check if prospector installed successfully $ prospector --version$ prospector 1.3.1
Now that we have successfully installed our dependencies we can write our flask API that we are going to be testing.
Our Flask Program
Let us begin importing the required modules or libraries in our program.
#Importing Libraries import os from flask import Flask,send_from_directory,request,url_for,jsonify,render_template,redirect from flask_restful import reqparse, abort, Api, Resource from flask_sqlalchemy import SQLAlchemy import prospect
Next, we initialize our Flask and configure our database using our app config and SQLAlchemy. Our database filename is data.sqlite3
app = Flask(__name__) application=app api = Api(app) db = SQLAlchemy(app)app.config ['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.sqlite3'
Now we would be creating our database tables and their structure using ORM(Object Relational Mapping) done with SQLAlchemy, whereas our table names are the class names and the class attributes are the table fields. We then use the command create_all to create our database file.
class User(db.Model):id = db.Column('id', db.Integer,primary_key=True) name = db.Column('name', db.String(100), nullable=True) email = db.Column('email', db.String(100), nullable=True) def __init__(self,id,student_id,pin,school_id,date,expire): self.id = id self.name = name self.email = email db.create_all()
At this point, we will be defining our API endpoint save which would save the user’s name and email to the database. Before saving we simply check if the email provided by the user already exists in the database. To save we define an object of the database class “User” called “user” and add this object instance to the DB.
class save(Resource): def get(self): name=request.form.get("name") if name: pass else: name= request.args.get("name") email=request.form.get("email") if email: pass else: email=request.args.get("email") if User.query.filter_by(email=email).first(): return jsonify({"success": False, "message": "Email already saved"}) else: user = User(name=name,email=email) db.session.add(user) db.session.commit() return jsonify({"success": True, "message": "data saved"})
Finally, we add a route for the save resource we just created above
#adding resouce with route api.add_resource(save, '/save') if __name__ == '__main__': app.run(debug=False)
If you successfully followed the previous steps, congratulations !!!, we can now test our code with the “Prospector” tool.
Using Prospector without customizing configurations
NOTE: Currently prospector supports just Celery, Django and Flask. So let’s try to stick to these frameworks when utilizing them. We are going to try out our prospector tool without customizing any configuration for coding style or strictness levels.
# run this on your command line interface prospector collect.py
Below are the results of running prospector on our Flask app:
In the image above, we can see all the 22 error messages, the tools used, strictness levels, and other settings on our prospector tool.
The prospector tool highlighted that from line 2 to line 4, there were some packages we imported in the program that we didn’t utilize. The packages are os, send_from_directory, and reqparse. With this, we can now go-ahead to remove these packages from our code.
We can also see that prospector generated an indentation error message because in our User class we indented with three spaces instead of four spaces according to the pep8 writing style. With this we can also make the pointed out writing correction, however, this is a less severe case and can be handled when we reduce the strictness levels of the prospector and customize it to your coding style.
Next on lines 20 to 23, we have error messages generated with the Pylint library. On line 20 we have a “too many arguments” error which is caused by exceeding the default maximum value of arguments __init__ function can possess. This maximum value can also be customized to whatever maximum number you desire. On lines 21 to 23, we have indentation error messages we talked about. However, for this case, some of the arguments defined were unused therefore irrelevant to our code. On line 25 we have a pep8 error message which was generated because according to the pep8 writing style we were meant to leave blank lines after our class definition before our next line of code. Like the indentation error, this can also be handled by reducing the strictness levels and customizing to the writing style of your choice. Still, on our user class, we can see the prospector raised an “undefined variable” error message for name and email because we had them used in our __init__ function but not defined as arguments for it. We can debug this by adding names and email to the arguments.
On line 46 we have another error message;” no-else-return” which is generated by Pylint. This error message was raised because Pylint recognizes that for the case where the condition in the if statement a return statement is executed and anything, after it is ignored, therefore an else statement after this, will be ignored too and be irrelevant, but if the condition is false then the return statement in it would be ignored and the return statement after it will be executed instead. Below is an example
The last error message is a pep8 indentation error located at line 55. This error is generated because our comment did not start at the beginning of the line and did not have four spaces but three instead. This comment was irrelevant in this code anyway so we would just remove it.
We are finally done with making necessary corrections to our code based on the prospector’s error messages. Our code should now look like this
#Importing Libraries from flask import Flask,jsonify from flask_restful import Api, Resource from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) application=app api = Api(app) db = SQLAlchemy(app) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.sqlite3' class User(db.Model): id = db.Column('id', db.Integer,primary_key=True) name = db.Column('name', db.String(100), nullable=True) email = db.Column('email', db.String(100), nullable=True) def __init__(self,id,name,email): self.id = id self.name = name self.email = email db.create_all() class save(Resource): def get(self): name=request.form.get("name") if name: pass else: name= request.args.get("name") email=request.form.get("email") if email: pass else: email=request.args.get("email") if User.query.filter_by(email=email).first(): return jsonify({"success": False, "message": "Email already saved"}) user = User(name=name,email=email) db.session.add(user)db.session.commit() return jsonify({"success": True, "message": "data saved"}) #adding resouce with route api.add_resource(save, '/save') if __name__ == '__main__': app.run(debug=False)
Customizing Prospector
The prospector can be configured by creating a profile. A profile is a YAML file containing several sections as described below. The prospector will search for a .prospector.yaml file (and several others) in the path that it is checking. If found, it will automatically be loaded. You can also pass in the profile path as an argument as shown below
$ prospector --profile /path/to/your/profile.yaml
An example of a YAML file with some customized configurations can be seen below
output-format: json output-format: json strictness: medium test-warnings: true doc-warnings: false member-warnings: false inherits: - default ignore-paths: - docs ignore-patterns: - (^|/)skip(this)?(/|$) autodetect: true max-line-length: 88 bandit: run: true options: config: .bandit.yml dodgy: run: true frosted: disable: - E103 - E306 mccabe: run: false options: max-complexity: 10 pep8: disable: - W602 - W603 enable: - W601 options: max-line-length: 79 pep257: disable: - D100 - D101py flakes: disable: - F403 - F810 pylint: disable: - bad-builtin - too-few-public-methods options: max-locals: 15 max-returns: 6 max-branches: 15 max-statements: 60 max-parents: 7 max-attributes: 7 min-public-methods: 1 max-public-methods: 20 max-module-lines: 1000 max-line-length: 99 max-args: 6 pyroma: disable: - PYR15 - PYR18 mypy: run: true options: ignore-missing-imports: true follow-imports: skip vulture: run: true
After saving your configurations and adding the path to your profile, the prospector tools will now work with the configurations you provided. Configuring before use is very important especially when testing or evaluating large projects. Without configuring, one can be overwhelmed with the number of error messages and ultimately will be unable to achieve anything. So setting up the configurations that work for you is advised.
You could see more about prospector and their configurations in their official documentation
Conclusion
- We learned about code testing, its importance, and the methods of code testing.
- We learned about static code analysis
- We learned about the Prospector tool and what makes it unique among other static analysis tools is its ability to combine several static analysis tools in evaluating a piece of code.
- We learned how to use prospector to test a piece of code.
After reading this article, readers should now be able to understand static code analysis and how to use prospector in analyzing their python codes and projects. Thank you for your time and see you soon😃. For any questions don’t hesitate to contact me on Twitter: @LordChuks3.