{quality development}

How to properly write your project

Università degli Studi di Catania

Dipartimento di Matematica e Informatica

{quality development}

How to properly write your project

Università degli Studi di Catania - Dipartimento di Matematica e Informatica - 2025

{quality development}

How to properly write your project

# Intro

whoami

Hi, I am Stefano Borzì 👋

Open-source developer
Full Stack Developer at "Royal BAM Group"

PhD student at University of Catania

# Intro

Course introduction

  • Shell UNIX
  • Git
  • GitHub
  • Opensource
  • Python
  • Unit-test
  • SOLID principles
  • CI / CD pipeline & tools
# Intro

Exam

Mini-project in a group of two

# Shell UNIX

Shell UNIX

Mandatory to use for servers

Shell UNIX

What is a Shell?

The shell is a program where users can type commands, where it’s possible to create an empty directory with only one line of code.

What is?

Learning

Learning

Using a shell allows to be sure about what you are doing without having the mask of a GUI.

# Shell UNIX

Shell vs GUI

The shell is faster and it allows to learn more than a GUI.

GUI?

# Shell UNIX
# Shell UNIX
# Shell UNIX

Useful commands #1

Command Description
ls show files and directories
man read manual
cd open directories
mv move (or rename) file
cp copy file or directory (-r)
rm remove file or directory (-r)
pwd (current) path working directory
mkdir create a directory
nano, vim, ed terminal text editor
# Shell UNIX

cd directory_name

# Shell UNIX

cd ..

cd

ls path

# Shell UNIX

ls -f

ls

ls

ls test_*

ls *.cpp

ls ?.cpp

rm -r path/of/directory/

# Shell UNIX

rm -rf path/of/directory

rm

rm path/of/file

The shell does not have a trash bin: once something is deleted, it’s really gone.

# Shell UNIX

pwd

pwd - return the absolute path of the working directory

= ~/

/home/helias/

Pathnames

/home/helias/Desktop/file.png

# Shell UNIX

absolute path

home/helias/Desktop/file.png

relative path

./home/helias/Desktop/file.png

Desktop/file.png

./Desktop/file.png

=

..   upper directory

.   current directory

TAB button

# Shell UNIX

superpower: autocomplete

Useful commands #2

Command Description
cat displays the contents of its inputs
head displays the first 10 lines of its input
tail displays the last 10 lines of its input.
​command > [file] redirects a command’s output to a file (overwriting any existing content).
command >> [file] appends a command’s output to a file.
[first] | [second] it is a pipeline: the output of the first command is used as the input to the second.
grep [filter] grep part of input based on filter
# Shell UNIX

Useful commands #3

Command Description
Ctrl+C halts the current command
Ctrl+Z stops the current command, resume it in the background
Ctrl+R type to bring up a recent command
Ctrl+L, clear clean the terminal
!! repeats the last command
exit log out of current session
more, less read a file part by part
# Shell UNIX

Useful commands #4

Command Description
$@ take all inputs parameters
$1 take the first input parameter
$2 take the second input parameter
wget "web get", download from URL
curl transfer a URL, send http requests
zip it allows to compress directories and/or files
mysql / mysqldump client mysql
# Shell UNIX

Secure SHell (SSH)

# Shell UNIX

RSA

# Shell UNIX

private

key

public

key

Alice

Bob

public

key

private

key

SSH key

# Shell UNIX

private

key

public

key

Bob

# Shell UNIX

man

What manual page do you want?

woman

-bash: woman: command not found

alias woman = man

What manual page do you want?

woman

tmux - cheatsheet

# Shell UNIX

Cltr+B + D      # detach session

tmux new -s new-session

tmux a -t new-session

Exercises #1

# Shell UNIX
  1. make a directory called first
  2. change directory to the first folder
  3. create a file called person.txt
  4. change the name of person.txt to another.txt
  5. make a copy of the another.txt file and call it copy.txt
  6. remove the copy.txt file
  7. make a copy of the first folder and call it second
  8. delete the second folder
  9. Create a bash script that executes automatically all these tasks waiting 1 second for each task using sleep

Exercises #2

# Shell UNIX
  1. What does the man command do? Type in man rm. How do you scroll and get out?
  2. Look at the man page for ls. What does the -l flag do? What does the -a flag do?
  3. Type the following command to download and save the contents of google.com: curl https://www.google.com > google.html
  4. Use less to look at the contents of google.html.
  5. Look at the man page for less. Read the section on -p (/pattern). Search for the text hplogo in the google.html file.
  6. What is the shortcut to clear the terminal?
  7. What is an absolute path?
  8. What is an relative path?
  9. What do the r and f flags do with the rm command?

alias exercises

# Shell UNIX

echo "hello world" | espeak -v it

zipthis DIRECTORY

notify MESSAGE

Challenge: extend notify-me

# Shell UNIX

Extend the bash script notify.sh into other bash scripts that allow to send single file or directory (zipped), using:

$ tg-send path/of/the/file/or/directory

 

Bonus feature:

- recognize if the file is more than 50 MB, in that case the Telegram API will block you, so it is better to warn the user about it

- use telegram client (not bot) API to overcome the 50 MB limit and get 2 GB as limit.

 

References:

- https://github.com/Helias/Notify-me

- https://github.com/azerothcore/telegram-automated-db-backup

# GIT

Git

A way to manage your project

2005

# GIT
# GIT
# GIT

https://ohmygit.org/

https://github.com/firstcontributions/first-contributions

https://learngitbranching.js.org/

# GIT
$ git config

$ git add
$ git rm 
$ git mv
$ git commit -m 'desc commit'

$ git checkout -b branch_name # craete a new branch
$ git checkout branch_name
$ git merge REMOTE BRANCH # ex. git merge origin master

$ git reset
$ git revert

$ git status
$ git log
$ git diff

$ git init
$ git clone
$ git remote → git remote add, git remote -v, git remote rm
$ git fetch

$ git pull
$ git push
# GIT

Linux (or WSL), via package manager:

  $ apt install git

 

Mac via Homebrew or MacPort:

  $ brew install git

  $ port install git

 

Windows

  https://gitforwindows.org/

How to install GIT

# GIT

$ git config --global user.name "Stefano Borzì"
$ git config --global user.email "stefano@example.com"
$ git config --global core.editor nano


$ git config --list
user.name=Stefano Borzì
user.email=stefano@example.com
core.editor=nano
...


$ git config user.name
Stefano Borzì

# GIT
$ git init

$ git add
$ git rm 
$ git mv

$ git status
$ git diff

$ git commit -m 'desc commit'

$ git log
# GIT

git status

# GIT
# GIT
fix: for a fix                      ex. fix(main): windows build
feat: implementing a new feature    ex. feat(home): add footer
docs: for documentation.            ex. doc(contribution): add contribution guidelines
refactor: for refactoring purposes  ex. refactor(tests): replace all "pippo" variables
test: adding unit-tests, e2e-etest  ex. test(lessons): add unit-tests for lesson
chore: minor improvements           ex. chore(merge): solve conflict

Conventional Commits

# GIT

git diff

# GIT

checkout, revert, reset

$ git checkout [COMMIT]

$ git revert [COMMIT]

$ git reset 
# GIT

Branches and Merges

# GIT

Branches and Merges

# craete a new branch and checkout
$ git checkout -b branch_name

$ git checkout branch_name

$ git merge [REMOTE] BRANCH
# ex. git merge origin master
# GIT

Branches and Merges

# GIT

DMI Bot - network graph

# GIT

.gitignore

https://git-scm.com/docs/gitignore

# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp
*.js.map

# dependencies
/node_modules

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
.vscode/*

# System Files
.DS_Store
Thumbs.db
# GIT

GIT LFS

# GIT

GIT LFS

# GIT

Visual Studio Code plugins

Git related

  • Git Lens
  • Git History 

 

Generic plugins

  • sonarlint
  • change-case
# GIT

Exercise

wget https://bit.ly/3ucdNHK

$

/*
Make a BRANCH per each task.

- fix warning and errors
- fix the code
- make N dynamic, let the user choose N
- remove unused
- make some improvements on your own
*/
# GIT
$ git config

$ git init

$ git add
$ git rm 
$ git mv
$ git commit -m 'desc commit'

$ git checkout -b branch_name # craete a new branch
$ git checkout branch_name
$ git merge BRANCH # ex. git merge feature-1

$ git reset
$ git revert

$ git status
$ git log
$ git diff

all the commands shown so far

# GITHUB

GitHub

Git and cloud

2007

# GITHUB

What is GitHub?

# GITHUB

Bitbucket

GitLab

GitHub

# GITHUB

github.com/torvalds

# GITHUB

GitHub organizations

# GITHUB

Followers - Following - Stars

# GITHUB

Repository

# GITHUB

Create a new repository - 1

# GITHUB

Create a new repository - 2

# GITHUB

Create a new repository - 3

# GITHUB
$ git init
$ git clone
$ git remote [-v, add, rm]
$ git fetch

$ git pull
$ git push

Update your repository

# GITHUB

Update your repository

# GITHUB

Markdown - cheat sheet

# GITHUB

GitHub issues

# GITHUB

Telegram & GitHub

# GITHUB

GitHub Projects

# GITHUB

GitHub Pages

# GITHUB

Pull Request

# GITHUB

Pull Request

# GITHUB

Fork

# GITHUB

Pull Request

# GITHUB

Pull Request

UNICT Devs

# GITHUB

Pull Request

...others open source communities...

# GITHUB

Pull Request

# GITHUB

Pull Request

# GITHUB

GitHub - Main or Master branch?

# GITHUB

GitHub - Main or Master branch?

# GITHUB

GitHub - Main or Master branch?

# GITHUB

GitHub - Main or Master branch?

# GITHUB

GitHub - Main or Master branch?

# GITHUB

Ban GitHub for russian developers

# GITHUB

GitHub, do not ban us

https://github.com/1995parham/github-do-not-ban-us

# GITHUB

Github in freedom

https://github.blog/2021-01-05-advancing-developer-freedom-github-is-fully-available-in-iran/

OPEN SOURCE

Opensource

Let's explore some opensource community

OPEN SOURCE
OPEN SOURCE

"Se tu hai una mela, e io ho una mela, e ce le scambiamo, allora tu ed io abbiamo sempre una mela ciascuno. Ma se tu hai un'idea, ed io ho un'idea, e ce le scambiamo, allora abbiamo entrambi due idee"

~ George Bernard Shaw

OPEN SOURCE

Microsoft & open source

OPEN SOURCE

Microsoft & open source

OPEN SOURCE

Android

OPEN SOURCE

BSD

OPEN SOURCE

Software licenses

https://choosealicense.com/licenses/

OPEN SOURCE

AzerothCore

OPEN SOURCE

OpenSource communities

OPEN SOURCE

Manage open source communities

https://www.youtube.com/watch?v=ZSFDm3UYkeE

OPEN SOURCE

Why should I care?

OPEN SOURCE

Opensource & monetization

OPEN SOURCE

"Se tu hai una mela, e io ho una mela, e ce le scambiamo, allora tu ed io abbiamo sempre una mela ciascuno. Ma se tu hai un'idea, ed io ho un'idea, e ce le scambiamo, allora abbiamo entrambi due idee"

~ George Bernard Shaw

OPEN SOURCE

Informatico

ingegnere-informatico

DMI_Bot

Sfotted DIEEI

Spotted DMI Bot

OPEN SOURCE

UNICT Devs

OPEN SOURCE

UNICT Devs Projects

Python

OPEN SOURCE

DMI Bot contributors

OPEN SOURCE

SPOTTED DMI BOT

Python

OPEN SOURCE

SPOTTED DMI BOT

OPEN SOURCE

ERSU BOT

OPEN SOURCE

DMI NEWS

Python

OPEN SOURCE

DMI NEWS

OPEN SOURCE

UNICT list Telegram groups & bots

OPEN SOURCE

UNICT list Telegram groups & bots

Contributors

OPEN SOURCE

unict-reservation

OPEN SOURCE

MedBot

Python

OPEN SOURCE

Albo UNICT

Python

OPEN SOURCE

UNICT Bookmarket bot

Python

OPEN SOURCE

Studium Bot

Python

OPEN SOURCE

Open job DMI UNICT

https://open-job-dmi.unictdev.org/

https://t.me/OpenJobDMI

OPEN SOURCE

Saturday Morning Snippets

OPEN SOURCE

Santini Generator

OPEN SOURCE

UNICT Elezioni

OPEN SOURCE

UNICT Elezioni

OPEN SOURCE

OPIS - Manager

OPEN SOURCE
OPEN SOURCE

END

Python

Python

A human language

Python

What is Python?

Python is a popular programming language. It was created by Guido van Rossum, and released in 1991.

It is used for:

  • web development (server-side)
  • software development
  • mathematics
  • system scripting
Python

How much is it used?

https://insights.stackoverflow.com/survey/2021#most-popular-technologies-language

Python

Download & Install Python

Linux

$ sudo apt install python

 

Mac OS

$ brew install python

 

Windows

https://www.python.org/downloads/

Python

Hello World!

print('Hello World!')
#include <iostream>
using namespace std;

int main() {
    cout << "Hello World!" << endl;
    return 0;
}
Python

Python style & indentation

if 5 > 2:
  print("Five is greater than two!")
#include <iostream>
using namespace std;

int main() {
    if (5 > 2) {
	    cout << "Five is greater than two!" << endl;
    }
    return 0;
}
Python

Python style & indentation

if 5 > 2:
        print("Five is greater than two!")
  
if 5 > 2:
	print("Five is greater than two!")
 
if 5 > 2:
  print("Five is greater than two!")
  
if 5 > 2:
    print("Five is greater than two!")
 
Python

Case style

Python

Case style

Python

Comments

# This is a comment
print("OpenSource ❤️") # This is another comment
"""
Multiple comments in
multiple lines
"""
print("Linux is an excellent operative system")
Python

Python variables and types

x = 4
print(type(x))

x = "Sally"
print(type(x))
<class 'int'>
<class 'str'>
x = str(3)    # x will be '3'
y = int(3)    # y will be 3
z = float(3)  # z will be 3.0
Python

Ducktyping

Python

Many Values to Multiple Variables

x, y, z = "Orange", "Banana", "Cherry"

print(x) # Orange
print(y) # Banana
print(z) # Cherry

Unpack a Collection

fruits = ["apple", "banana", "cherry"]
x, y, z = fruits
print(x) # apple
print(y) # banana
print(z) # cherry
Python

Scope and functions

x = "awesome"

def myfunc() -> None:
  print("Python is " + x)

myfunc()
x = "awesome"

def myfunc() -> None:
  x = "fantastic"
  print("Python is " + x)

myfunc()

print("Python is " + x)
# Python is awesome
Python

The global keyword

def myfunc() -> None:
  global x
  x = "fantastic"

myfunc()

print("Open source is " + x)
# Open source is fantastic
x = "awesome"

def myfunc() -> None:
  global x
  x = "fantastic"

myfunc()

print("Python is " + x) # Python is fantastic
Python

Python types

Text Type:

str

Numeric Types:

int, float, complex

Sequence Types:

list, tuple, range

Mapping Type:

dict

Set Types:

set, frozenset

Boolean Type:

bool

Binary Types:

bytes, bytearray, memoryview

Python
x = "Hello World" str
x = 20 int
x = 20.5 float
x = 1j complex
x = ["apple", "banana", "cherry"] list
x = ("apple", "banana", "cherry") tuple
x = range(6) range
x = {"name" : "John", "age" : 36} dict
x = {"apple", "banana", "cherry"} set
x = frozenset({"apple", "banana", "cherry"}) frozenset
x = True bool
x = b"Hello" bytes
x = bytearray(5) bytearray
x = memoryview(bytes(5)) memoryview
Python

Strings are arrays

a = "Hello, World!"
print(a[1]) # e
for x in "mock":
  print(x)

# m
# o
# c
# k
a = "Hello, World!"
print(len(a))
txt = "freedom"
print("free" in txt)
txt = "freedom"
if "free" in txt:
  print('there is!')

if "free" not in txt:
  print("there isn't!")

Python

Slicing / Substring

b = "Hello, World!"
print(b[2:5]) # llo

print(b[:5]) # Hello

print(b[2:]) # llo, World!

print(b[-5:-2]) # orl
b[:]
Python

Operators

x in y     # x is in the object y
x not in y

x is y     # id(x) == id(y)
x is not y

x and y    # x && y
x or y     # x || y
not(x < 5) # !(x < 5)

id magics

>>> x = 10
>>> y = 10
>>> id(x)
11760968
>>> id(y)
11760968
>>> a = 300
>>> b = 300
>>> id(a)
140601663305712
>>> id(b)
140601663306832
Python

Range & short loop

thislist = ["apple", "linux", "microsoft"]
[print(x) for x in thislist]
thislist = ["apple", "linux", "microsoft"]
for i in range(len(thislist)):
  print(thislist[i])
Python

Short loop

fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
newlist = []

for x in fruits:
  if "a" in x:
    newlist.append(x)

print(newlist)
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]

newlist = [x for x in fruits if "a" in x]

print(newlist)
newlist = [expression for item in iterable if condition == True]
Python

ternary operator

'identical' if x == y else 'not identical'
x == y ? 'identical' : 'not identical'
Python

Functions and types

def sum(a, b):
  return a + b
def sum(a: int, b: int) -> int:
  return a + b
from typing import Union

def sum(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
  return a + b
Python

Functions and multiple types

from typing import Union
from typing import Tuple

def sum(a: Union[int, float], b: Union[int, float]) -> Tuple[int, float]:
  return a + b, b + a

x, y = sum(a, b)
from typing import Union
from typing import Tuple

def sum(a: Union[int, float], b: Union[int, float]) -> Tuple[Union[int, float], Union[int, float]]:
  return a + b, b + a

x, y = sum(a, b)

print(sum(3,4))
print(sum(3.0,4.0))
print(sum(3,4.0))
print(sum(3.0,4))
from typing import Union
from typing import Tuple

def sum(a: int, b: int) -> Tuple[int, int]:
  return a + b, b + a

x, y = sum(a, b)
Python

List and types

def my_magic_func(arr: list) -> list:
  # my magic code
  return arr
from typing import List

def my_magic_func(arr: List[str]) -> List[str]:
  # my magic code
  return arr
Python

Are types necessary?

https://www.reddit.com/r/Python/comments/5nb0si/why_optional_type_hinting_in_python_is_not_that/

Does the Python interpreter check types?

def sum(a: int, b: int) -> int:
  return a + b
sum(3.0, 3) # 6.0
type(sum(3.0, 3)) # float
Python

try - except

try:
  print(x)
except:
  print("An exception occurred")
print(x)
try:
  print(x)
except NameError:
  print("Variable x is not defined")
except:
  print("Something else went wrong")
  
Python

for - else

for x in range(6):
  print(x)
else:
  print("Finally finished!")

Print all numbers from 0 to 5, and print a message when the loop has ended:

for x in range(6):
  if x == 3: break
  print(x)
else:
  print("Finally finished!")

If the loop breaks, the else block is not executed.

Python

Classes & Objects

class MyClass:
  x = 5
  
p1 = MyClass()
print(p1.x)
class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age

  def myfunc(self):
    print("Hello my name is " + self.name)

p1 = Person("Helias", 26)
p1.myfunc()
Python

Classes & Objects

class Student(Person):
  def __init__(self, fname, age, year):
    super().__init__(fname, age)
    self.graduationyear = year

x = Student("Lorenzo", 23, 2021)
Python

private attributes and methods

class P:
   def __init__(self, name, alias):
      self.name = name       # public
      self.__alias = alias   # private
 
x = P(name='Alessio', alias='Admin di Spotted')
x.alias
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: P instance has no attribute 'alias'
x._P__alias
'Admin di Spotted'
Python

pip - the pyhton package manager

$ pip install python-telegram-bot

$ pip install -r requirements.txt

$ pip install -r requirements_dev.txt

Python

virtual environment

$ pip install virtualenv

$ virtualenv myenv

$ source myenv/bin/activate

$ python --version

Python 3.9.10

$ deactivate

$ python --version

Python 2.7

Unit test

Unit test

Software testing

Unit test
def sum(a: int, b: int) -> int:
  return a + b
def test_sum() -> None:
  assert sum(3,5) == 8
  assert type(sum(3,5)) is int
Unit test
Unit test
# example.py
from random import random 

def a() -> bool:
  return random() > 0.5

def b() -> int:
  is_a = a()
  if is_a:
    ...
    return 10
  else:
    ...
    return 20
 
Unit test

$ pip install pytest

Unit test

$ pip install pytest-mock

Unit test

Mock

def a() -> bool:
  return random() > 0.5
from pytest_mock import MockerFixture
import example

def test_a_1(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.4
    mocker.patch.object(example, "random", return_value=mock_random_return)

    # act
    res = example.a() 

    # assert
    assert res is False
Unit test

Spy

def a() -> bool:
  return random() > 0.5
from pytest_mock import MockerFixture
import example

def test_a_1(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.4
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # act
    res = example.a() 

    # assert
    assert res is False
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return
Unit test

all tests "a" cases

from pytest_mock import MockerFixture
import example

def test_a_1(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.4
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # act
    res = example.a() 

    # assert
    assert res is False
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return

def test_a_2(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.6
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # act
    res = example.a() 

    # assert
    assert res is True
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return
Unit test

test "b" case 1

def b() -> int:
  is_a = a()
  if is_a:
    ...
    return 10
  else:
    ...
    return 20
 
def test_b_1(mocker: MockerFixture) -> None:
    # arrange
    mock_a_return = True
    mocker.patch(__name__ + '.example.a', return_value=mock_a_return)
    spy = mocker.spy(example, "a")

    # act
    res = example.b()

    # assert
    assert res == 10
    assert spy.call_count == 1
    assert spy.spy_return == mock_a_return
Unit test
from pytest_mock import MockerFixture
import example

def test_a_1(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.4
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # act
    res = example.a() 

    # assert
    assert res is False
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return

def test_a_2(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.6
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # act
    res = example.a() 

    # assert
    assert res is True
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return

def test_b_1(mocker: MockerFixture) -> None:
    # arrange
    mock_a_return = True
    mocker.patch(__name__ + '.example.a', return_value=mock_a_return)
    spy = mocker.spy(example, "a")

    # act
    res = example.b()

    # assert
    assert res == 10
    assert spy.call_count == 1
    assert spy.spy_return == mock_a_return

def test_b_2(mocker: MockerFixture) -> None:
    # arrange
    mock_a_return = False
    mocker.patch(__name__ + '.example.a', return_value=mock_a_return)
    spy = mocker.spy(example, "a")

    # act
    res = example.b()

    # assert
    assert res == 20
    assert spy.call_count == 1
    assert spy.spy_return == mock_a_return

Unit test
import pytest

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass
from typing import List
import pytest
from pytest_mock import MockerFixture
import example

tests: List[dict] = [
    { "mock_value": 0.4,   "mock_function": "random", "spy_count": 1, "res": example.a, "expected_res": False  },
    { "mock_value": 0.6,   "mock_function": "random", "spy_count": 1, "res": example.a, "expected_res": True  },
    { "mock_value": True,  "mock_function": "a",      "spy_count": 1, "res": example.b, "expected_res": 10  },
    { "mock_value": False, "mock_function": "a",      "spy_count": 1, "res": example.b, "expected_res": 20  },
]

@pytest.mark.parametrize("test", tests)
def test_generic(mocker: MockerFixture, test: dict) -> None:
    # arrange
    mocker.patch.object(example, test["mock_function"], return_value=test["mock_value"])
    spy = mocker.spy(example, test["mock_function"])

    # act
    res = test["res"]()

    # assert
    assert res is test["expected_res"]
    assert spy.call_count == test["spy_count"]
    assert spy.spy_return == test["mock_value"]

test parametrization

x=0 => y=2, y=3
x=1 => y=2, y=3
4 test cases
Unit test

Stub

def test_stub(mocker):
    def foo(on_something):
        on_something('foo', 'bar')

    stub = mocker.stub(name='on_something_stub')

    foo(stub)

    stub.assert_called_once_with('foo', 'bar')

The stub is a mock object that accepts any arguments and is useful to test callbacks.

Unit test

code coverage

Unit test

code coverage

$ pip install pytest-cov

$ pytest test_sample.py --cov=example --cov-report=html

Unit test

code coverage

Unit test

Unit Test

PROs

  • long-term maintenance
  • safer and easier refactoring
  • quality assurance
  • error detection

CONs

  • time-consuming
  • hard to write
  • Not all errors can be detected later different integration bugs may appear.
Unit test
Unit test

Unit, integration & end-to-end test

Unit test

Integration tests

Unit test

End-to-end tests

Unit test

End-to-end tests

Unit test

Pipeline & software testing

Unit test

Design Pattern

  • Che cos'è un Design Pattern?
  • Quanto sono importanti i Design Pattern?
Unit test

Pattern: 'mbare pattern

Unit test

Pattern: Arrange Act Assert (AAA)

Unit test

Pattern: Arrange Act Assert (AAA)

from pytest_mock import MockerFixture
import example

def test_a_1(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.4
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # act
    res = example.a() 

    # assert
    assert res is False
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return
Unit test

Pattern: Arrange Assert Act Assert (AAAA)

from pytest_mock import MockerFixture
import example

def test_a_1(mocker: MockerFixture) -> None:
    # arrange
    mock_random_return = 0.4
    mocker.patch.object(example, "random", return_value=mock_random_return)
    spy = mocker.spy(example, "random")

    # assert
    ... # verify the initial conditions

    # act
    res = example.a() 

    # assert
    assert res is False
    assert spy.call_count == 1
    assert spy.spy_return == mock_random_return
Unit test

Pattern: Test Driven Development (TDD)

Unit test

Pattern: Behavior Driven Development (BDD)

Unit test

Pattern: Behavior Driven Development (BDD)

Unit test

Pattern: Page Object

Unit test

Pattern: Page Object

CI/CD

Continuous

Integration

Continuous

Delivery

# CI/CD

Pipeline

Pipeline -> CI / CD

# CI/CD

Pipeline -> CI / CD

# CI/CD

Pipeline tools

# CI/CD

Github Action

# CI/CD

2018

Hello World

# CI/CD
name: Hello-World

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  hello-world-job:

    runs-on: ubuntu-latest

    steps:
      - name: Hello World
        run: echo 'Hello World'

Hello World (C++)

# CI/CD
name: build-hello-world

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-hello-world:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: install g++
        run: sudo apt install -y g++

      - name: check build
        run: |
          g++ hello_world.cpp -o hello_world
          ./hello_world

Release

# CI/CD
name: release-hello-world

on:
  workflow_dispatch:

jobs:
  build-hello-world:
    permissions: write-all
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: compile and run
        run: g++ hello_world.cpp -o hello_world_linux

      - name: Create Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: >-
          gh release create ${{ github.ref_name }}
          "hello_world_linux"
          --generate-notes
          --title "Version ${{ github.ref_name }}"

Release - cross-platform

# CI/CD
name: release-hello-world

on:
  workflow_dispatch:

jobs:
  create-release:
    permissions: write-all
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Create Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: gh release create ${{ github.ref_name }} --generate-notes --title "Version ${{ github.ref_name }}"

  build-hello-world:
    needs: create-release
    permissions: write-all

    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            file_name: hello_world_linux
          - os: macos-latest
            file_name: hello_world_mac
          - os: windows-latest
            file_name: hello_world_windows.exe

    runs-on: ${{ matrix.os }}
    name: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4
      - name: compile
        run: g++ hello_world.cpp -o ${{ matrix.file_name }}

      - name: Update Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: gh release upload ${{ github.ref_name }} "${{ matrix.file_name}}"

pipeline checks

# CI/CD
name: build-hello-world

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-hello-world:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: install g++
        run: sudo apt install -y g++

      - name: check build
        run: |
          g++ hello_world.cpp -o hello_world
          ./hello_world
#include <iostream>
using namespace std;

int main() {

  cout << "Hello World" << endl;

  return 0;
}
#include <iostream>
using namespace std;

int main() {
  int x;
  cout << x << endl;

  cout << "Hello World" << endl;

  return 0;
}

pipeline checks

# CI/CD
#include <iostream>
using namespace std;

int main() {
  int* p = nullptr;

  cout << *p << endl;

  cout << "Hello World" << endl;

  return 0;
}

Linter cppcheck

# CI/CD
name: build-hello-world

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-hello-world:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: install g++
        run: sudo apt install -y g++ cppcheck

      - name: run cppcheck
        run: |
          cppcheck hello_world.cpp --output-file=report.txt
          if [ -s report.txt ]; then # if file is not empty
            cat report.txt
            exit 1 # let github action fails
          fi

      - name: check build
        run: |
          g++ hello_world.cpp -o hello_world
          ./hello_world

Pylint & Pytest

# CI/CD
name: CI

on:
  push:
    branches: [main]
    paths-ignore:
      - "README.md"
      - "docs/**"
  pull_request:
    branches: [main]
    paths-ignore:
      - "README.md"
      - "docs/**"

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Set up Python 3.10.0
        uses: actions/setup-python@v2
        with:
          python-version: 3.10.0

      - name: Install dependencies for requirements and testing
        run: |
          python -m pip install --upgrade pip
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi

      - name: Lint with pylint
        run: pylint src

      - name: Test with pytest
        run: pytest --cov src tests/ --cov-fail-under=75

Reusable workflows

# CI/CD
name: Create and publish a Docker image

on:
  workflow_call:
    inputs:
      repo_ref: # "author/repository_name" or ${{ github.repository }}
        required: true
        type: string

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ inputs.repo_ref }}

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Log in to the Container registry
        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Reusable workflows

# CI/CD
name: Create and publish a Docker image
on:
  push:
    branches:
      - 'main'

jobs:
  build:
    uses: unict-dmi/reusable-workflows/.github/workflows/docker.yml@main
    with:
      repo_ref: ${{ github.repository }}

Secrets token

# CI/CD
name: Telegram-Secret-Token

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  hello-world-job:

    runs-on: ubuntu-latest

    steps:
      - name: Telegram Notify
        run: >-
          curl -s --data-urlencode "text=Hello World ✅"
          "https://api.telegram.org/bot${{ secrets.MY_SECRET_TOKEN }}/sendMessage?chat_id=1044003630" > /dev/null

Secrets token

# CI/CD

Github Pages

# CI/CD
# CI/CD
#include <iostream>
using namespace std;

int SafeDivide(int a, int b) {
    cout << "a: " << a << endl;
    cout << "b: " << b << endl;

    if (b == 0) {
        return 0; // Return 0 if division by zero
    }

    return a / b;
}


int main() {
    int a, b;

    cout << "Enter a: ";
    cin >> a;

    cout << "Enter b: ";
    cin >> b;

    cout << SafeDivide(a, b) << endl;

    return 0;
}

example.cpp

Unit Test example

# CI/CD
#include <gtest/gtest.h>
#include "math_utils.h"

TEST(MathUtilsTest, HandlesZeroDivision) {
    EXPECT_EQ(SafeDivide(10, 0), 0);
}

TEST(MathUtilsTest, HandlesNormalDivision) {
    EXPECT_EQ(SafeDivide(10, 2), 5);
}
#include "math_utils.h"

int SafeDivide(int a, int b) {
    if (b == 0) {
        return 0; // Return 0 if division by zero
    }

    return a / b;
}
#pragma once

int SafeDivide(int a, int b);

src/math_utils.cpp

src/math_utils.h

test / math_utils_test.cpp

Fuzz test example

# CI/CD
#include <cstdint>
#include <cstddef>
#include "math_utils.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
    if (size < 8) return 0;

    int a = *(reinterpret_cast<const int*>(data));
    int b = *(reinterpret_cast<const int*>(data + 4));

    SafeDivide(a, b);

    return 0;
}

Fuzz test example

# CI/CD

$ ./fuzz_math_utils

==1453512==ERROR: AddressSanitizer: FPE on unknown address

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE (build/fuzz_math_utils+0x1418d4) (BuildId: b39d1e6b5479d39aaee3a49ef227df07d8e95b48) in SafeDivide(int, int)
==1453512==ABORTING
MS: 5 CrossOver-InsertRepeatedBytes-ChangeByte-ShuffleBytes-ChangeBinInt-; base unit: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
0x0,0x0,0x0,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xca,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf

crash-123456789......

Fuzz test example

# CI/CD

$ hexdump crash-123456789......

0000000 0000 8000 ffff ffff ffff ffff ffff ffff
0000010 ffff ffff ffff ffff ffff ffff ffff ffff
0000020 ffff ffff ffff ffff ffff ffff ffff 00ff
0000030 0000 a300                              
0000034

HEX signed -> Decimal

80000000 = -2147483648
ffffffff = -1

$ hexdump -v -e '"%d, "' -e '8/1 "0x%02x, " "\n"' ./crash-123456789

-2147483648, -1, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, [....]

Fuzz test example

# CI/CD
name: fuzz-test-example

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  fuzz-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: install g++
        run: sudo apt install -y g++

      - name: run build, test and fuzz test
        run: |
          mkdir build
          cd build
          cmake .. -DCMAKE_CXX_COMPILER=clang++
          cmake --build . -- -j$(nproc)
          ./math_utils_test
          timeout 30 ./fuzz_math_utils || echo "Fuzz test crashed or timed out"
          if ls crash-* 1> /dev/null 2>&1; then
            hexdump -v -e '"%d, "' -e '8/1 "0x%02x, " "\n"' ./crash-*
            exit 1 # let github action fails
          fi
          echo "Build, test and fuzz test completed successfully"

Fuzz test example - binary

# CI/CD

BIN                                 DEC  MAX (bit length)

  1   = 2^0 * 1                     = 1    2^1-1 = 1

 10   = 2^1 * 1 + 2^0 * 0           = 2

100   = 2^2 * 1 + 2^1 * 0 + 2^0 * 0 = 4

 11   = 2^1 * 1 + 2^0 * 1           = 3    2^2-1 = 3

111   = 2^2 * 1 + 2^1 * 1 + 2^0 * 1 = 7    2^3-1 = 7

1000  = 2^3 * 1 ...                 = 8

1111  = 2^3 * 1 ...              = 15   2^4-1 = 15

Fuzz test example - binary signed

# CI/CD

4 bit

0000 = 0

0001 = 1

...

0111 = 7

1000 = -8

32 bit (int32)

00..00 = 0

00..01 = 1

...

01..11 =  2147483647

10..00 = -2147483648

-2147483648 / -1 = ????

= 2147483648

-8 / -1 = ????

= 8

# CI/CD
#include <iostream>
using namespace std;

int SafeDivide(int a, int b) {
    cout << "a: " << a << endl;
    cout << "b: " << b << endl;

    if (b == 0) {
        return 0; // Return 0 if division by zero
    }

    if (a == -2147483648 && b < 0) {
        return 2147483647; // Handle overflow case
    }

    return a / b;
}


int main() {
    int a, b;

    cout << "Enter a: ";
    cin >> a;

    cout << "Enter b: ";
    cin >> b;

    cout << SafeDivide(a, b) << endl;

    return 0;
}

example_workaround.cpp

SOLID

SOLID principles

Code quality and best practices

Strongly inspired by:

https://towardsdatascience.com/solid-coding-in-python-1281392a6a94
https://www.pythontutorial.net/python-oop/python-liskov-substitution-principle/

https://testdriven.io/blog/clean-code-python/
https://gist.github.com/dmmeteo/f630fa04c7a79d3c132b9e9e5d037bfd

SOLID

SOLID   -   The Single-responsibility principle (SRP)

“A class should have one, and only one, reason to exist”

import numpy as np

def math_operations(list_):
    # Compute Average
    print(f"the mean is {np.mean(list_)}")

    # Compute Max
    print(f"the max is {np.max(list_)}") 

math_operations(list_ = [1,2,3,4,5])
# the mean is 3.0
# the max is 5

❌ Bad code ❌

def get_mean(list_: list[int]) -> None:
    '''Compute Mean'''
    print(f"the mean is {np.mean(list_)}")

def get_max(list_: list[int]) -> None:
    '''Compute Max'''
    print(f"the max is {np.max(list_)}")

def main(list_: list[int]) -> None: 
    # Compute Average
    get_mean(list_)
    # Compute Max
    get_max(list_)

main([1,2,3,4,5])
# the mean is 3.0
# the max is 5

✅ Good code ✅

SOLID   -   The Open–closed principle (OCP)

“Software entities … should be open for extension but closed for modification”

class Animal:
    def __init__(self, name: str):
        self.name = name
    
    def get_name(self) -> str:
        pass

animals = [
    Animal('lion'),
    Animal('mouse')
]

def animal_sound(animals: list):
    for animal in animals:
        if animal.name == 'lion':
            print('roar')

        elif animal.name == 'mouse':
            print('squeak')

animal_sound(animals)

❌ Bad code ❌

class Animal:
    def __init__(self, name: str):
        self.name = name
    
    def get_name(self) -> str:
        pass

    def make_sound(self) -> None:
        pass


class Lion(Animal):
    def make_sound(self) -> str:
        return 'roar'

class Mouse(Animal):
    def make_sound(self) -> str:
        return 'squeak'

def animal_sound(animals: list) -> None:
    for animal in animals:
        print(animal.make_sound())

animal_sound(animals)

✅ Good code ✅

SOLID

SOLID   -   The Liskov substitution principle (LSP)

“Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it”

from abc import ABC, abstractmethod


class Notification(ABC):
    @abstractmethod
    def notify(self, message, email):
        pass


class Email(Notification):
    def notify(self, message, email):
        print(f'Send {message} to {email}')


class SMS(Notification):
    def notify(self, message, phone):
        print(f'Send {message} to {phone}')


if __name__ == '__main__':
    notification = SMS()
    notification.notify('Hello', 'john@test.com')

❌ Bad code ❌

from abc import ABC, abstractmethod


class Notification(ABC):
    @abstractmethod
    def notify(self, message: str) -> None:
        pass

class Email(Notification):
    def __init__(self, email: str):
        self.email = email

    def notify(self, message: str) -> None:
        print(f'Send "{message}" to {self.email}')


class SMS(Notification):
    def __init__(self, phone: str):
        self.phone = phone

    def notify(self, message: str) -> None:
        print(f'Send "{message}" to {self.phone}')

if __name__ == '__main__':
    notification = SMS('+3912345678')
    notification.notify('Hello')

    notification = Email('stefano.borzi@phd.unict.it')
    notification.notify('Hello')

✅ Good code ✅

SOLID

SOLID   -   The Interface Segregation Principle (ISP)

“Many client-specific interfaces are better than one general-purpose interface”

from abc import ABC, abstractmethod

class Mammals(ABC):
    @abstractmethod
    def swim():
        print("Can Swim")

    @abstractmethod
    def walk():
        print("Can Walk")

class Human(Mammals):
    def swim():
        print("Humans can swim")

    def walk():
        print("Humans can walk")

class Whale(Mammals):
    def swim():
        print("Whales can swim")

Human.swim() # Humans can swim
Human.walk() # Humans can walk

Whale.swim() # Whales can swim
Whale.walk() # Can Walk

❌ Bad code ❌

✅ Good code ✅

from abc import ABC, abstractmethod

class Walker(ABC):
  @abstractmethod
  def walk() -> None:
    print("Can Walk") 

class Swimmer(ABC):
  @abstractmethod
  def swim() -> None:
    print("Can Swim") 

class Human(Walker, Swimmer):
  def walk() -> None:
    print("Humans can walk") 
  def swim() -> None:
    print("Humans can swim") 

class Whale(Swimmer):
  def swim() -> None:
    print("Whales can swim") 

if __name__ == "__main__":
  Human.walk() # Humans can walk
  Human.swim() # Humans can swim

  Whale.swim() # Whales can swim
  Whale.walk() # ❌❌❌❌❌
SOLID

SOLID   -   The Dependency Inversion Principle (DIP)

“Abstractions should not depend on details. Details should depend on abstraction. High-level modules should not depend on low-level modules. Both should depend on abstractions”

class FXConverter:

  def convert(self, from_curr, to_curr, amount):
    print(
      f'{amount} {from_curr} = {amount * 1.2} {to_curr}'
    )
    return amount * 1.2


class App:
  def start(self):
    converter = FXConverter()
    converter.convert('EUR', 'USD', 100)


if __name__ == '__main__':
  app = App()
  app.start()

❌ Bad code ❌

from abc import ABC


class CurrencyConverter(ABC):
  def convert(self, from_curr: str, to_curr: str, amount: float) -> None:
    pass

class FXConverter(CurrencyConverter):
  def convert(self, from_curr: str, to_curr: str, amount: float) -> float:
    print('Converting currency using FX API')
    print(
      f'{amount} {from_curr} = {amount * 1.2} {to_curr}'
    )
    return amount * 2

class App:
  def __init__(self, converter: CurrencyConverter):
    self.converter = converter

  def start(self) -> None:
    self.converter.convert('EUR', 'USD', 100)
 
if __name__ == '__main__':
  converter = FXConverter()
  app = App(converter)
  app.start()

✅ Good code ✅

SOLID

SOLID   -   The Dependency Inversion Principle (DIP)

“Abstractions should not depend on details. Details should depend on abstraction. High-level modules should not depend on low-level modules. Both should depend on abstractions”

❌ Bad code ❌

✅ Good code ✅

SOLID

DRY - Don't Repeat Yourself

Quality Code
text1 = 1
text2 = 2
text3 = 3
text4 = 4
text5 = 5

print(f"Number: {text1}")
print(f"Number: {text2}")
print(f"Number: {text3}")
print(f"Number: {text4}")
print(f"Number: {text5}")

❌ Bad code ❌

✅ Good code ✅

texts = [1, 2, 3, 4, 5]

for i in range(len(texts)):
    print(f"Number: {texts[i]}")

KISS - Keep It Simple Stupid

Quality Code
f = lambda x: 1 if x <= 1 else x * f(x - 1)

❌ Bad code ❌

✅ Good code ✅

def factorial(number: int) -> int:
    if number <= 1:
        return 1

    return number * factorial(number - 1)

SoC - Separation Of Concerns

Quality Code
# calculator.py
class Calculator():
  def sum(num1: int | float, num2: int | float) -> int | float:
    return num1 + num2

  def convert_to_float(num: int) -> float:
    return float(num)

❌ Bad code ❌

✅ Good code ✅

# calculator.py
class Calculator():
  def sum(num1: int | float, num2: int | float) -> int | float:
    return num1 + num2

# helpers.py
def convert_to_float(num: int) -> float:
  return float(num)

Clean Code

Quality Code
# ❌ This is bad ❌
c = 5
d = 12

# ✅ This is good ✅
city_counter = 5
elapsed_time_in_days = 12

Use descriptive/intention-revealing names

Use pronounceable names


from datetime import datetime


# ❌ This is bad ❌
genyyyymmddhhmmss = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')

# ✅ This is good ✅
generation_datetime = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')

Avoid using ambiguous abbreviations

# ❌ This is bad ❌
fna = 'Bob'
cre_tmstp = 1621535852

# ✅ This is good ✅
first_name = 'Bob'
creation_timestamp = 1621535852

Always use the same vocabulary

# ❌ This is bad ❌
client_first_name = 'Bob'
customer_last_name = 'Smith'

# ✅ This is good ✅
client_first_name = 'Bob'
client_last_name = 'Smith'

Clean Code

Quality Code
import random

# ❌ This is bad ❌
def roll():
    return random.randint(0, 36)  # what is 36 supposed to represent?

# ✅ This is good ✅
ROULETTE_POCKET_COUNT = 36

def roll() -> float:
    return random.randint(0, ROULETTE_POCKET_COUNT)

Don't use "magic numbers"

# ❌ This is bad ❌
class Person:
    def __init__(self, person_first_name, person_last_name, person_age):
        self.person_first_name = person_first_name
        self.person_last_name = person_last_name
        self.person_age = person_age


# ✅ This is good ✅
class Person:
    def __init__(self, first_name: str, last_name: str, age: int):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

Don't add redundant context

Clean Code

Quality Code
numbers = [1, 2, 3, 4, 5]

# This variable stores the average of list of numbers.
average = sum(numbers) / len(numbers)
print(average)

Don't add noise comments

Readable code doesn't need comments

Clean Code

Quality Code
# ❌ This is bad ❌
def get_name(): pass
def fetch_age(): pass

# ✅ This is good ✅
def get_name() -> None: pass
def get_age() -> None: pass

Do not use different words for the same concept

Functions should only perform a single task

# ❌ This is bad ❌
def fetch_and_display_personnel():
    data = # ...

    for person in data:
        print(person)


# ✅ This is good ✅
def fetch_personnel():
    return # ...

def display_personnel(data: str) -> None:
    for person in data:
        print(person)

Clean Code

Quality Code
# ❌ This is bad ❌
def render_blog_post(title, author, created_timestamp, updated_timestamp, content):
    # ...

render_blog_post("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")


# ✅ This is good ✅
class BlogPost:
    def __init__(self, title, author, created_timestamp, updated_timestamp, content):
        self.title = title
        self.author = author
        self.created_timestamp = created_timestamp
        self.updated_timestamp = updated_timestamp
        self.content = content

blog_post1 = BlogPost("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")

def render_blog_post(blog_post):
    # ...

render_blog_post(blog_post1)

Keep your arguments at a minimum

Clean Code

Quality Code
# ❌ This is bad ❌
text = "This is a cool blog post."
def transform(text, uppercase):
    if uppercase:
        return text.upper()
    else:
        return text.lower()

uppercase_text = transform(text, True)
lowercase_text = transform(text, False)


# ✅ This is good ✅
text = "This is a cool blog post."
def uppercase(text: str) -> str:
    return text.upper()

def lowercase(text: str) -> str:
    return text.lower()

uppercase_text = uppercase(text)
lowercase_text = lowercase(text)

Don't use flags in functions

Misc

Debugging, utilities,

tools

# Debugging

Debugging

# Debugging

Debugging

def get_mean(arr: list[int]) -> None:
    avg = sum(arr) / len(arr)
    print(avg)

x = [1,2,3,4]
get_mean(x)

print('debugging 1')

for x in range(10):
    print('test2: ', x)

    print(x)

print('test3')
# Debugging

Debugging - VScode + Python

# Debugging

Debugging (JavaScript)

function get_mean(arr) {
    avg = arr.reduce((acc, tot) => acc+tot) / arr.length
    console.log(avg)
}

const x = [1,2,3,4]
get_mean(x)

console.log('debugging 1')

for (let x = 0; x < 10; x++) {
    console.log(x)
}

console.log('test2')

console.log('test3')
# Debugging

Debugging (JavaScript)

# Tools

Tools: auto-formatter

# Tools

Tools: auto-formatter (Python)

YAPF

$ sudo apt install black isort

VScode settings

  "[python]": {
    "editor.defaultFormatter": "ms-python.black-formatter",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": "explicit"
    }
  },
  "isort.args": ["--profile", "black"]
# Tools

VScode plugins

# Tools

Tools: auto-formatter (C++)

clang-format

# Tools

Tools: auto-formatter (Javascript)

Quality Development

By Stefano Borzì

Quality Development

  • 201