diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..83235cb61937a9f9fd26b8893d4ca589dcf5cbcb
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,10 @@
+test-job:
+  script:
+    - python3 -m venv .venv             # Create Python virtual environment
+    - source .venv/bin/activate         # Activate Python virtual environment
+    - pip3 install -r requirements.txt  # Install Python dependencies
+    - pip3 install pytest               # Install pytest
+    - pytest test/                      # Run tests
+    - pip3 install build                # Install build package
+    - python3 -m build                  # Build package
+    - pip3 install .                    # Install package
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8922d1190bad3bfaecad2779ebf487a5ec2596b9
--- /dev/null
+++ b/test/__init__.py
@@ -0,0 +1,3 @@
+"""
+Unit tests for the package `smart-home-testbed`.
+"""
diff --git a/test/devices/__init__.py b/test/devices/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/devices/cameras/__init__.py b/test/devices/cameras/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/devices/cameras/sample.png b/test/devices/cameras/sample.png
new file mode 100644
index 0000000000000000000000000000000000000000..0218b6e84d0d2d2605dfba62368436ddbe357ddb
Binary files /dev/null and b/test/devices/cameras/sample.png differ
diff --git a/test/devices/cameras/test_DLinkCamera.py b/test/devices/cameras/test_DLinkCamera.py
new file mode 100644
index 0000000000000000000000000000000000000000..c44f727124753c0286b5e715b214eaacb980eac1
--- /dev/null
+++ b/test/devices/cameras/test_DLinkCamera.py
@@ -0,0 +1,31 @@
+from smart_home_testbed import init_device, DLinkCamera
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.dlink.mydlinkunified"
+path_screenshot_stream = "sample.png"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the DLinkCamera constructor.
+    """
+    device = DLinkCamera(ipv4=ipv4, path_screenshot_stream=path_screenshot_stream)
+    assert isinstance(device, DLinkCamera)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a DLinkCamera object.
+    """
+    device = init_device("DLinkCamera", ipv4=ipv4, path_screenshot_stream=path_screenshot_stream)
+    assert isinstance(device, DLinkCamera)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/cameras/test_TapoCamera.py b/test/devices/cameras/test_TapoCamera.py
new file mode 100644
index 0000000000000000000000000000000000000000..53553e109431e1add596a87966f6adf44f59e9a8
--- /dev/null
+++ b/test/devices/cameras/test_TapoCamera.py
@@ -0,0 +1,31 @@
+from smart_home_testbed import init_device, TapoCamera
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.tplink.iot"
+path_screenshot_stream = "sample.png"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TapoCamera constructor.
+    """
+    device = TapoCamera(ipv4=ipv4, path_screenshot_stream=path_screenshot_stream)
+    assert isinstance(device, TapoCamera)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TapoCamera object.
+    """
+    device = init_device("TapoCamera", ipv4=ipv4, path_screenshot_stream=path_screenshot_stream)
+    assert isinstance(device, TapoCamera)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/cameras/test_TapoCameraSmartThings.py b/test/devices/cameras/test_TapoCameraSmartThings.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5c148b38a304ff7943b35b9b35af5cb13a0c1e1
--- /dev/null
+++ b/test/devices/cameras/test_TapoCameraSmartThings.py
@@ -0,0 +1,31 @@
+from smart_home_testbed import init_device, TapoCameraSmartThings
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.samsung.android.oneconnect"
+path_screenshot_stream = "sample.png"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TapoCameraSmartThings constructor.
+    """
+    device = TapoCameraSmartThings(ipv4=ipv4, path_screenshot_stream=path_screenshot_stream)
+    assert isinstance(device, TapoCameraSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TapoCameraSmartThings object.
+    """
+    device = init_device("TapoCameraSmartThings", ipv4=ipv4, path_screenshot_stream=path_screenshot_stream)
+    assert isinstance(device, TapoCameraSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/cameras/test_XiaomiCamera.py b/test/devices/cameras/test_XiaomiCamera.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c1f3d3d6502bb1ba070f43b04574cf3409ccb0d
--- /dev/null
+++ b/test/devices/cameras/test_XiaomiCamera.py
@@ -0,0 +1,34 @@
+from secrets import token_hex
+from smart_home_testbed import init_device, XiaomiCamera
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+token = token_hex(16)
+android_package = "com.xiaomi.smarthome"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the XiaomiCamera constructor.
+    """
+    device = XiaomiCamera(ipv4=ipv4, token=token)
+    assert isinstance(device, XiaomiCamera)
+    assert device.ip == ipv4
+    assert device.token == token
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a XiaomiCamera object.
+    """
+    device = init_device("XiaomiCamera", ipv4=ipv4, token=token)
+    assert isinstance(device, XiaomiCamera)
+    assert device.ip == ipv4
+    assert device.token == token
+    assert device.android_package == android_package
diff --git a/test/devices/lights/__init__.py b/test/devices/lights/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/devices/lights/sample.png b/test/devices/lights/sample.png
new file mode 100644
index 0000000000000000000000000000000000000000..0218b6e84d0d2d2605dfba62368436ddbe357ddb
Binary files /dev/null and b/test/devices/lights/sample.png differ
diff --git a/test/devices/lights/test_HueLight.py b/test/devices/lights/test_HueLight.py
new file mode 100644
index 0000000000000000000000000000000000000000..208add867592ff5fb0a0f464c99bd790ffbd1b4c
--- /dev/null
+++ b/test/devices/lights/test_HueLight.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, HueLight
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.philips.lighting.hue2"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the HueLight constructor.
+    """
+    device = HueLight(ipv4=ipv4)
+    assert isinstance(device, HueLight)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a HueLight object.
+    """
+    device = init_device("HueLight", ipv4=ipv4)
+    assert isinstance(device, HueLight)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/lights/test_HueLightEssentials.py b/test/devices/lights/test_HueLightEssentials.py
new file mode 100644
index 0000000000000000000000000000000000000000..76adeb1eb7fdf2d4c78f46388b39d529235e3a0d
--- /dev/null
+++ b/test/devices/lights/test_HueLightEssentials.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, HueLightSmartThings
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.samsung.android.oneconnect"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the HueLightSmartThings constructor.
+    """
+    device = HueLightSmartThings(ipv4=ipv4)
+    assert isinstance(device, HueLightSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a HueLightSmartThings object.
+    """
+    device = init_device("HueLightSmartThings", ipv4=ipv4)
+    assert isinstance(device, HueLightSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/lights/test_HueLightSmartThings.py b/test/devices/lights/test_HueLightSmartThings.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b93469ea7a0fc934dbfa05bc2a4a80e535176e4
--- /dev/null
+++ b/test/devices/lights/test_HueLightSmartThings.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, HueLightEssentials
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.superthomaslab.hueessentials"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the HueLightEssentials constructor.
+    """
+    device = HueLightEssentials(ipv4=ipv4)
+    assert isinstance(device, HueLightEssentials)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a HueLightEssentials object.
+    """
+    device = init_device("HueLightEssentials", ipv4=ipv4)
+    assert isinstance(device, HueLightEssentials)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/lights/test_TapoLight.py b/test/devices/lights/test_TapoLight.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf646dd3b2abcf5c6517a869190cb8382483fb5b
--- /dev/null
+++ b/test/devices/lights/test_TapoLight.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, TapoLight
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.tplink.iot"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TapoLight constructor.
+    """
+    device = TapoLight(ipv4=ipv4)
+    assert isinstance(device, TapoLight)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TapoLight object.
+    """
+    device = init_device("TapoLight", ipv4=ipv4)
+    assert isinstance(device, TapoLight)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/lights/test_TapoLightSmartThings.py b/test/devices/lights/test_TapoLightSmartThings.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d3c92739170daa396d850d2c3428eb1b1fa902d
--- /dev/null
+++ b/test/devices/lights/test_TapoLightSmartThings.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, TapoLightSmartThings
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.samsung.android.oneconnect"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TapoLightSmartThings constructor.
+    """
+    device = TapoLightSmartThings(ipv4=ipv4)
+    assert isinstance(device, TapoLightSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TapoLightSmartThings object.
+    """
+    device = init_device("TapoLightSmartThings", ipv4=ipv4)
+    assert isinstance(device, TapoLightSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/lights/test_TuyaLight.py b/test/devices/lights/test_TuyaLight.py
new file mode 100644
index 0000000000000000000000000000000000000000..64e53080190a65e6f71aa116a41511455ecb030e
--- /dev/null
+++ b/test/devices/lights/test_TuyaLight.py
@@ -0,0 +1,31 @@
+from smart_home_testbed import init_device, TuyaLight
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.tuya.smart"
+path_screenshot_off = "sample.png"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TuyaLight constructor.
+    """
+    device = TuyaLight(ipv4=ipv4, path_screenshot_off=path_screenshot_off)
+    assert isinstance(device, TuyaLight)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TuyaLight object.
+    """
+    device = init_device("TuyaLight", ipv4=ipv4, path_screenshot_off=path_screenshot_off)
+    assert isinstance(device, TuyaLight)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/__init__.py b/test/devices/plugs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/devices/plugs/test_SmartThingsOutlet.py b/test/devices/plugs/test_SmartThingsOutlet.py
new file mode 100644
index 0000000000000000000000000000000000000000..5dd099f78e769812f6dcafbe7ac814668d7df0c7
--- /dev/null
+++ b/test/devices/plugs/test_SmartThingsOutlet.py
@@ -0,0 +1,37 @@
+from uuid import uuid4
+from smart_home_testbed import init_device, SmartThingsOutlet
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+id = str(uuid4())
+token = "smart-things-token"
+android_package = "com.samsung.android.oneconnect"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the SmartThingsOutlet constructor.
+    """
+    device = SmartThingsOutlet(ipv4=ipv4, id=id, token=token)
+    assert isinstance(device, SmartThingsOutlet)
+    assert device.ipv4 == ipv4
+    assert device.id == id
+    assert device.token == token
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a SmartThingsOutlet object.
+    """
+    device = init_device("SmartThingsOutlet", ipv4=ipv4, id=id, token=token)
+    assert isinstance(device, SmartThingsOutlet)
+    assert device.ipv4 == ipv4
+    assert device.id == id
+    assert device.token == token
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/test_TapoPlug.py b/test/devices/plugs/test_TapoPlug.py
new file mode 100644
index 0000000000000000000000000000000000000000..7db82fbd90dde919027e153e2aeb7ef58892635a
--- /dev/null
+++ b/test/devices/plugs/test_TapoPlug.py
@@ -0,0 +1,32 @@
+from smart_home_testbed import init_device, TapoPlug
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+username = "user"
+password = "password"
+android_package = "com.tplink.iot"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TapoPlug constructor.
+    """
+    device = TapoPlug(ipv4=ipv4, username=username, password=password)
+    assert isinstance(device, TapoPlug)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TapoPlug object.
+    """
+    device = init_device("TapoPlug", ipv4=ipv4, username=username, password=password)
+    assert isinstance(device, TapoPlug)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/test_TapoPlugSmartThings.py b/test/devices/plugs/test_TapoPlugSmartThings.py
new file mode 100644
index 0000000000000000000000000000000000000000..742eddfed63fd38ddee770d872920d13587ba462
--- /dev/null
+++ b/test/devices/plugs/test_TapoPlugSmartThings.py
@@ -0,0 +1,32 @@
+from smart_home_testbed import init_device, TapoPlugSmartThings
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+username = "user"
+password = "password"
+android_package = "com.samsung.android.oneconnect"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TapoPlugSmartThings constructor.
+    """
+    device = TapoPlugSmartThings(ipv4=ipv4, username=username, password=password)
+    assert isinstance(device, TapoPlugSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TapoPlugSmartThings object.
+    """
+    device = init_device("TapoPlugSmartThings", ipv4=ipv4, username=username, password=password)
+    assert isinstance(device, TapoPlugSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/test_TpLinkPlug.py b/test/devices/plugs/test_TpLinkPlug.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad25fdd9a62d5caf483644e6cf16edcbfbe7aa68
--- /dev/null
+++ b/test/devices/plugs/test_TpLinkPlug.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, TpLinkPlug
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.tplink.kasa_android"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TpLinkPlug constructor.
+    """
+    device = TpLinkPlug(ipv4=ipv4)
+    assert isinstance(device, TpLinkPlug)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TpLinkPlug object.
+    """
+    device = init_device("TpLinkPlug", ipv4=ipv4)
+    assert isinstance(device, TpLinkPlug)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/test_TpLinkPlugSmartThings.py b/test/devices/plugs/test_TpLinkPlugSmartThings.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e6d8c490210680235ddd81c7945568197d0c816
--- /dev/null
+++ b/test/devices/plugs/test_TpLinkPlugSmartThings.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, TpLinkPlugSmartThings
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.samsung.android.oneconnect"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TpLinkPlugSmartThings constructor.
+    """
+    device = TpLinkPlugSmartThings(ipv4=ipv4)
+    assert isinstance(device, TpLinkPlugSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TpLinkPlugSmartThings object.
+    """
+    device = init_device("TpLinkPlugSmartThings", ipv4=ipv4)
+    assert isinstance(device, TpLinkPlugSmartThings)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/test_TpLinkPlugTapo.py b/test/devices/plugs/test_TpLinkPlugTapo.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ce513e7c92d7a18441d7c5eb351a0f8c1f305c1
--- /dev/null
+++ b/test/devices/plugs/test_TpLinkPlugTapo.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, TpLinkPlugTapo
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.tplink.iot"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TpLinkPlugTapo constructor.
+    """
+    device = TpLinkPlugTapo(ipv4=ipv4)
+    assert isinstance(device, TpLinkPlugTapo)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TpLinkPlugTapo object.
+    """
+    device = init_device("TpLinkPlugTapo", ipv4=ipv4)
+    assert isinstance(device, TpLinkPlugTapo)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
diff --git a/test/devices/plugs/test_TuyaPlug.py b/test/devices/plugs/test_TuyaPlug.py
new file mode 100644
index 0000000000000000000000000000000000000000..13b2b83f57e2190b2139416e02be03bcf82a7aed
--- /dev/null
+++ b/test/devices/plugs/test_TuyaPlug.py
@@ -0,0 +1,30 @@
+from smart_home_testbed import init_device, TuyaPlug
+
+
+### VARIABLES ###
+
+ipv4 = "192.168.1.2"
+android_package = "com.tuya.smart"
+
+
+### TEST FUNCTIONS ###
+
+def test_constructor() -> None:
+    """
+    Test the TuyaPlug constructor.
+    """
+    device = TuyaPlug(ipv4=ipv4)
+    assert isinstance(device, TuyaPlug)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package
+
+
+def test_init_device() -> None:
+    """
+    Test the init_device function,
+    with a TuyaPlug object.
+    """
+    device = init_device("TuyaPlug", ipv4=ipv4)
+    assert isinstance(device, TuyaPlug)
+    assert device.ipv4 == ipv4
+    assert device.android_package == android_package