diff --git a/.gitignore b/.gitignore
index aa84aa888a9ddc04b1ff26b1230e541d770906cc..e2e3606b6373b61cb77b5691c34bf34fb5f08f24 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ __pycache__/
 *.log
 .tox
 .cache
+results/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2daf3e1bbee5b23de59955287fb8d5c3e8ed7ad2..afb604bbe6780419824d651bef12057a12932f73 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,32 +1,16 @@
 ---
-stages:
-  - lint
-  - test
-  - release
-
-pre-commit:
-  stage: lint
-  image: adarnimrod/ci-images:pre-commit
-  variables: &variables
-    XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache"
-    PIPENV_VENV_IN_PROJECT: "1"
-    LANG: C.UTF-8
-  before_script:
-    - apt-get update
-    - apt-get install -y libdbus-1-dev
-    - pipenv install --dev && rm pyproject.toml
-  script:
-    - pipenv run lint
-  cache: &cache
-    key: "$CI_JOB_NAME"
-    paths:
-      - .cache/
-      - .venv/
+include:
+  - project: shore/ci-templates
+    file: templates/pre-commit.yml
+  - project: shore/ci-templates
+    file: templates/python.yml
+  - project: shore/ci-templates
+    file: templates/gitlab-release.yml
 
 test:
   stage: test
   image: $project:$version-slim
-  before_script: &before_script
+  before_script:
     - apt-get update
     - >-
       apt-get install -y
@@ -37,6 +21,8 @@ test:
       libffi-dev
       libglib2.0-dev
       libssl-dev
+    # yamllint disable-line rule:line-length
+    - wget https://github.com/cloudbees-oss/juxr/releases/download/0.1.22/juxr-x86_64-unknown-linux-gnu.tar.gz -O - | tar -xzC /usr/local/bin
     - |-
       if [ "$project" = 'pypy' ]
       then
@@ -46,13 +32,22 @@ test:
     - pipenv install --dev --python=$version
   script:
     - pipenv run bats
-    - pipenv run check
     - pipenv run doctest
-    - pipenv run build
+  after_script:
+    - juxr tap --name bats --output results/ -- cat results.tap
   variables:
-    <<: *variables
+    XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache"
+    PIPENV_VENV_IN_PROJECT: "1"
+    LANG: C.UTF-8
     PIPENV_SKIP_LOCK: 1
-  cache: *cache
+  cache:
+    key: "$CI_JOB_NAME"
+    paths:
+      - .cache/
+      - .venv/
+  artifacts:
+    reports:
+      junit: results/*.xml
   parallel:
     matrix:
       - project: python
@@ -67,32 +62,3 @@ test:
           - "2.7"
           - "3.6"
           - "3.7"
-
-upload:
-  stage: release
-  image: python:3.6
-  before_script: *before_script
-  script:
-    - mv "$pypirc" $HOME/.pypirc
-    - pipenv run build
-    - pipenv run upload dist/*
-  variables: *variables
-  cache: *cache
-  rules:
-    - if: $CI_COMMIT_TAG
-  artifacts:
-    paths:
-      - dist/*.whl
-
-release:
-  stage: release
-  image: registry.gitlab.com/gitlab-org/release-cli:latest
-  script:
-    - !!str true
-  rules:
-    - if: $CI_COMMIT_TAG
-  release:
-    name: Release $CI_COMMIT_TAG
-    tag_name: $CI_COMMIT_TAG
-    ref: $CI_COMMIT_TAG
-    description: Release $CI_COMMIT_TAG
diff --git a/Pipfile b/Pipfile
index 0a19f57e81baf2911dc76cea23042c1e9860b20e..b5e9a902bcb418906d40dd9f4c429ca9625e8e7a 100644
--- a/Pipfile
+++ b/Pipfile
@@ -5,9 +5,10 @@ verify_ssl = true
 
 [dev-packages]
 pre-commit = "*"
-bumpversion = {version = "*", markers="python_version >= '3.6'"}
+bumpversion = {version = "*",markers = "python_version >= '3.6'"}
 twine = "*"
 template = {editable = true,path = "."}
+pytest = "*"
 
 [scripts]
 lint = "pre-commit run --all-files"
@@ -16,4 +17,4 @@ clean = "git clean -fdX"
 upload = "twine upload"
 bats = "bats -t tests/"
 check = "sh -c 'rm -rf dist/ && python setup.py bdist_wheel && twine check dist/*'"
-doctest = "sh -c 'python -m doctest template/*.py'"
+doctest = "pytest --doctest-modules --junit-xml results/doctest.xml"
diff --git a/Pipfile.lock b/Pipfile.lock
index 41d5c7348f3db560c0d792e1fcd7f9e3b581bf48..e9fa077dc9c9ba8b4da27fab274fd0b880aaa159 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "ddd82c5bc3cff65fd129139e828c3e9e5dd6720007ced4bf3de815ef06b2f134"
+            "sha256": "421f66614cac0e4a683865046a25ea8c50879b524bb96effc1234c45c9e7829c"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -22,6 +22,13 @@
             ],
             "version": "==1.4.4"
         },
+        "attrs": {
+            "hashes": [
+                "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
+                "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
+            ],
+            "version": "==20.3.0"
+        },
         "bleach": {
             "hashes": [
                 "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080",
@@ -156,10 +163,10 @@
         },
         "identify": {
             "hashes": [
-                "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5",
-                "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e"
+                "sha256:7aef7a5104d6254c162990e54a203cdc0fd202046b6c415bd5d636472f6565c4",
+                "sha256:b2c71bf9f5c482c389cef816f3a15f1c9d7429ad70f497d4a2e522442d80c6de"
             ],
-            "version": "==1.5.10"
+            "version": "==1.5.11"
         },
         "idna": {
             "hashes": [
@@ -178,11 +185,18 @@
         },
         "importlib-resources": {
             "hashes": [
-                "sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592",
-                "sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"
+                "sha256:0a948d0c8c3f9344de62997e3f73444dbba233b1eaf24352933c2d264b9e4182",
+                "sha256:6b45007a479c4ec21165ae3ffbe37faf35404e2041fac6ae1da684f38530ca73"
             ],
             "markers": "python_version < '3.7'",
-            "version": "==3.3.0"
+            "version": "==4.1.1"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+            ],
+            "version": "==1.1.1"
         },
         "jeepney": {
             "hashes": [
@@ -208,10 +222,10 @@
         },
         "keyring": {
             "hashes": [
-                "sha256:12de23258a95f3b13e5b167f7a641a878e91eab8ef16fafc077720a95e6115bb",
-                "sha256:207bd66f2a9881c835dad653da04e196c678bf104f8252141d2d3c4f31051579"
+                "sha256:1746d3ac913d449a090caf11e9e4af00e26c3f7f7e81027872192b2398b98675",
+                "sha256:4be9cbaaaf83e61d6399f733d113ede7d1c73bc75cb6aeb64eee0f6ac39b30ea"
             ],
-            "version": "==21.5.0"
+            "version": "==21.8.0"
         },
         "markupsafe": {
             "hashes": [
@@ -272,6 +286,13 @@
             ],
             "version": "==1.6.1"
         },
+        "pluggy": {
+            "hashes": [
+                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
+                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
+            ],
+            "version": "==0.13.1"
+        },
         "pre-commit": {
             "hashes": [
                 "sha256:6c86d977d00ddc8a60d68eec19f51ef212d9462937acf3ea37c7adec32284ac0",
@@ -280,6 +301,13 @@
             "index": "pypi",
             "version": "==2.9.3"
         },
+        "py": {
+            "hashes": [
+                "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
+                "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
+            ],
+            "version": "==1.10.0"
+        },
         "pycparser": {
             "hashes": [
                 "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
@@ -301,6 +329,14 @@
             ],
             "version": "==2.4.7"
         },
+        "pytest": {
+            "hashes": [
+                "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
+                "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
+            ],
+            "index": "pypi",
+            "version": "==6.2.1"
+        },
         "pyyaml": {
             "hashes": [
                 "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
@@ -375,19 +411,18 @@
         },
         "tqdm": {
             "hashes": [
-                "sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5",
-                "sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"
+                "sha256:556c55b081bd9aa746d34125d024b73f0e2a0e62d5927ff0e400e20ee0a03b9a",
+                "sha256:b8b46036fd00176d0870307123ef06bb851096964fa7fc578d789f90ce82c3e4"
             ],
-            "version": "==4.54.1"
+            "version": "==4.55.1"
         },
         "twine": {
             "hashes": [
-                "sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab",
-                "sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472"
+                "sha256:2f6942ec2a17417e19d2dd372fc4faa424c87ee9ce49b4e20c427eb00a0f3f41",
+                "sha256:fcffa8fc37e8083a5be0728371f299598870ee1eccc94e9a25cef7b1dcfa8297"
             ],
             "index": "pypi",
-            "markers": "python_version >= '3.6'",
-            "version": "==3.2.0"
+            "version": "==3.3.0"
         },
         "typing-extensions": {
             "hashes": [