diff --git a/pkgs_envsetup.py b/pkgs_envsetup.py
index 4eef2a24c05e4358f8c38dc7c6c0678c2012e059..be816fb0dbd15735e7da7437ee06189b11fb4cec 100755
--- a/pkgs_envsetup.py
+++ b/pkgs_envsetup.py
@@ -4,6 +4,7 @@ from subprocess import run, DEVNULL, PIPE, STDOUT
 
 PACKAGES = [
     "bsd-mailx",  # for "mail" command used in tester
+    "python3-apt",  # for: test_apt
     "python3-defusedxml",  # for: test_wowza
     "python3-dnspython",  # for: test_caches
     "python3-openssl",  # for: test_ssl
diff --git a/utils.py b/utils/__init__.py
similarity index 100%
rename from utils.py
rename to utils/__init__.py
diff --git a/utils/apt.py b/utils/apt.py
new file mode 100644
index 0000000000000000000000000000000000000000..d434c38b6132c3c8c02d8cdafc212efde475f04a
--- /dev/null
+++ b/utils/apt.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+
+"""
+A wrapper of apt module that is actually usable.
+"""
+
+import utils as u
+
+try:
+    import apt
+    import apt_pkg
+except ModuleNotFoundError:
+    u.warning("apt python module not found")
+    exit(2)
+
+
+class Apt:
+    cache: apt.cache.Cache
+    packages: list
+    installed_packages: list
+    removable_packages: list
+    upgradable_packages: list
+
+    def __init__(self, update: bool = False):
+        self.cache = self.get_cache(update)
+        self.packages = self.get_packages()
+        self.installed_packages = self.get_installed_packages()
+        self.removable_packages = self.get_removable_packages()
+        self.upgradable_packages = self.get_upgradable_packages()
+
+    def install(self, name: str) -> bool:
+        """Install a package with APT.
+
+        :param name: Package name
+        :type name: str
+        :return: Wether installation is successful or not
+        :rtype: bool
+        """
+
+        pkg = self.cache[name]
+        if not pkg.installed:
+            pkg.mark_install(auto_fix=False)
+        success = self.cache.commit()
+        self.reload()
+
+        return success
+
+    def remove(self, name: str) -> bool:
+        """Remove a package with APT.
+
+        :param name: Package name
+        :type name: str
+        :return: Wether uninstallation is successful or not
+        :rtype: bool
+        """
+
+        pkg = self.cache[name]
+        if pkg.installed:
+            pkg.mark_delete(auto_fix=False)
+        success = self.cache.commit()
+        self.reload()
+
+        return success
+
+    def reload(self):
+        """Reload object."""
+
+        self.cache.clear()
+        self.__init__()
+
+    def get_cache(self, update: bool = False) -> apt.cache.Cache:
+        """Get an eventually updated Cache object.
+
+        :param update: Wether to update cacheor not, default=False
+        :type update: bool
+        :return: An APT Cache object
+        :rtype: apt.cache.Cache
+        """
+
+        apt_cache = apt.cache.Cache()
+        if update:
+            apt_cache.update()
+            apt_cache.open()
+
+        return apt_cache
+
+    def get_packages(self) -> list:
+        """Get packages list.
+
+        :return: Packages list
+        :rtype: list
+        """
+
+        packages = list(self.cache)
+
+        return packages
+
+    def get_installed_packages(self) -> list:
+        """Get installed packages list.
+
+        :return: Installed packages list
+        :rtype: list
+        """
+
+        installed_packages = [p for p in self.packages if p.is_installed]
+
+        return installed_packages
+
+    def get_removable_packages(self) -> list:
+        """Get auto-removable packages list.
+
+        :return: Auto-removable packages list
+        :rtype: list
+        """
+
+        removable_packages = [p for p in self.installed_packages if p.is_auto_removable]
+
+        return removable_packages
+
+    def get_upgradable_packages(self) -> list:
+        """Get upgradable packages list.
+
+        :return: Upgradable packages list
+        :rtype: list
+        """
+
+        upgradable_packages = [p for p in self.installed_packages if p.is_upgradable]
+
+        return upgradable_packages
+