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