You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by pa...@apache.org on 2022/09/30 06:19:18 UTC

[beam] branch master updated: Update Python katas to latest version of EduTools and Beam 2.41 (#23180)

This is an automated email from the ASF dual-hosted git repository.

pabloem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new e6a7c505f60 Update Python katas to latest version of EduTools and Beam 2.41 (#23180)
e6a7c505f60 is described below

commit e6a7c505f6033341a4c1a7a2fda2177a8e74280e
Author: Israel Herraiz <ih...@google.com>
AuthorDate: Fri Sep 30 08:19:10 2022 +0200

    Update Python katas to latest version of EduTools and Beam 2.41 (#23180)
    
    * Remove automatically generated files
    
    The files *-remote-info.yaml are automatically generated by Edutools when a new
    version of the course is generated (e.g. new package created for offline
    distribution, or when a new version is uploaded to the courses marketplace).
    
    * Update Python katas to latest version of EduTools
    
    With the latest versions of the EduTools plugin, the previous katas stopped
    working, and this made it impossible to release new versions of the
    katas (e.g. after a new Beam release).
    
    The main change is in the testing method. EduTools now uses Python's unittest
    for checking the exercises.
    
    I have also re-organized some of the katas (e.g. windowing and triggers are now
    under the Streaming category), and updated the katas to Beam 2.41.
    
    * Add missing license headers
---
 .gitignore                                         |   3 +
 .../Aggregation/Count/__init__.py}                 |   3 -
 .../Aggregation/Count/task-info.yaml               |   6 +-
 .../Aggregation/Count/task-remote-info.yaml        |   2 -
 .../Common Transforms/Aggregation/Count/task.py    |   1 -
 .../Common Transforms/Aggregation/Count/tests.py   |  33 ----
 .../Aggregation/Count/tests/__init__.py}           |   3 -
 .../task-info.yaml => Count/tests/test_task.py}    |  25 ++-
 .../Aggregation/Largest/__init__.py}               |   3 -
 .../Aggregation/Largest/task-info.yaml             |   6 +-
 .../Aggregation/Largest/task-remote-info.yaml      |   2 -
 .../Common Transforms/Aggregation/Largest/task.py  |   1 -
 .../Common Transforms/Aggregation/Largest/tests.py |  33 ----
 .../Aggregation/Largest/tests/__init__.py}         |   3 -
 .../task-info.yaml => Largest/tests/test_task.py}  |  25 ++-
 .../Aggregation/Mean/__init__.py}                  |   3 -
 .../Aggregation/Mean/task-info.yaml                |   6 +-
 .../Aggregation/Mean/task-remote-info.yaml         |   2 -
 .../Common Transforms/Aggregation/Mean/task.py     |   1 -
 .../Common Transforms/Aggregation/Mean/tests.py    |  33 ----
 .../Aggregation/Mean/tests/__init__.py}            |   3 -
 .../task-info.yaml => Mean/tests/test_task.py}     |  25 ++-
 .../Aggregation/Smallest/__init__.py}              |   3 -
 .../Aggregation/Smallest/task-info.yaml            |   6 +-
 .../Aggregation/Smallest/task-remote-info.yaml     |   2 -
 .../Common Transforms/Aggregation/Smallest/task.py |   1 -
 .../Aggregation/Smallest/tests.py                  |  33 ----
 .../Aggregation/Smallest/tests/__init__.py}        |   3 -
 .../task-info.yaml => Smallest/tests/test_task.py} |  25 ++-
 .../Aggregation/Sum/__init__.py}                   |   3 -
 .../Aggregation/Sum/task-info.yaml                 |   6 +-
 .../Aggregation/Sum/task-remote-info.yaml          |   2 -
 .../Common Transforms/Aggregation/Sum/task.py      |   1 -
 .../Common Transforms/Aggregation/Sum/tests.py     |  33 ----
 .../Aggregation/Sum/tests/__init__.py}             |   3 -
 .../task-info.yaml => Sum/tests/test_task.py}      |  25 ++-
 .../Aggregation/lesson-remote-info.yaml            |   3 -
 .../Filter/Filter/__init__.py}                     |   3 -
 .../Common Transforms/Filter/Filter/task-info.yaml |   6 +-
 .../Filter/Filter/task-remote-info.yaml            |   2 -
 .../python/Common Transforms/Filter/Filter/task.py |   1 -
 .../Common Transforms/Filter/Filter/tests.py       |  33 ----
 .../Filter/Filter/tests/__init__.py}               |   3 -
 .../Filter/tests/test_task.py}                     |  26 ++-
 .../Filter/ParDo/__init__.py}                      |   3 -
 .../Common Transforms/Filter/ParDo/task-info.yaml  |  12 +-
 .../Filter/ParDo/task-remote-info.yaml             |   2 -
 .../python/Common Transforms/Filter/ParDo/task.py  |   1 -
 .../Filter/ParDo/tests/__init__.py}                |   3 -
 .../ParDo/tests/test_task.py}                      |  26 ++-
 .../Filter/lesson-remote-info.yaml                 |   3 -
 .../WithKeys/WithKeys/__init__.py}                 |   3 -
 .../WithKeys/WithKeys/task-info.yaml               |   6 +-
 .../WithKeys/WithKeys/task-remote-info.yaml        |   2 -
 .../Common Transforms/WithKeys/WithKeys/task.py    |   1 -
 .../Common Transforms/WithKeys/WithKeys/tests.py   |  35 ----
 .../WithKeys/WithKeys/tests/__init__.py}           |   3 -
 .../WithKeys/tests/test_task.py}                   |  27 ++-
 .../WithKeys/lesson-remote-info.yaml               |   3 -
 .../Common Transforms/section-remote-info.yaml     |   2 -
 .../Branching/Branching/__init__.py}               |   3 -
 .../Branching/Branching/task-info.yaml             |   6 +-
 .../Branching/Branching/task-remote-info.yaml      |   2 -
 .../Core Transforms/Branching/Branching/task.py    |   1 -
 .../Core Transforms/Branching/Branching/tests.py   |  43 -----
 .../Branching/Branching/tests/__init__.py}         |   3 -
 .../Branching/Branching/tests/test_task.py}        |  35 +++-
 .../Branching/lesson-remote-info.yaml              |   3 -
 .../CoGroupByKey/CoGroupByKey/__init__.py}         |   3 -
 .../CoGroupByKey/CoGroupByKey/task-info.yaml       |   8 +-
 .../CoGroupByKey/task-remote-info.yaml             |   2 -
 .../CoGroupByKey/CoGroupByKey/task.py              |   3 +-
 .../CoGroupByKey/CoGroupByKey/tests.py             |  37 ----
 .../CoGroupByKey/CoGroupByKey/tests/__init__.py}   |   3 -
 .../CoGroupByKey/CoGroupByKey/tests/test_task.py}  |  30 ++-
 .../CoGroupByKey/lesson-remote-info.yaml           |   3 -
 .../Combine/Combine PerKey/__init__.py}            |   3 -
 .../Combine/Combine PerKey/task-info.yaml          |   6 +-
 .../Combine/Combine PerKey/task-remote-info.yaml   |   2 -
 .../Core Transforms/Combine/Combine PerKey/task.py |   1 -
 .../Combine/Combine PerKey/tests.py                |  38 ----
 .../Combine/Combine PerKey/tests/__init__.py}      |   3 -
 .../Combine/Combine PerKey/tests/test_task.py}     |  30 ++-
 .../Combine/CombineFn/__init__.py}                 |   3 -
 .../Combine/CombineFn/task-info.yaml               |   6 +-
 .../Combine/CombineFn/task-remote-info.yaml        |   2 -
 .../Core Transforms/Combine/CombineFn/task.py      |   1 -
 .../Core Transforms/Combine/CombineFn/tests.py     |  33 ----
 .../Combine/CombineFn/tests/__init__.py}           |   3 -
 .../Combine/CombineFn/tests/test_task.py}          |  24 ++-
 .../Combine/Simple Function/__init__.py}           |   3 -
 .../Combine/Simple Function/task-info.yaml         |   6 +-
 .../Combine/Simple Function/task-remote-info.yaml  |   2 -
 .../Combine/Simple Function/task.py                |   1 -
 .../Combine/Simple Function/tests.py               |  33 ----
 .../Combine/Simple Function/tests/__init__.py}     |   3 -
 .../Combine/Simple Function/tests/test_task.py}    |  24 ++-
 .../Combine/lesson-remote-info.yaml                |   3 -
 .../Composite Transform/__init__.py}               |   3 -
 .../Composite Transform/task-info.yaml             |   6 +-
 .../Composite Transform/task-remote-info.yaml      |   2 -
 .../Composite Transform/task.py                    |   1 -
 .../Composite Transform/tests.py                   |  33 ----
 .../Composite Transform/tests/__init__.py}         |   3 -
 .../Composite Transform/tests/test_task.py}        |  26 ++-
 .../Composite Transform/lesson-remote-info.yaml    |   3 -
 .../Flatten/Flatten/__init__.py}                   |   3 -
 .../Core Transforms/Flatten/Flatten/task-info.yaml |   6 +-
 .../Flatten/Flatten/task-remote-info.yaml          |   2 -
 .../python/Core Transforms/Flatten/Flatten/task.py |   1 -
 .../Core Transforms/Flatten/Flatten/tests.py       |  33 ----
 .../Flatten/Flatten/tests/__init__.py}             |   3 -
 .../Flatten/Flatten/tests/test_task.py}            |  26 ++-
 .../Flatten/lesson-remote-info.yaml                |   3 -
 .../GroupByKey/GroupByKey/__init__.py}             |   3 -
 .../GroupByKey/GroupByKey/task-info.yaml           |   8 +-
 .../GroupByKey/GroupByKey/task-remote-info.yaml    |   2 -
 .../Core Transforms/GroupByKey/GroupByKey/task.py  |   3 +-
 .../Core Transforms/GroupByKey/GroupByKey/tests.py |  35 ----
 .../GroupByKey/GroupByKey/tests/__init__.py}       |   3 -
 .../GroupByKey/GroupByKey/tests/test_task.py}      |  28 ++-
 .../GroupByKey/lesson-remote-info.yaml             |   3 -
 .../Map/FlatMap/__init__.py}                       |   3 -
 .../Core Transforms/Map/FlatMap/task-info.yaml     |   6 +-
 .../Map/FlatMap/task-remote-info.yaml              |   2 -
 .../python/Core Transforms/Map/FlatMap/task.py     |   1 -
 .../python/Core Transforms/Map/FlatMap/tests.py    |  33 ----
 .../Map/FlatMap/tests/__init__.py}                 |   3 -
 .../Map/FlatMap/tests/test_task.py}                |  26 ++-
 .../Map/Map/__init__.py}                           |   3 -
 .../python/Core Transforms/Map/Map/task-info.yaml  |   6 +-
 .../Core Transforms/Map/Map/task-remote-info.yaml  |   2 -
 .../katas/python/Core Transforms/Map/Map/task.py   |   1 -
 .../katas/python/Core Transforms/Map/Map/tests.py  |  33 ----
 .../Map/Map/tests/__init__.py}                     |   3 -
 .../Map/Map/tests/test_task.py}                    |  26 ++-
 .../Map/ParDo OneToMany/__init__.py}               |   3 -
 .../Map/ParDo OneToMany/task-info.yaml             |  10 +-
 .../Map/ParDo OneToMany/task-remote-info.yaml      |   2 -
 .../Core Transforms/Map/ParDo OneToMany/task.py    |   3 +-
 .../Core Transforms/Map/ParDo OneToMany/tests.py   |  33 ----
 .../Map/ParDo OneToMany/tests/__init__.py}         |   3 -
 .../Map/ParDo OneToMany/tests/test_task.py}        |  26 ++-
 .../Map/ParDo/__init__.py}                         |   3 -
 .../Core Transforms/Map/ParDo/task-info.yaml       |   6 +-
 .../Map/ParDo/task-remote-info.yaml                |   2 -
 .../python/Core Transforms/Map/ParDo/tests.py      |  33 ----
 .../Map/ParDo/tests/__init__.py}                   |   3 -
 .../Map/ParDo/tests/test_task.py}                  |  23 ++-
 .../Core Transforms/Map/lesson-remote-info.yaml    |   3 -
 .../Partition/Partition/__init__.py}               |   3 -
 .../Partition/Partition/task-info.yaml             |   6 +-
 .../Partition/Partition/task-remote-info.yaml      |   2 -
 .../Core Transforms/Partition/Partition/task.py    |   1 -
 .../Core Transforms/Partition/Partition/tests.py   |  42 -----
 .../Partition/Partition/tests/__init__.py}         |   3 -
 .../Partition/Partition/tests/test_task.py}        |  35 +++-
 .../Partition/lesson-remote-info.yaml              |   3 -
 .../Side Input/Side Input/__init__.py}             |   3 -
 .../Side Input/Side Input/task-info.yaml           |  14 +-
 .../Side Input/Side Input/task-remote-info.yaml    |   2 -
 .../Core Transforms/Side Input/Side Input/task.py  |  26 +--
 .../Core Transforms/Side Input/Side Input/tests.py |  47 -----
 .../Side Input/Side Input/tests/__init__.py}       |   3 -
 .../Side Input/Side Input/tests/test_task.py}      |  32 +++-
 .../Side Input/lesson-remote-info.yaml             |   3 -
 .../Side Output/Side Output/__init__.py}           |   3 -
 .../Side Output/Side Output/task-info.yaml         |   8 +-
 .../Side Output/Side Output/task-remote-info.yaml  |   2 -
 .../Side Output/Side Output/task.py                |   1 -
 .../Side Output/Side Output/tests.py               |  42 -----
 .../Side Output/Side Output/tests/__init__.py}     |   3 -
 .../Side Output/Side Output/tests/test_task.py}    |  35 +++-
 .../Side Output/lesson-remote-info.yaml            |   3 -
 .../Core Transforms/section-remote-info.yaml       |   2 -
 .../Word Count/Word Count/__init__.py}             |   3 -
 .../Examples/Word Count/Word Count/task-info.yaml  |  10 +-
 .../Word Count/Word Count/task-remote-info.yaml    |   2 -
 .../python/Examples/Word Count/Word Count/task.py  |   3 -
 .../python/Examples/Word Count/Word Count/tests.py |  39 ----
 .../Word Count/Word Count/tests/__init__.py}       |   3 -
 .../Word Count/Word Count/tests/test_task.py}      |  32 +++-
 .../Examples/Word Count/lesson-remote-info.yaml    |   3 -
 .../katas/python/Examples/section-remote-info.yaml |   2 -
 .../IO/Built-in IOs/Built-in IOs/task-info.yaml    |  25 ---
 .../Built-in IOs/task-remote-info.yaml             |   2 -
 .../python/IO/Built-in IOs/Built-in IOs/task.md    |  28 ---
 .../python/IO/Built-in IOs/Built-in IOs/task.py    |  21 ---
 .../python/IO/Built-in IOs/Built-in IOs/tests.py   |  16 --
 .../python/IO/Built-in IOs/lesson-remote-info.yaml |   3 -
 .../TextIO/ReadFromText/__init__.py}               |   3 -
 .../python/IO/TextIO/ReadFromText/task-info.yaml   |  15 +-
 .../IO/TextIO/ReadFromText/task-remote-info.yaml   |   2 -
 .../katas/python/IO/TextIO/ReadFromText/task.md    |   9 +
 .../katas/python/IO/TextIO/ReadFromText/task.py    |   4 +-
 .../katas/python/IO/TextIO/ReadFromText/tests.py   |  44 -----
 .../TextIO/ReadFromText/tests/__init__.py}         |   3 -
 .../TextIO/ReadFromText/tests/test_task.py}        |  37 +++-
 .../katas/python/IO/TextIO/lesson-remote-info.yaml |   3 -
 learning/katas/python/IO/section-info.yaml         |   1 -
 learning/katas/python/IO/section-remote-info.yaml  |   2 -
 .../Hello Beam/Hello Beam/__init__.py}             |   3 -
 .../Hello Beam/Hello Beam/task-info.yaml           |   6 +-
 .../Hello Beam/Hello Beam/task-remote-info.yaml    |   2 -
 .../Introduction/Hello Beam/Hello Beam/task.md     |   2 +-
 .../Introduction/Hello Beam/Hello Beam/tests.py    |  34 ----
 .../Hello Beam/Hello Beam/tests/__init__.py}       |   3 -
 .../Hello Beam/Hello Beam/tests/test_task.py}      |  23 +--
 .../Hello Beam/lesson-remote-info.yaml             |   3 -
 .../python/Introduction/section-remote-info.yaml   |   2 -
 .../Timestamps/Add Timestamps/__init__.py}         |   3 -
 .../Timestamps/Add Timestamps}/task-info.yaml      |  14 +-
 .../Timestamps/Add Timestamps}/task.md             |   0
 .../Timestamps/Add Timestamps}/task.py             |   2 -
 .../Timestamps/Add Timestamps/tests/__init__.py}   |   3 -
 .../Timestamps/Add Timestamps/tests/test_task.py   |  41 ++++
 .../Timestamps}/lesson-info.yaml                   |   2 +-
 .../Triggers/Early Triggers/__init__.py}           |   3 -
 .../Triggers}/Early Triggers/generate_event.py     |   0
 .../Triggers}/Early Triggers/task-info.yaml        |   6 +-
 .../Triggers}/Early Triggers/task.md               |   2 +-
 .../Triggers}/Early Triggers/task.py               |   0
 .../Triggers/Early Triggers/tests/__init__.py}     |   3 -
 .../Triggers/Early Triggers/tests/test_task.py     |  55 ++++++
 .../Triggers/Event Time Triggers/__init__.py}      |   3 -
 .../Event Time Triggers/generate_event.py          |   0
 .../Triggers}/Event Time Triggers/task-info.yaml   |   8 +-
 .../Triggers}/Event Time Triggers/task.md          |   0
 .../Triggers}/Event Time Triggers/task.py          |   0
 .../Event Time Triggers/tests/__init__.py}         |   3 -
 .../Event Time Triggers/tests/test_task.py         |  41 ++++
 .../Window Accumulation Modes/__init__.py}         |   3 -
 .../Window Accumulation Modes}/generate_event.py   |   0
 .../Window Accumulation Modes}/task-info.yaml      |   8 +-
 .../Triggers/Window Accumulation Modes}/task.md    |   2 +-
 .../Triggers/Window Accumulation Modes}/task.py    |   0
 .../Window Accumulation Modes/tests/__init__.py}   |   3 -
 .../Window Accumulation Modes/tests/test_task.py   |  54 ++++++
 .../Triggers/lesson-info.yaml}                     |   2 +-
 .../Windows/Fixed Windows/__init__.py}             |   3 -
 .../Windows/Fixed Windows}/task-info.yaml          |   6 +-
 .../Windows/Fixed Windows}/task.md                 |   0
 .../Windows/Fixed Windows}/task.py                 |   1 -
 .../Windows/Fixed Windows/tests/__init__.py}       |   3 -
 .../Windows/Fixed Windows/tests/test_task.py       |  40 ++++
 .../Windows}/lesson-info.yaml                      |   2 +-
 .../section-info.yaml}                             |   4 +-
 .../Early Triggers/task-remote-info.yaml           |   2 -
 .../Early Triggers/Early Triggers/tests.py         |  54 ------
 .../Early Triggers/lesson-remote-info.yaml         |   3 -
 .../Event Time Triggers/task-remote-info.yaml      |   2 -
 .../Event Time Triggers/tests.py                   |  39 ----
 .../Event Time Triggers/lesson-remote-info.yaml    |   3 -
 .../Window Accumulation Mode/task-remote-info.yaml |   2 -
 .../Window Accumulation Mode/tests.py              |  52 ------
 .../Window Accumulation Mode/lesson-info.yaml      |  21 ---
 .../lesson-remote-info.yaml                        |   3 -
 .../katas/python/Triggers/section-remote-info.yaml |   2 -
 .../Adding Timestamp/ParDo/task-remote-info.yaml   |   2 -
 .../Windowing/Adding Timestamp/ParDo/tests.py      |  39 ----
 .../Adding Timestamp/lesson-remote-info.yaml       |   3 -
 .../Fixed Time Window/task-remote-info.yaml        |   2 -
 .../Fixed Time Window/Fixed Time Window/tests.py   |  38 ----
 .../Fixed Time Window/lesson-remote-info.yaml      |   3 -
 learning/katas/python/Windowing/section-info.yaml  |  22 ---
 .../python/Windowing/section-remote-info.yaml      |   2 -
 learning/katas/python/course-info.yaml             |  15 +-
 learning/katas/python/course-remote-info.yaml      |   2 -
 learning/katas/python/requirements.txt             |   6 +-
 learning/katas/python/test_helper.py               | 207 ++-------------------
 270 files changed, 932 insertions(+), 2161 deletions(-)

diff --git a/.gitignore b/.gitignore
index ec600f50f83..fede9851091 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,3 +133,6 @@ website/www/yarn-error.log
 **/*.tfstate.*
 **/*.hcl
 **/*.tfvars
+
+# Ignore Katas auto-generated files
+**/*-remote-info.yaml
\ No newline at end of file
diff --git a/learning/katas/python/Triggers/Early Triggers/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Count/__init__.py
similarity index 96%
rename from learning/katas/python/Triggers/Early Triggers/lesson-info.yaml
rename to learning/katas/python/Common Transforms/Aggregation/Count/__init__.py
index 184f82e8281..30097ef0481 100644
--- a/learning/katas/python/Triggers/Early Triggers/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Count/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- Early Triggers
diff --git a/learning/katas/python/Common Transforms/Aggregation/Count/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Count/task-info.yaml
index 99a6d4f98a4..0830c40df32 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Count/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Count/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1134
     length: 31
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Aggregation/Count/task-remote-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Count/task-remote-info.yaml
deleted file mode 100644
index 7c826b4c43a..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Count/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755597
-update_date: Sat, 01 Aug 2020 09:42:11 UTC
diff --git a/learning/katas/python/Common Transforms/Aggregation/Count/task.py b/learning/katas/python/Common Transforms/Aggregation/Count/task.py
index 58bf24dca16..6920643f98e 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Count/task.py	
+++ b/learning/katas/python/Common Transforms/Aggregation/Count/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.combiners.Count.Globally()
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/Aggregation/Count/tests.py b/learning/katas/python/Common Transforms/Aggregation/Count/tests.py
deleted file mode 100644
index 3cdcee10214..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Count/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['10']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Use Count.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Fixed Time Window/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Count/tests/__init__.py
similarity index 96%
rename from learning/katas/python/Windowing/Fixed Time Window/lesson-info.yaml
rename to learning/katas/python/Common Transforms/Aggregation/Count/tests/__init__.py
index 9f65c8adcd8..30097ef0481 100644
--- a/learning/katas/python/Windowing/Fixed Time Window/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Count/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- Fixed Time Window
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Count/tests/test_task.py
similarity index 66%
copy from learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Count/tests/test_task.py
index 44afe842448..0945c4e29cb 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Count/tests/test_task.py	
@@ -17,13 +17,18 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1141
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '10'
+
+        self.assertIn(answer, output, "Incorrect output. Use the Count combiner.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Largest/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Largest/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Largest/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Largest/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Largest/task-info.yaml
index 02682221566..3f9c25cddb3 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Largest/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Largest/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1150
     length: 29
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Aggregation/Largest/task-remote-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Largest/task-remote-info.yaml
deleted file mode 100644
index 372d7065588..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Largest/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755601
-update_date: Sat, 01 Aug 2020 09:42:23 UTC
diff --git a/learning/katas/python/Common Transforms/Aggregation/Largest/task.py b/learning/katas/python/Common Transforms/Aggregation/Largest/task.py
index 3ef2b3c352c..c4c72ecfa4a 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Largest/task.py	
+++ b/learning/katas/python/Common Transforms/Aggregation/Largest/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.combiners.Top.Largest(2)
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/Aggregation/Largest/tests.py b/learning/katas/python/Common Transforms/Aggregation/Largest/tests.py
deleted file mode 100644
index 9836dbd39cc..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Largest/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['[10, 9]']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Calculate a list of the largest two elements.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Largest/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Largest/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Largest/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Largest/tests/test_task.py
similarity index 66%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Largest/tests/test_task.py
index b4013450572..4c03341741d 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Largest/tests/test_task.py	
@@ -17,13 +17,18 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '[10, 9]'
+
+        self.assertIn(answer, output, "Incorrect output. Use the Top.Largest combiner.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Mean/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Mean/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Mean/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
index b4013450572..02b6e506ec8 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1156
     length: 30
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-remote-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Mean/task-remote-info.yaml
deleted file mode 100644
index 3f6a9da7355..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755599
-update_date: Sat, 01 Aug 2020 09:42:17 UTC
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task.py b/learning/katas/python/Common Transforms/Aggregation/Mean/task.py
index 493631dd7e9..ab6456e1f89 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task.py	
+++ b/learning/katas/python/Common Transforms/Aggregation/Mean/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.combiners.Mean.Globally()
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/tests.py b/learning/katas/python/Common Transforms/Aggregation/Mean/tests.py
deleted file mode 100644
index 0f21c4886a6..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['5.5']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Calculate the mean of all elements.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Mean/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Mean/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Mean/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Mean/tests/test_task.py
similarity index 66%
copy from learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Mean/tests/test_task.py
index 44afe842448..1cbd2aa0136 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Mean/tests/test_task.py	
@@ -17,13 +17,18 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1141
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '5.5'
+
+        self.assertIn(answer, output, "Incorrect output. Use the Mean combiner.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Smallest/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Smallest/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Smallest/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml
index 44afe842448..b5919c4b5d5 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1141
     length: 30
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-remote-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Smallest/task-remote-info.yaml
deleted file mode 100644
index 6d7ffb5b66a..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755600
-update_date: Sat, 01 Aug 2020 09:42:20 UTC
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task.py b/learning/katas/python/Common Transforms/Aggregation/Smallest/task.py
index 78af939440d..5c070d68f7f 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task.py	
+++ b/learning/katas/python/Common Transforms/Aggregation/Smallest/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.combiners.Top.Smallest(1)
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/tests.py b/learning/katas/python/Common Transforms/Aggregation/Smallest/tests.py
deleted file mode 100644
index 0e574670e53..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['[1]']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Calculate the smallest of all elements.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Smallest/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Smallest/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Smallest/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Smallest/tests/test_task.py
similarity index 66%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Smallest/tests/test_task.py
index b4013450572..d060e9baf1f 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Smallest/tests/test_task.py	
@@ -17,13 +17,18 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '[1]'
+
+        self.assertIn(answer, output, "Incorrect output. Use the Top.Smallest combiner.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Sum/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Sum/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Sum/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Sum/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Sum/task-info.yaml
index d909a48ddd8..53e3354edfd 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Sum/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Sum/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1135
     length: 25
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Aggregation/Sum/task-remote-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Sum/task-remote-info.yaml
deleted file mode 100644
index 8a69eaec02b..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Sum/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755598
-update_date: Sat, 01 Aug 2020 09:42:14 UTC
diff --git a/learning/katas/python/Common Transforms/Aggregation/Sum/task.py b/learning/katas/python/Common Transforms/Aggregation/Sum/task.py
index 4d7719c498a..631f96c1ee2 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Sum/task.py	
+++ b/learning/katas/python/Common Transforms/Aggregation/Sum/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.CombineGlobally(sum)
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/Aggregation/Sum/tests.py b/learning/katas/python/Common Transforms/Aggregation/Sum/tests.py
deleted file mode 100644
index 30964e79956..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/Sum/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['55']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Calculate the sum of all elements.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Sum/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Sum/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Sum/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/Aggregation/Sum/tests/test_task.py
similarity index 65%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Common Transforms/Aggregation/Sum/tests/test_task.py
index b4013450572..29b0493f7e7 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Aggregation/Sum/tests/test_task.py	
@@ -17,13 +17,18 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '55'
+
+        self.assertIn(answer, output, "Incorrect output. Use CombineGlobally with the builtin sum function.")
diff --git a/learning/katas/python/Common Transforms/Aggregation/lesson-remote-info.yaml b/learning/katas/python/Common Transforms/Aggregation/lesson-remote-info.yaml
deleted file mode 100644
index f1ca421ff3d..00000000000
--- a/learning/katas/python/Common Transforms/Aggregation/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238438
-update_date: Mon, 09 Mar 2020 14:44:48 UTC
-unit: 210898
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Filter/Filter/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Filter/Filter/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/Filter/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Filter/Filter/task-info.yaml b/learning/katas/python/Common Transforms/Filter/Filter/task-info.yaml
index b2cdd321f13..0164f0fce48 100644
--- a/learning/katas/python/Common Transforms/Filter/Filter/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/Filter/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1152
     length: 37
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Filter/Filter/task-remote-info.yaml b/learning/katas/python/Common Transforms/Filter/Filter/task-remote-info.yaml
deleted file mode 100644
index f0db9079c03..00000000000
--- a/learning/katas/python/Common Transforms/Filter/Filter/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755596
-update_date: Sat, 01 Aug 2020 09:42:09 UTC
diff --git a/learning/katas/python/Common Transforms/Filter/Filter/task.py b/learning/katas/python/Common Transforms/Filter/Filter/task.py
index e6b93cf3dcc..ff3fc9dd4ff 100644
--- a/learning/katas/python/Common Transforms/Filter/Filter/task.py	
+++ b/learning/katas/python/Common Transforms/Filter/Filter/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.Filter(lambda num: num % 2 == 0)
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/Filter/Filter/tests.py b/learning/katas/python/Common Transforms/Filter/Filter/tests.py
deleted file mode 100644
index da8cd880051..00000000000
--- a/learning/katas/python/Common Transforms/Filter/Filter/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['2', '4', '6', '8', '10']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Filter out the odd numbers.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Filter/Filter/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Filter/Filter/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/Filter/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/Filter/Filter/tests/test_task.py
similarity index 63%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Common Transforms/Filter/Filter/tests/test_task.py
index b4013450572..032df7a96fd 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/Filter/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['2', '4', '6', '8', '10']
+
+        for num in answers:
+            self.assertIn(num, output, "Incorrect output. Filter out the odd numbers.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Filter/ParDo/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Filter/ParDo/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/ParDo/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Filter/ParDo/task-info.yaml b/learning/katas/python/Common Transforms/Filter/ParDo/task-info.yaml
index c63886dd467..00b0f82ee5f 100644
--- a/learning/katas/python/Common Transforms/Filter/ParDo/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/ParDo/task-info.yaml	
@@ -22,11 +22,15 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 1290
-    length: 33
-    placeholder_text: TODO()
   - offset: 1133
     length: 87
     placeholder_text: TODO()
-- name: tests.py
+  - offset: 1290
+    length: 33
+    placeholder_text: TODO()
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/Filter/ParDo/task-remote-info.yaml b/learning/katas/python/Common Transforms/Filter/ParDo/task-remote-info.yaml
deleted file mode 100644
index 283880cbf95..00000000000
--- a/learning/katas/python/Common Transforms/Filter/ParDo/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755595
-update_date: Sat, 01 Aug 2020 09:42:06 UTC
diff --git a/learning/katas/python/Common Transforms/Filter/ParDo/task.py b/learning/katas/python/Common Transforms/Filter/ParDo/task.py
index 3a68fce5e68..63cc7930b34 100644
--- a/learning/katas/python/Common Transforms/Filter/ParDo/task.py	
+++ b/learning/katas/python/Common Transforms/Filter/ParDo/task.py	
@@ -38,4 +38,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(range(1, 11))
      | beam.ParDo(FilterOutEvenNumber())
      | LogElements())
-
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/Filter/ParDo/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/Filter/ParDo/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/ParDo/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/Filter/ParDo/tests/test_task.py
similarity index 63%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Common Transforms/Filter/ParDo/tests/test_task.py
index b4013450572..2f80ea828f2 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/Filter/ParDo/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['1', '3', '5', '7', '9']
+
+        for num in answers:
+            self.assertIn(num, output, "Incorrect output. Filter out the even numbers.")
diff --git a/learning/katas/python/Common Transforms/Filter/lesson-remote-info.yaml b/learning/katas/python/Common Transforms/Filter/lesson-remote-info.yaml
deleted file mode 100644
index 64c1510ee41..00000000000
--- a/learning/katas/python/Common Transforms/Filter/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238437
-update_date: Mon, 09 Mar 2020 14:44:44 UTC
-unit: 210897
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/WithKeys/WithKeys/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/WithKeys/WithKeys/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/WithKeys/WithKeys/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-info.yaml b/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-info.yaml
index c26ccfa86e4..d251e491e5f 100644
--- a/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1200
     length: 37
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-remote-info.yaml b/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-remote-info.yaml
deleted file mode 100644
index 18a370d3bc4..00000000000
--- a/learning/katas/python/Common Transforms/WithKeys/WithKeys/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 1124221
-update_date: Sat, 01 Aug 2020 09:42:26 UTC
diff --git a/learning/katas/python/Common Transforms/WithKeys/WithKeys/task.py b/learning/katas/python/Common Transforms/WithKeys/WithKeys/task.py
index 27a0cc53c35..b94b0b36bf0 100644
--- a/learning/katas/python/Common Transforms/WithKeys/WithKeys/task.py	
+++ b/learning/katas/python/Common Transforms/WithKeys/WithKeys/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(['apple', 'banana', 'cherry', 'durian', 'guava', 'melon'])
      | beam.WithKeys(lambda word: word[0:1])
      | LogElements())
-
diff --git a/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests.py b/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests.py
deleted file mode 100644
index 7b37e800894..00000000000
--- a/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests.py	
+++ /dev/null
@@ -1,35 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ["('a', 'apple')", "('b', 'banana')", "('c', 'cherry')",
-               "('d', 'durian')", "('g', 'guava')", "('m', 'melon')"]
-
-    if all(kv in output for kv in answers):
-        passed()
-    else:
-        failed('Incorrect output. ' +
-               'Convert into a KV by its first letter and itself.')
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Common Transforms/WithKeys/WithKeys/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests/test_task.py
similarity index 57%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Common Transforms/WithKeys/WithKeys/tests/test_task.py
index b4013450572..92cb3c70d27 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Common Transforms/WithKeys/WithKeys/tests/test_task.py	
@@ -17,13 +17,20 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ["('a', 'apple')", "('b', 'banana')", "('c', 'cherry')",
+                   "('d', 'durian')", "('g', 'guava')", "('m', 'melon')"]
+
+        for num in answers:
+            self.assertIn(num, output, "Incorrect output. Convert into a KV by its first letter and itself.")
diff --git a/learning/katas/python/Common Transforms/WithKeys/lesson-remote-info.yaml b/learning/katas/python/Common Transforms/WithKeys/lesson-remote-info.yaml
deleted file mode 100644
index ec6b8dcab05..00000000000
--- a/learning/katas/python/Common Transforms/WithKeys/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 316601
-update_date: Mon, 09 Mar 2020 14:44:52 UTC
-unit: 299309
diff --git a/learning/katas/python/Common Transforms/section-remote-info.yaml b/learning/katas/python/Common Transforms/section-remote-info.yaml
deleted file mode 100644
index 4f76ab52eb5..00000000000
--- a/learning/katas/python/Common Transforms/section-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 85646
-update_date: Thu, 13 Jun 2019 13:03:29 UTC
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Branching/Branching/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Branching/Branching/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Branching/Branching/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Branching/Branching/task-info.yaml b/learning/katas/python/Core Transforms/Branching/Branching/task-info.yaml
index 857ca3b646b..68b8cf784c5 100644
--- a/learning/katas/python/Core Transforms/Branching/Branching/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Branching/Branching/task-info.yaml	
@@ -28,5 +28,9 @@ files:
   - offset: 1354
     length: 40
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Branching/Branching/task-remote-info.yaml b/learning/katas/python/Core Transforms/Branching/Branching/task-remote-info.yaml
deleted file mode 100644
index 34690e7c2d2..00000000000
--- a/learning/katas/python/Core Transforms/Branching/Branching/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755592
-update_date: Sat, 01 Aug 2020 09:42:00 UTC
diff --git a/learning/katas/python/Core Transforms/Branching/Branching/task.py b/learning/katas/python/Core Transforms/Branching/Branching/task.py
index 485e927a598..a0721847a3c 100644
--- a/learning/katas/python/Core Transforms/Branching/Branching/task.py	
+++ b/learning/katas/python/Core Transforms/Branching/Branching/task.py	
@@ -37,4 +37,3 @@ with beam.Pipeline() as p:
 
   mult5_results | 'Log multiply 5' >> LogElements(prefix='Multiplied by 5: ')
   mult10_results | 'Log multiply 10' >> LogElements(prefix='Multiplied by 10: ')
-
diff --git a/learning/katas/python/Core Transforms/Branching/Branching/tests.py b/learning/katas/python/Core Transforms/Branching/Branching/tests.py
deleted file mode 100644
index 6df8cd5fdee..00000000000
--- a/learning/katas/python/Core Transforms/Branching/Branching/tests.py	
+++ /dev/null
@@ -1,43 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    mult5_results = ['5', '10', '15', '20', '25']
-    mult10_results = ['10', '20', '30', '40', '50']
-
-    answers = []
-
-    for num in mult5_results:
-        answers.append('Multiplied by 5: ' + num)
-
-    for num in mult10_results:
-        answers.append('Multiplied by 10: ' + num)
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed('Incorrect output. Branch out the numbers and multiply '
-               'accordingly.')
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Branching/Branching/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Branching/Branching/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Branching/Branching/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Branching/Branching/tests/test_task.py
similarity index 51%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Branching/Branching/tests/test_task.py
index b4013450572..adc4c8a452e 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Branching/Branching/tests/test_task.py	
@@ -17,13 +17,28 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        mult5_results = ['5', '10', '15', '20', '25']
+        mult10_results = ['10', '20', '30', '40', '50']
+
+        answers = []
+
+        for num in mult5_results:
+            answers.append('Multiplied by 5: ' + num)
+
+        for num in mult10_results:
+            answers.append('Multiplied by 10: ' + num)
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Branch out the numbers and multiply accordingly.")
diff --git a/learning/katas/python/Core Transforms/Branching/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Branching/lesson-remote-info.yaml
deleted file mode 100644
index 3848b9cfb20..00000000000
--- a/learning/katas/python/Core Transforms/Branching/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238435
-update_date: Wed, 19 Jun 2019 09:54:50 UTC
-unit: 210895
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-info.yaml b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-info.yaml
index 52a393d00ac..bfead684b1b 100644
--- a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-info.yaml	
@@ -22,8 +22,12 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 1542
+  - offset: 1540
     length: 545
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-remote-info.yaml b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-remote-info.yaml
deleted file mode 100644
index 08be3d36eaf..00000000000
--- a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755583
-update_date: Sat, 01 Aug 2020 09:41:35 UTC
diff --git a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task.py b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task.py
index b80842a969b..0ee8219004e 100644
--- a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task.py	
+++ b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/task.py	
@@ -21,7 +21,7 @@
 #   multifile: false
 #   context_line: 31
 #   categories:
-#     - Combiners
+#     - Combine
 
 import apache_beam as beam
 
@@ -62,4 +62,3 @@ with beam.Pipeline() as p:
 
   (apply_transforms(fruits, countries)
    | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests.py b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests.py
deleted file mode 100644
index 16e0501187e..00000000000
--- a/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests.py	
+++ /dev/null
@@ -1,37 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        "WordsAlphabet(alphabet:'a', fruit='apple', country='australia')",
-        "WordsAlphabet(alphabet:'b', fruit='banana', country='brazil')",
-        "WordsAlphabet(alphabet:'c', fruit='cherry', country='canada')"
-    ]
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Use CoGroupByKey.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests/test_task.py
similarity index 53%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests/test_task.py
index b4013450572..82d22c59ddd 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/CoGroupByKey/CoGroupByKey/tests/test_task.py	
@@ -17,13 +17,23 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            "WordsAlphabet(alphabet:'a', fruit='apple', country='australia')",
+            "WordsAlphabet(alphabet:'b', fruit='banana', country='brazil')",
+            "WordsAlphabet(alphabet:'c', fruit='cherry', country='canada')"
+        ]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Generate WordsAlphabet objects after CoGroupByKey.")
diff --git a/learning/katas/python/Core Transforms/CoGroupByKey/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/CoGroupByKey/lesson-remote-info.yaml
deleted file mode 100644
index bdca1ad4908..00000000000
--- a/learning/katas/python/Core Transforms/CoGroupByKey/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238429
-update_date: Wed, 19 Jun 2019 09:53:26 UTC
-unit: 210889
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Combine/Combine PerKey/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Combine/Combine PerKey/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Combine PerKey/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-info.yaml b/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-info.yaml
index a5bd8f868e9..58c6f9d13a9 100644
--- a/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1303
     length: 23
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-remote-info.yaml b/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-remote-info.yaml
deleted file mode 100644
index 070eaad415b..00000000000
--- a/learning/katas/python/Core Transforms/Combine/Combine PerKey/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755587
-update_date: Sat, 01 Aug 2020 09:41:46 UTC
diff --git a/learning/katas/python/Core Transforms/Combine/Combine PerKey/task.py b/learning/katas/python/Core Transforms/Combine/Combine PerKey/task.py
index 004dc8312a3..0333d79265a 100644
--- a/learning/katas/python/Core Transforms/Combine/Combine PerKey/task.py	
+++ b/learning/katas/python/Core Transforms/Combine/Combine PerKey/task.py	
@@ -36,4 +36,3 @@ with beam.Pipeline() as p:
                     (PLAYER_3, 25), (PLAYER_2, 75)])
      | beam.CombinePerKey(sum)
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests.py b/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests.py
deleted file mode 100644
index cd6ab2e6638..00000000000
--- a/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests.py	
+++ /dev/null
@@ -1,38 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    PLAYER_1 = 'Player 1'
-    PLAYER_2 = 'Player 2'
-    PLAYER_3 = 'Player 3'
-
-    answers = [str((PLAYER_1, 115)), str((PLAYER_2, 85)), str((PLAYER_3, 25))]
-    print(answers)
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Sum all the scores per player.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Combine/Combine PerKey/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests/test_task.py
similarity index 58%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Combine/Combine PerKey/tests/test_task.py
index b4013450572..a6c67cbe913 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Combine PerKey/tests/test_task.py	
@@ -17,13 +17,23 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        PLAYER_1 = 'Player 1'
+        PLAYER_2 = 'Player 2'
+        PLAYER_3 = 'Player 3'
+
+        answers = [str((PLAYER_1, 115)), str((PLAYER_2, 85)), str((PLAYER_3, 25))]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Sum all the scores per player.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Combine/CombineFn/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Combine/CombineFn/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/CombineFn/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Combine/CombineFn/task-info.yaml b/learning/katas/python/Core Transforms/Combine/CombineFn/task-info.yaml
index 8a8dafa7697..2344c2e1024 100644
--- a/learning/katas/python/Core Transforms/Combine/CombineFn/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/CombineFn/task-info.yaml	
@@ -28,5 +28,9 @@ files:
   - offset: 1591
     length: 33
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Combine/CombineFn/task-remote-info.yaml b/learning/katas/python/Core Transforms/Combine/CombineFn/task-remote-info.yaml
deleted file mode 100644
index 1e1a5780513..00000000000
--- a/learning/katas/python/Core Transforms/Combine/CombineFn/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755585
-update_date: Sat, 01 Aug 2020 09:41:42 UTC
diff --git a/learning/katas/python/Core Transforms/Combine/CombineFn/task.py b/learning/katas/python/Core Transforms/Combine/CombineFn/task.py
index 2e68a1cd6f9..eb5ecd10ab1 100644
--- a/learning/katas/python/Core Transforms/Combine/CombineFn/task.py	
+++ b/learning/katas/python/Core Transforms/Combine/CombineFn/task.py	
@@ -50,4 +50,3 @@ with beam.Pipeline() as p:
   (p | beam.Create([10, 20, 50, 70, 90])
      | beam.CombineGlobally(AverageFn())
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Combine/CombineFn/tests.py b/learning/katas/python/Core Transforms/Combine/CombineFn/tests.py
deleted file mode 100644
index 9883983df90..00000000000
--- a/learning/katas/python/Core Transforms/Combine/CombineFn/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['48.0']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Average all the numbers.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Combine/CombineFn/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Combine/CombineFn/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/CombineFn/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml b/learning/katas/python/Core Transforms/Combine/CombineFn/tests/test_task.py
similarity index 66%
copy from learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml
copy to learning/katas/python/Core Transforms/Combine/CombineFn/tests/test_task.py
index 44afe842448..b0908aac0e3 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/CombineFn/tests/test_task.py	
@@ -17,13 +17,17 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1141
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '48.0'
+        self.assertIn(answer, output, "Incorrect output. Average all the numbers.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Combine/Simple Function/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Combine/Simple Function/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Simple Function/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Combine/Simple Function/task-info.yaml b/learning/katas/python/Core Transforms/Combine/Simple Function/task-info.yaml
index 0800d5e8ef9..32624dd941b 100644
--- a/learning/katas/python/Core Transforms/Combine/Simple Function/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Simple Function/task-info.yaml	
@@ -28,5 +28,9 @@ files:
   - offset: 1247
     length: 25
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Combine/Simple Function/task-remote-info.yaml b/learning/katas/python/Core Transforms/Combine/Simple Function/task-remote-info.yaml
deleted file mode 100644
index 21fefa6cf5a..00000000000
--- a/learning/katas/python/Core Transforms/Combine/Simple Function/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755584
-update_date: Sat, 01 Aug 2020 09:41:38 UTC
diff --git a/learning/katas/python/Core Transforms/Combine/Simple Function/task.py b/learning/katas/python/Core Transforms/Combine/Simple Function/task.py
index d7fd80aef84..f10574782bd 100644
--- a/learning/katas/python/Core Transforms/Combine/Simple Function/task.py	
+++ b/learning/katas/python/Core Transforms/Combine/Simple Function/task.py	
@@ -41,4 +41,3 @@ with beam.Pipeline() as p:
   (p | beam.Create([1, 2, 3, 4, 5])
      | beam.CombineGlobally(sum)
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Combine/Simple Function/tests.py b/learning/katas/python/Core Transforms/Combine/Simple Function/tests.py
deleted file mode 100644
index a5465af2d10..00000000000
--- a/learning/katas/python/Core Transforms/Combine/Simple Function/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['15']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Sum all the numbers.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Combine/Simple Function/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Combine/Simple Function/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Simple Function/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Combine/Simple Function/tests/test_task.py
similarity index 65%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Combine/Simple Function/tests/test_task.py
index b4013450572..fd6a10dc20b 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Combine/Simple Function/tests/test_task.py	
@@ -17,13 +17,17 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answer = '15'
+        self.assertIn(answer, output, "Incorrect output. Sum all the numbers with a simple function.")
diff --git a/learning/katas/python/Core Transforms/Combine/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Combine/lesson-remote-info.yaml
deleted file mode 100644
index f778f599f82..00000000000
--- a/learning/katas/python/Core Transforms/Combine/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238430
-update_date: Wed, 19 Jun 2019 09:53:36 UTC
-unit: 210890
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Composite Transform/Composite Transform/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-info.yaml b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-info.yaml
index 9a9cd4f0820..fc9ffd31461 100644
--- a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-info.yaml	
@@ -28,5 +28,9 @@ files:
   - offset: 1502
     length: 27
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-remote-info.yaml b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-remote-info.yaml
deleted file mode 100644
index ac9db3b7bc1..00000000000
--- a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755593
-update_date: Sat, 01 Aug 2020 09:45:35 UTC
diff --git a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task.py b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task.py
index cdda61bf605..4edede23ce8 100644
--- a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task.py	
+++ b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/task.py	
@@ -42,4 +42,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(['1,2,3,4,5', '6,7,8,9,10'])
      | ExtractAndMultiplyNumbers()
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests.py b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests.py
deleted file mode 100644
index f0fa900f8cb..00000000000
--- a/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['10', '20', '30', '40', '50', '60', '70', '80', '90', '100']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Extract the numbers and multiply each by 10.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests/test_task.py
similarity index 61%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests/test_task.py
index b4013450572..4730637b99e 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Composite Transform/Composite Transform/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['10', '20', '30', '40', '50', '60', '70', '80', '90', '100']
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Extract the numbers and multiply each by 10.")
diff --git a/learning/katas/python/Core Transforms/Composite Transform/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Composite Transform/lesson-remote-info.yaml
deleted file mode 100644
index d0e3a1b1f94..00000000000
--- a/learning/katas/python/Core Transforms/Composite Transform/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238436
-update_date: Wed, 19 Jun 2019 09:55:02 UTC
-unit: 210896
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Flatten/Flatten/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Flatten/Flatten/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Flatten/Flatten/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Flatten/Flatten/task-info.yaml b/learning/katas/python/Core Transforms/Flatten/Flatten/task-info.yaml
index 9103c3ed9f5..b801d71446f 100644
--- a/learning/katas/python/Core Transforms/Flatten/Flatten/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Flatten/Flatten/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1320
     length: 63
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Flatten/Flatten/task-remote-info.yaml b/learning/katas/python/Core Transforms/Flatten/Flatten/task-remote-info.yaml
deleted file mode 100644
index 390ba359353..00000000000
--- a/learning/katas/python/Core Transforms/Flatten/Flatten/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755588
-update_date: Sat, 01 Aug 2020 09:41:49 UTC
diff --git a/learning/katas/python/Core Transforms/Flatten/Flatten/task.py b/learning/katas/python/Core Transforms/Flatten/Flatten/task.py
index 74f33b21b1a..656f6fe9a36 100644
--- a/learning/katas/python/Core Transforms/Flatten/Flatten/task.py	
+++ b/learning/katas/python/Core Transforms/Flatten/Flatten/task.py	
@@ -37,4 +37,3 @@ with beam.Pipeline() as p:
   ((wordsStartingWithA, wordsStartingWithB)
       | beam.Flatten()
       | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Flatten/Flatten/tests.py b/learning/katas/python/Core Transforms/Flatten/Flatten/tests.py
deleted file mode 100644
index db32bc65ee9..00000000000
--- a/learning/katas/python/Core Transforms/Flatten/Flatten/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['ball', 'book', 'bow', 'apple', 'ant', 'arrow']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Flatten both PCollection into one.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Flatten/Flatten/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Flatten/Flatten/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Flatten/Flatten/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Flatten/Flatten/tests/test_task.py
similarity index 62%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Flatten/Flatten/tests/test_task.py
index b4013450572..6845a7e2fcf 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Flatten/Flatten/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['ball', 'book', 'bow', 'apple', 'ant', 'arrow']
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Flatten both PCollection into one.")
diff --git a/learning/katas/python/Core Transforms/Flatten/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Flatten/lesson-remote-info.yaml
deleted file mode 100644
index 892a41d50f3..00000000000
--- a/learning/katas/python/Core Transforms/Flatten/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238431
-update_date: Wed, 19 Jun 2019 09:53:56 UTC
-unit: 210891
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/GroupByKey/GroupByKey/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-info.yaml b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-info.yaml
index a19a82c9862..06efafd3eea 100644
--- a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-info.yaml	
@@ -22,8 +22,12 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 1172
+  - offset: 1170
     length: 63
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-remote-info.yaml b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-remote-info.yaml
deleted file mode 100644
index fea74cbc827..00000000000
--- a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755582
-update_date: Sat, 01 Aug 2020 09:41:31 UTC
diff --git a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task.py b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task.py
index 8948087882f..55e78d45227 100644
--- a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task.py	
+++ b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/task.py	
@@ -20,7 +20,7 @@
 #   multifile: false
 #   context_line: 29
 #   categories:
-#     - Combiners
+#     - Combine
 
 import apache_beam as beam
 
@@ -32,4 +32,3 @@ with beam.Pipeline() as p:
      | beam.Map(lambda word: (word[0], word))
      | beam.GroupByKey()
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests.py b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests.py
deleted file mode 100644
index e16fb6cb488..00000000000
--- a/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests.py	
+++ /dev/null
@@ -1,35 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ["('a', ['apple', 'ant'])",
-               "('b', ['ball', 'bear'])",
-               "('c', ['car', 'cheetah'])"]
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Use GroupByKey.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests/test_task.py
similarity index 58%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests/test_task.py
index b4013450572..b0609a34dad 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/GroupByKey/GroupByKey/tests/test_task.py	
@@ -17,13 +17,21 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ["('a', ['apple', 'ant'])",
+                   "('b', ['ball', 'bear'])",
+                   "('c', ['car', 'cheetah'])"]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Create tuples and apply GroupByKey.")
diff --git a/learning/katas/python/Core Transforms/GroupByKey/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/GroupByKey/lesson-remote-info.yaml
deleted file mode 100644
index 6401fb6f657..00000000000
--- a/learning/katas/python/Core Transforms/GroupByKey/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238428
-update_date: Wed, 19 Jun 2019 09:53:00 UTC
-unit: 210888
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/FlatMap/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/FlatMap/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/FlatMap/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Map/FlatMap/task-info.yaml b/learning/katas/python/Core Transforms/Map/FlatMap/task-info.yaml
index 8adaec4d4f0..2aacc672c3c 100644
--- a/learning/katas/python/Core Transforms/Map/FlatMap/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/FlatMap/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1230
     length: 47
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Map/FlatMap/task-remote-info.yaml b/learning/katas/python/Core Transforms/Map/FlatMap/task-remote-info.yaml
deleted file mode 100644
index 3335fbe04af..00000000000
--- a/learning/katas/python/Core Transforms/Map/FlatMap/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755580
-update_date: Sat, 01 Aug 2020 09:41:28 UTC
diff --git a/learning/katas/python/Core Transforms/Map/FlatMap/task.py b/learning/katas/python/Core Transforms/Map/FlatMap/task.py
index f3c3fc2131f..270d23119be 100644
--- a/learning/katas/python/Core Transforms/Map/FlatMap/task.py	
+++ b/learning/katas/python/Core Transforms/Map/FlatMap/task.py	
@@ -32,4 +32,3 @@ with beam.Pipeline() as p:
   (p | beam.Create(['Apache Beam', 'Unified Batch and Streaming'])
      | beam.FlatMap(lambda sentence: sentence.split())
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Map/FlatMap/tests.py b/learning/katas/python/Core Transforms/Map/FlatMap/tests.py
deleted file mode 100644
index e166eae902a..00000000000
--- a/learning/katas/python/Core Transforms/Map/FlatMap/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['Apache', 'Beam', 'Unified', 'Batch', 'and', 'Streaming']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Break each sentence into words.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/FlatMap/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/FlatMap/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/FlatMap/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Map/FlatMap/tests/test_task.py
similarity index 61%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Map/FlatMap/tests/test_task.py
index b4013450572..63c89402636 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/FlatMap/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['Apache', 'Beam', 'Unified', 'Batch', 'and', 'Streaming']
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Break each sentence into words.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/Map/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/Map/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/Map/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Map/Map/task-info.yaml b/learning/katas/python/Core Transforms/Map/Map/task-info.yaml
index a442f52936b..dd2a0e5cf10 100644
--- a/learning/katas/python/Core Transforms/Map/Map/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/Map/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1178
     length: 29
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Map/Map/task-remote-info.yaml b/learning/katas/python/Core Transforms/Map/Map/task-remote-info.yaml
deleted file mode 100644
index e529042ac12..00000000000
--- a/learning/katas/python/Core Transforms/Map/Map/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755579
-update_date: Sat, 01 Aug 2020 09:41:24 UTC
diff --git a/learning/katas/python/Core Transforms/Map/Map/task.py b/learning/katas/python/Core Transforms/Map/Map/task.py
index 14be7492f63..fa911415490 100644
--- a/learning/katas/python/Core Transforms/Map/Map/task.py	
+++ b/learning/katas/python/Core Transforms/Map/Map/task.py	
@@ -31,4 +31,3 @@ with beam.Pipeline() as p:
   (p | beam.Create([10, 20, 30, 40, 50])
      | beam.Map(lambda num: num * 5)
      | LogElements())
-
diff --git a/learning/katas/python/Core Transforms/Map/Map/tests.py b/learning/katas/python/Core Transforms/Map/Map/tests.py
deleted file mode 100644
index 2380fe438df..00000000000
--- a/learning/katas/python/Core Transforms/Map/Map/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['50', '100', '150', '200', '250']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Multiply each element by 5.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/Map/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/Map/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/Map/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Map/Map/tests/test_task.py
similarity index 63%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Map/Map/tests/test_task.py
index b4013450572..a3063378d3e 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/Map/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['50', '100', '150', '200', '250']
+
+        for num in answers:
+            self.assertIn(num, output, "Incorrect output. Multiply each element by 5.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/ParDo OneToMany/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-info.yaml
index 1eb62400af9..48fc545d81b 100644
--- a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-info.yaml	
@@ -23,10 +23,14 @@ files:
   visible: true
   placeholders:
   - offset: 1166
-    length: 63
+    length: 86
     placeholder_text: TODO()
-  - offset: 1319
+  - offset: 1342
     length: 32
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-remote-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-remote-info.yaml
deleted file mode 100644
index eab3ba4ca94..00000000000
--- a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755578
-update_date: Sat, 01 Aug 2020 09:41:21 UTC
diff --git a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task.py b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task.py
index 8d63b568d43..6bf124f33fa 100644
--- a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task.py	
+++ b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/task.py	
@@ -31,7 +31,8 @@ from log_elements import LogElements
 class BreakIntoWordsDoFn(beam.DoFn):
 
     def process(self, element):
-        return element.split()
+        for w in element.split():
+            yield w
 
 
 with beam.Pipeline() as p:
diff --git a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests.py b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests.py
deleted file mode 100644
index c83a7dd7b0c..00000000000
--- a/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['Hello', 'Beam', 'It', 'is', 'awesome']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Break each sentence into words.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests/test_task.py
similarity index 62%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests/test_task.py
index b4013450572..23435d37dd7 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo OneToMany/tests/test_task.py	
@@ -17,13 +17,19 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ['Hello', 'Beam', 'It', 'is', 'awesome']
+
+        for word in answers:
+            self.assertIn(word, output, "Incorrect output. Break each sentence into words.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/ParDo/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Map/ParDo/task-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo/task-info.yaml
index c5353ee3488..da24c8ed7fe 100644
--- a/learning/katas/python/Core Transforms/Map/ParDo/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo/task-info.yaml	
@@ -28,5 +28,9 @@ files:
   - offset: 1270
     length: 31
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Map/ParDo/task-remote-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo/task-remote-info.yaml
deleted file mode 100644
index 3b83a93e972..00000000000
--- a/learning/katas/python/Core Transforms/Map/ParDo/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755577
-update_date: Sat, 01 Aug 2020 09:41:17 UTC
diff --git a/learning/katas/python/Core Transforms/Map/ParDo/tests.py b/learning/katas/python/Core Transforms/Map/ParDo/tests.py
deleted file mode 100644
index a274e68cd35..00000000000
--- a/learning/katas/python/Core Transforms/Map/ParDo/tests.py	
+++ /dev/null
@@ -1,33 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = ['10', '20', '30', '40', '50']
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Multiply each element by 10.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Map/ParDo/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml b/learning/katas/python/Core Transforms/Map/ParDo/tests/test_task.py
similarity index 70%
copy from learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml
copy to learning/katas/python/Core Transforms/Map/ParDo/tests/test_task.py
index 44afe842448..c1c5803bf32 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Smallest/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Map/ParDo/tests/test_task.py	
@@ -17,13 +17,16 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1141
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_output(self):
+        output = get_file_output(path="task.py")
+
+        answers = ['10', '20', '30', '40', '50']
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Multiply each element by 10.")
diff --git a/learning/katas/python/Core Transforms/Map/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Map/lesson-remote-info.yaml
deleted file mode 100644
index 3b52f9f208c..00000000000
--- a/learning/katas/python/Core Transforms/Map/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238427
-update_date: Wed, 19 Jun 2019 09:52:09 UTC
-unit: 210887
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Partition/Partition/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Partition/Partition/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Partition/Partition/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Partition/Partition/task-info.yaml b/learning/katas/python/Core Transforms/Partition/Partition/task-info.yaml
index a12f21f5c63..144dd240da9 100644
--- a/learning/katas/python/Core Transforms/Partition/Partition/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Partition/Partition/task-info.yaml	
@@ -28,5 +28,9 @@ files:
   - offset: 1437
     length: 33
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Partition/Partition/task-remote-info.yaml b/learning/katas/python/Core Transforms/Partition/Partition/task-remote-info.yaml
deleted file mode 100644
index 15bbc9685a6..00000000000
--- a/learning/katas/python/Core Transforms/Partition/Partition/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755589
-update_date: Sat, 01 Aug 2020 09:41:52 UTC
diff --git a/learning/katas/python/Core Transforms/Partition/Partition/task.py b/learning/katas/python/Core Transforms/Partition/Partition/task.py
index 2d560f58078..1139e9c2d7f 100644
--- a/learning/katas/python/Core Transforms/Partition/Partition/task.py	
+++ b/learning/katas/python/Core Transforms/Partition/Partition/task.py	
@@ -43,4 +43,3 @@ with beam.Pipeline() as p:
 
   results[0] | 'Log numbers > 100' >> LogElements(prefix='Number > 100: ')
   results[1] | 'Log numbers <= 100' >> LogElements(prefix='Number <= 100: ')
-
diff --git a/learning/katas/python/Core Transforms/Partition/Partition/tests.py b/learning/katas/python/Core Transforms/Partition/Partition/tests.py
deleted file mode 100644
index d8285aede76..00000000000
--- a/learning/katas/python/Core Transforms/Partition/Partition/tests.py	
+++ /dev/null
@@ -1,42 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    numbers_greater_than_100 = ['110', '150', '250']
-    remaining_numbers = ['1', '2', '3', '4', '5', '100']
-
-    answers = []
-
-    for num in numbers_greater_than_100:
-        answers.append('Number > 100: ' + num)
-
-    for num in remaining_numbers:
-        answers.append('Number <= 100: ' + num)
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Partition the numbers accordingly.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Partition/Partition/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Partition/Partition/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Partition/Partition/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Partition/Partition/tests/test_task.py
similarity index 51%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Partition/Partition/tests/test_task.py
index b4013450572..db24f3b35c1 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Partition/Partition/tests/test_task.py	
@@ -17,13 +17,28 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        numbers_greater_than_100 = ['110', '150', '250']
+        remaining_numbers = ['1', '2', '3', '4', '5', '100']
+
+        answers = []
+
+        for num in numbers_greater_than_100:
+            answers.append('Number > 100: ' + num)
+
+        for num in remaining_numbers:
+            answers.append('Number <= 100: ' + num)
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Partition the numbers accordingly.")
diff --git a/learning/katas/python/Core Transforms/Partition/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Partition/lesson-remote-info.yaml
deleted file mode 100644
index c46be4ad1e3..00000000000
--- a/learning/katas/python/Core Transforms/Partition/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238432
-update_date: Wed, 19 Jun 2019 09:54:12 UTC
-unit: 210892
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Side Input/Side Input/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Side Input/Side Input/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Input/Side Input/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Side Input/Side Input/task-info.yaml b/learning/katas/python/Core Transforms/Side Input/Side Input/task-info.yaml
index b6cbaf782c5..75f86ffde3f 100644
--- a/learning/katas/python/Core Transforms/Side Input/Side Input/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Input/Side Input/task-info.yaml	
@@ -22,11 +22,15 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 1378
-    length: 158
+  - offset: 1376
+    length: 134
     placeholder_text: TODO()
-  - offset: 1984
-    length: 52
+  - offset: 2194
+    length: 72
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Side Input/Side Input/task-remote-info.yaml b/learning/katas/python/Core Transforms/Side Input/Side Input/task-remote-info.yaml
deleted file mode 100644
index 61d0bf1bfc5..00000000000
--- a/learning/katas/python/Core Transforms/Side Input/Side Input/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755590
-update_date: Sat, 01 Aug 2020 09:44:19 UTC
diff --git a/learning/katas/python/Core Transforms/Side Input/Side Input/task.py b/learning/katas/python/Core Transforms/Side Input/Side Input/task.py
index 8b79c9c0908..4c5bd14ae5a 100644
--- a/learning/katas/python/Core Transforms/Side Input/Side Input/task.py	
+++ b/learning/katas/python/Core Transforms/Side Input/Side Input/task.py	
@@ -5,9 +5,9 @@
 #  to you under the Apache License, Version 2.0 (the
 #  "License"); you may not use this file except in compliance
 #  with the License.  You may obtain a copy of the License at
-# 
+#
 #      http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 #  Unless required by applicable law or agreed to in writing, software
 #  distributed under the License is distributed on an "AS IS" BASIS,
 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -38,21 +38,16 @@ class Person:
 
 
 class EnrichCountryDoFn(beam.DoFn):
-
     def process(self, element, cities_to_countries):
-        yield Person(element.name, element.city,
-                     cities_to_countries[element.city])
+      yield Person(element.name, element.city, cities_to_countries[element.city])
 
 
 with beam.Pipeline() as p:
-
-  cities_to_countries = {
-      'Beijing': 'China',
-      'London': 'United Kingdom',
-      'San Francisco': 'United States',
-      'Singapore': 'Singapore',
-      'Sydney': 'Australia'
-  }
+  cities_to_countries = p | "Side input" >> beam.Create([('Beijing', 'China'),
+                                                         ('London', 'United Kingdom'),
+                                                         ('San Francisco', 'United States'),
+                                                         ('Singapore', 'Singapore'),
+                                                         ('Sydney', 'Australia')])
 
   persons = [
       Person('Henry', 'Singapore'),
@@ -63,6 +58,5 @@ with beam.Pipeline() as p:
   ]
 
   (p | beam.Create(persons)
-     | beam.ParDo(EnrichCountryDoFn(), cities_to_countries)
-     | LogElements())
-
+     | beam.ParDo(EnrichCountryDoFn(), beam.pvalue.AsDict(cities_to_countries))
+   | LogElements())
diff --git a/learning/katas/python/Core Transforms/Side Input/Side Input/tests.py b/learning/katas/python/Core Transforms/Side Input/Side Input/tests.py
deleted file mode 100644
index 6171323e0bb..00000000000
--- a/learning/katas/python/Core Transforms/Side Input/Side Input/tests.py	
+++ /dev/null
@@ -1,47 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-# 
-#      http://www.apache.org/licenses/LICENSE-2.0
-# 
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        'Person[Henry,Singapore,Singapore]',
-        'Person[Jane,San Francisco,United States]',
-        'Person[Lee,Beijing,China]',
-        'Person[John,Sydney,Australia]',
-        'Person[Alfred,London,United Kingdom]'
-    ]
-
-    if all(person in output for person in answers):
-        passed()
-    else:
-        failed("Incorrect output. Enrich the Person's country by the city.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Side Input/Side Input/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Side Input/Side Input/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Input/Side Input/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Side Input/Side Input/tests/test_task.py
similarity index 53%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Side Input/Side Input/tests/test_task.py
index b4013450572..c97fd6eadfa 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Input/Side Input/tests/test_task.py	
@@ -17,13 +17,25 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            'Person[Henry,Singapore,Singapore]',
+            'Person[Jane,San Francisco,United States]',
+            'Person[Lee,Beijing,China]',
+            'Person[John,Sydney,Australia]',
+            'Person[Alfred,London,United Kingdom]'
+        ]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Enrich the Person's country by the city.")
diff --git a/learning/katas/python/Core Transforms/Side Input/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Side Input/lesson-remote-info.yaml
deleted file mode 100644
index a8304b37cad..00000000000
--- a/learning/katas/python/Core Transforms/Side Input/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238433
-update_date: Wed, 19 Jun 2019 09:54:25 UTC
-unit: 210893
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Side Output/Side Output/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Side Output/Side Output/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Output/Side Output/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Core Transforms/Side Output/Side Output/task-info.yaml b/learning/katas/python/Core Transforms/Side Output/Side Output/task-info.yaml
index 16f5aef94db..c7ca82353b5 100644
--- a/learning/katas/python/Core Transforms/Side Output/Side Output/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Output/Side Output/task-info.yaml	
@@ -26,7 +26,11 @@ files:
     length: 165
     placeholder_text: TODO()
   - offset: 1526
-    length: 100
+    length: 99
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Core Transforms/Side Output/Side Output/task-remote-info.yaml b/learning/katas/python/Core Transforms/Side Output/Side Output/task-remote-info.yaml
deleted file mode 100644
index 0bd57f0ac4f..00000000000
--- a/learning/katas/python/Core Transforms/Side Output/Side Output/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755591
-update_date: Sat, 01 Aug 2020 09:41:58 UTC
diff --git a/learning/katas/python/Core Transforms/Side Output/Side Output/task.py b/learning/katas/python/Core Transforms/Side Output/Side Output/task.py
index 7c0df9f2a09..515917340c9 100644
--- a/learning/katas/python/Core Transforms/Side Output/Side Output/task.py	
+++ b/learning/katas/python/Core Transforms/Side Output/Side Output/task.py	
@@ -50,4 +50,3 @@ with beam.Pipeline() as p:
 
   results[num_below_100_tag] | 'Log numbers <= 100' >> LogElements(prefix='Number <= 100: ')
   results[num_above_100_tag] | 'Log numbers > 100' >> LogElements(prefix='Number > 100: ')
-
diff --git a/learning/katas/python/Core Transforms/Side Output/Side Output/tests.py b/learning/katas/python/Core Transforms/Side Output/Side Output/tests.py
deleted file mode 100644
index 89b299b4074..00000000000
--- a/learning/katas/python/Core Transforms/Side Output/Side Output/tests.py	
+++ /dev/null
@@ -1,42 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    numbers_below_100 = ['0', '10', '20', '50']
-    numbers_above_100 = ['120', '200']
-
-    answers = []
-
-    for num in numbers_below_100:
-        answers.append('Number <= 100: ' + num)
-
-    for num in numbers_above_100:
-        answers.append('Number > 100: ' + num)
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Output the numbers to the output tags accordingly.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Core Transforms/Side Output/Side Output/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Core Transforms/Side Output/Side Output/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Output/Side Output/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Core Transforms/Side Output/Side Output/tests/test_task.py
similarity index 52%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Core Transforms/Side Output/Side Output/tests/test_task.py
index b4013450572..e69393baa92 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Core Transforms/Side Output/Side Output/tests/test_task.py	
@@ -17,13 +17,28 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        numbers_below_100 = ['0', '10', '20', '50']
+        numbers_above_100 = ['120', '200']
+
+        answers = []
+
+        for num in numbers_below_100:
+            answers.append('Number <= 100: ' + num)
+
+        for num in numbers_above_100:
+            answers.append('Number > 100: ' + num)
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Output the numbers to the output tags accordingly.")
diff --git a/learning/katas/python/Core Transforms/Side Output/lesson-remote-info.yaml b/learning/katas/python/Core Transforms/Side Output/lesson-remote-info.yaml
deleted file mode 100644
index 9dc9d4dc772..00000000000
--- a/learning/katas/python/Core Transforms/Side Output/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238434
-update_date: Wed, 19 Jun 2019 09:54:36 UTC
-unit: 210894
diff --git a/learning/katas/python/Core Transforms/section-remote-info.yaml b/learning/katas/python/Core Transforms/section-remote-info.yaml
deleted file mode 100644
index 51df5671c8c..00000000000
--- a/learning/katas/python/Core Transforms/section-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 85645
-update_date: Thu, 13 Jun 2019 13:29:11 UTC
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Examples/Word Count/Word Count/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Examples/Word Count/Word Count/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Examples/Word Count/Word Count/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Examples/Word Count/Word Count/task-info.yaml b/learning/katas/python/Examples/Word Count/Word Count/task-info.yaml
index 6cc94cba9aa..1e7a4d53169 100644
--- a/learning/katas/python/Examples/Word Count/Word Count/task-info.yaml	
+++ b/learning/katas/python/Examples/Word Count/Word Count/task-info.yaml	
@@ -22,8 +22,12 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 1228
-    length: 147
+  - offset: 1238
+    length: 140
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Examples/Word Count/Word Count/task-remote-info.yaml b/learning/katas/python/Examples/Word Count/Word Count/task-remote-info.yaml
deleted file mode 100644
index 80e087a4f42..00000000000
--- a/learning/katas/python/Examples/Word Count/Word Count/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755604
-update_date: Sat, 01 Aug 2020 09:42:41 UTC
diff --git a/learning/katas/python/Examples/Word Count/Word Count/task.py b/learning/katas/python/Examples/Word Count/Word Count/task.py
index 4184deb0044..320c7520faa 100644
--- a/learning/katas/python/Examples/Word Count/Word Count/task.py	
+++ b/learning/katas/python/Examples/Word Count/Word Count/task.py	
@@ -34,10 +34,7 @@ lines = [
 with beam.Pipeline() as p:
 
   (p | beam.Create(lines)
-
      | beam.FlatMap(lambda sentence: sentence.split())
      | beam.combiners.Count.PerElement()
      | beam.MapTuple(lambda k, v: k + ":" + str(v))
-
      | LogElements())
-
diff --git a/learning/katas/python/Examples/Word Count/Word Count/tests.py b/learning/katas/python/Examples/Word Count/Word Count/tests.py
deleted file mode 100644
index 50a2bdcc3e6..00000000000
--- a/learning/katas/python/Examples/Word Count/Word Count/tests.py	
+++ /dev/null
@@ -1,39 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        'apple:2',
-        'banana:4',
-        'grape:1',
-        'orange:2',
-        'papaya:1'
-    ]
-
-    if all(wordCount in output for wordCount in answers):
-        passed()
-    else:
-        failed('Incorrect output. Count number of each word in "word:count" format.')
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Examples/Word Count/Word Count/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Examples/Word Count/Word Count/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Examples/Word Count/Word Count/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/Examples/Word Count/Word Count/tests/test_task.py
similarity index 57%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/Examples/Word Count/Word Count/tests/test_task.py
index b4013450572..a92feae0214 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/Examples/Word Count/Word Count/tests/test_task.py	
@@ -17,13 +17,25 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            'apple:2',
+            'banana:4',
+            'grape:1',
+            'orange:2',
+            'papaya:1'
+        ]
+
+        for ans in answers:
+            self.assertIn(ans, output, 'Incorrect output. Count number of each word in "word:count" format.')
diff --git a/learning/katas/python/Examples/Word Count/lesson-remote-info.yaml b/learning/katas/python/Examples/Word Count/lesson-remote-info.yaml
deleted file mode 100644
index 0fd14042ca9..00000000000
--- a/learning/katas/python/Examples/Word Count/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238441
-update_date: Wed, 19 Jun 2019 09:57:49 UTC
-unit: 210901
diff --git a/learning/katas/python/Examples/section-remote-info.yaml b/learning/katas/python/Examples/section-remote-info.yaml
deleted file mode 100644
index 121445ed683..00000000000
--- a/learning/katas/python/Examples/section-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 85647
-update_date: Thu, 12 May 2022 13:06:24 UTC
diff --git a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task-info.yaml b/learning/katas/python/IO/Built-in IOs/Built-in IOs/task-info.yaml
deleted file mode 100644
index c164ed0b17c..00000000000
--- a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task-info.yaml	
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#  http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-type: theory
-files:
-- name: task.py
-  visible: true
-- name: tests.py
-  visible: false
diff --git a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task-remote-info.yaml b/learning/katas/python/IO/Built-in IOs/Built-in IOs/task-remote-info.yaml
deleted file mode 100644
index 9c9a16369f2..00000000000
--- a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 1076138
-update_date: Sat, 01 Aug 2020 09:42:32 UTC
diff --git a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task.md b/learning/katas/python/IO/Built-in IOs/Built-in IOs/task.md
deleted file mode 100644
index 692aa4dc343..00000000000
--- a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task.md	
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~     http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-Built-in I/Os
--------------
-
-Beam SDKs provide many out of the box I/O transforms that can be used to read from many different
-sources and write to many different sinks.
-
-See the [Beam-provided I/O Transforms](https://beam.apache.org/documentation/io/built-in/) page
-for a list of the currently available I/O transforms.
-
-**Note:** There is no kata for this task. Please proceed to the next task.
diff --git a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task.py b/learning/katas/python/IO/Built-in IOs/Built-in IOs/task.py
deleted file mode 100644
index b95ceb2dc38..00000000000
--- a/learning/katas/python/IO/Built-in IOs/Built-in IOs/task.py	
+++ /dev/null
@@ -1,21 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-import apache_beam as beam
-
-with beam.Pipeline() as p:
-
-
diff --git a/learning/katas/python/IO/Built-in IOs/Built-in IOs/tests.py b/learning/katas/python/IO/Built-in IOs/Built-in IOs/tests.py
deleted file mode 100644
index 826d5788a40..00000000000
--- a/learning/katas/python/IO/Built-in IOs/Built-in IOs/tests.py	
+++ /dev/null
@@ -1,16 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
diff --git a/learning/katas/python/IO/Built-in IOs/lesson-remote-info.yaml b/learning/katas/python/IO/Built-in IOs/lesson-remote-info.yaml
deleted file mode 100644
index c28a5addf4f..00000000000
--- a/learning/katas/python/IO/Built-in IOs/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238440
-update_date: Wed, 19 Jun 2019 09:57:38 UTC
-unit: 210900
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/IO/TextIO/ReadFromText/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/IO/TextIO/ReadFromText/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/IO/TextIO/ReadFromText/__init__.py
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/IO/TextIO/ReadFromText/task-info.yaml b/learning/katas/python/IO/TextIO/ReadFromText/task-info.yaml
index c89ce4e2370..19be70a30df 100644
--- a/learning/katas/python/IO/TextIO/ReadFromText/task-info.yaml
+++ b/learning/katas/python/IO/TextIO/ReadFromText/task-info.yaml
@@ -22,13 +22,14 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 1100
-    length: 31
+  - offset: 1099
+    length: 75
     placeholder_text: TODO()
-  - offset: 1139
-    length: 41
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
 - name: countries.txt
   visible: true
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
+  visible: false
diff --git a/learning/katas/python/IO/TextIO/ReadFromText/task-remote-info.yaml b/learning/katas/python/IO/TextIO/ReadFromText/task-remote-info.yaml
deleted file mode 100644
index a047a2dba85..00000000000
--- a/learning/katas/python/IO/TextIO/ReadFromText/task-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755602
-update_date: Sat, 01 Aug 2020 09:42:29 UTC
diff --git a/learning/katas/python/IO/TextIO/ReadFromText/task.md b/learning/katas/python/IO/TextIO/ReadFromText/task.md
index 0c4f5fc0a53..c9382dc15c1 100644
--- a/learning/katas/python/IO/TextIO/ReadFromText/task.md
+++ b/learning/katas/python/IO/TextIO/ReadFromText/task.md
@@ -40,3 +40,12 @@ transform and specify the path of the file(s) to be read.
   <a href="https://beam.apache.org/documentation/programming-guide/#pipeline-io-reading-data">
     "Reading input data"</a> section for more information.
 </div>
+
+Built-in I/Os
+-------------
+
+Beam SDKs provide many out of the box I/O transforms that can be used to read from many different
+sources and write to many different sinks.
+
+See the [Beam-provided I/O Transforms](https://beam.apache.org/documentation/io/built-in/) page
+for a list of the currently available I/O transforms.
\ No newline at end of file
diff --git a/learning/katas/python/IO/TextIO/ReadFromText/task.py b/learning/katas/python/IO/TextIO/ReadFromText/task.py
index 1121779328b..067059a3d69 100644
--- a/learning/katas/python/IO/TextIO/ReadFromText/task.py
+++ b/learning/katas/python/IO/TextIO/ReadFromText/task.py
@@ -30,7 +30,5 @@ with beam.Pipeline() as p:
 
   file_path = 'countries.txt'
 
-  (p | beam.io.ReadFromText(file_path)
-     | beam.Map(lambda country: country.upper())
+  (p | beam.io.ReadFromText(file_path) | beam.Map(lambda country: country.upper())
      | LogElements())
-
diff --git a/learning/katas/python/IO/TextIO/ReadFromText/tests.py b/learning/katas/python/IO/TextIO/ReadFromText/tests.py
deleted file mode 100644
index 5a29e43d879..00000000000
--- a/learning/katas/python/IO/TextIO/ReadFromText/tests.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        'AUSTRALIA',
-        'CHINA',
-        'ENGLAND',
-        'FRANCE',
-        'GERMANY',
-        'INDONESIA',
-        'JAPAN',
-        'MEXICO',
-        'SINGAPORE',
-        'UNITED STATES'
-    ]
-
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Convert each country name to uppercase.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/IO/TextIO/ReadFromText/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/IO/TextIO/ReadFromText/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/IO/TextIO/ReadFromText/tests/__init__.py
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml b/learning/katas/python/IO/TextIO/ReadFromText/tests/test_task.py
similarity index 53%
copy from learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml
copy to learning/katas/python/IO/TextIO/ReadFromText/tests/test_task.py
index b4013450572..037ff396abf 100644
--- a/learning/katas/python/Common Transforms/Aggregation/Mean/task-info.yaml	
+++ b/learning/katas/python/IO/TextIO/ReadFromText/tests/test_task.py
@@ -17,13 +17,30 @@
 # under the License.
 #
 
-type: edu
-files:
-- name: task.py
-  visible: true
-  placeholders:
-  - offset: 1156
-    length: 30
-    placeholder_text: TODO()
-- name: tests.py
-  visible: false
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            'AUSTRALIA',
+            'CHINA',
+            'ENGLAND',
+            'FRANCE',
+            'GERMANY',
+            'INDONESIA',
+            'JAPAN',
+            'MEXICO',
+            'SINGAPORE',
+            'UNITED STATES'
+        ]
+
+        for num in answers:
+            self.assertIn(num, output, "Incorrect output. Convert each country name to uppercase.")
diff --git a/learning/katas/python/IO/TextIO/lesson-remote-info.yaml b/learning/katas/python/IO/TextIO/lesson-remote-info.yaml
deleted file mode 100644
index 28cc664b42e..00000000000
--- a/learning/katas/python/IO/TextIO/lesson-remote-info.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238439
-update_date: Wed, 19 Jun 2019 09:57:25 UTC
-unit: 210899
diff --git a/learning/katas/python/IO/section-info.yaml b/learning/katas/python/IO/section-info.yaml
index 1d93752c3a8..0574bd2fde4 100644
--- a/learning/katas/python/IO/section-info.yaml
+++ b/learning/katas/python/IO/section-info.yaml
@@ -19,4 +19,3 @@
 
 content:
 - TextIO
-- Built-in IOs
diff --git a/learning/katas/python/IO/section-remote-info.yaml b/learning/katas/python/IO/section-remote-info.yaml
deleted file mode 100644
index 17618fead58..00000000000
--- a/learning/katas/python/IO/section-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 88017
-update_date: Thu, 13 Jun 2019 14:30:40 UTC
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Introduction/Hello Beam/Hello Beam/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Introduction/Hello Beam/Hello Beam/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Introduction/Hello Beam/Hello Beam/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-info.yaml b/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-info.yaml
index 2e22dceaa32..cb4288bb349 100644
--- a/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-info.yaml	
+++ b/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 1157
     length: 26
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-remote-info.yaml b/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-remote-info.yaml
deleted file mode 100644
index 239fbb88cc2..00000000000
--- a/learning/katas/python/Introduction/Hello Beam/Hello Beam/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 755575
-update_date: Sat, 01 Aug 2020 09:41:14 UTC
diff --git a/learning/katas/python/Introduction/Hello Beam/Hello Beam/task.md b/learning/katas/python/Introduction/Hello Beam/Hello Beam/task.md
index ebf81e09325..7714b777fc4 100644
--- a/learning/katas/python/Introduction/Hello Beam/Hello Beam/task.md	
+++ b/learning/katas/python/Introduction/Hello Beam/Hello Beam/task.md	
@@ -46,4 +46,4 @@ To learn more about Apache Beam, refer to
   Refer to the Beam Programming Guide
   <a href="https://beam.apache.org/documentation/programming-guide/#creating-pcollection-in-memory">
   "Creating a PCollection from in-memory data"</a> section for more information.
-</div>
+</div>
\ No newline at end of file
diff --git a/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests.py b/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests.py
deleted file mode 100644
index 5888f4b475d..00000000000
--- a/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests.py	
+++ /dev/null
@@ -1,34 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    # Remove warning line about docker and Python versions
-    output = [x for x in output if not x.startswith("WARNING")]
-
-    if len(output) == 1 and 'Hello Beam' in output:
-        passed()
-    else:
-        failed('The input element should contain "Hello Beam"')
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Introduction/Hello Beam/Hello Beam/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Common Transforms/Filter/ParDo/tests.py b/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests/test_task.py
similarity index 57%
rename from learning/katas/python/Common Transforms/Filter/ParDo/tests.py
rename to learning/katas/python/Introduction/Hello Beam/Hello Beam/tests/test_task.py
index 0a2cbba3efe..1f10717ed4a 100644
--- a/learning/katas/python/Common Transforms/Filter/ParDo/tests.py	
+++ b/learning/katas/python/Introduction/Hello Beam/Hello Beam/tests/test_task.py	
@@ -14,20 +14,21 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from test_helper import failed, passed, get_file_output, test_is_not_empty
+import unittest
 
+from test_helper import get_file_output, test_is_not_empty
 
-def test_output():
-    output = get_file_output()
 
-    answers = ['1', '3', '5', '7', '9']
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
 
-    if all(num in output for num in answers):
-        passed()
-    else:
-        failed("Incorrect output. Filter out the even numbers.")
+    def test_output(self):
+        output = get_file_output(path='task.py')
 
+        # Remove warning line about docker and Python versions
+        output = [x for x in output if not x.startswith("WARNING")]
+
+        self.assertIn('Hello Beam', output, 'The input element should contain "Hello Beam".')
+        self.assertEqual(1, len(output), 'The output should contain a single element.')
 
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Introduction/Hello Beam/lesson-remote-info.yaml b/learning/katas/python/Introduction/Hello Beam/lesson-remote-info.yaml
deleted file mode 100644
index 50d8ca119b7..00000000000
--- a/learning/katas/python/Introduction/Hello Beam/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 238426
-update_date: Wed, 19 Jun 2019 09:51:26 UTC
-unit: 210886
diff --git a/learning/katas/python/Introduction/section-remote-info.yaml b/learning/katas/python/Introduction/section-remote-info.yaml
deleted file mode 100644
index f1d2fa3f981..00000000000
--- a/learning/katas/python/Introduction/section-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 85644
-update_date: Fri, 31 May 2019 17:58:15 UTC
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Timestamps/Add Timestamps/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Timestamps/Add Timestamps/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Timestamps/Add Timestamps/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task-info.yaml b/learning/katas/python/Streaming/Timestamps/Add Timestamps/task-info.yaml
similarity index 85%
rename from learning/katas/python/Windowing/Adding Timestamp/ParDo/task-info.yaml
rename to learning/katas/python/Streaming/Timestamps/Add Timestamps/task-info.yaml
index 7f4dabc9f21..e9096c14b00 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task-info.yaml	
+++ b/learning/katas/python/Streaming/Timestamps/Add Timestamps/task-info.yaml	
@@ -22,11 +22,15 @@ files:
 - name: task.py
   visible: true
   placeholders:
-  - offset: 2165
-    length: 30
+  - offset: 1450
+    length: 156
     placeholder_text: TODO()
-  - offset: 1451
-    length: 160
+  - offset: 2159
+    length: 30
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task.md b/learning/katas/python/Streaming/Timestamps/Add Timestamps/task.md
similarity index 100%
rename from learning/katas/python/Windowing/Adding Timestamp/ParDo/task.md
rename to learning/katas/python/Streaming/Timestamps/Add Timestamps/task.md
diff --git a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task.py b/learning/katas/python/Streaming/Timestamps/Add Timestamps/task.py
similarity index 99%
rename from learning/katas/python/Windowing/Adding Timestamp/ParDo/task.py
rename to learning/katas/python/Streaming/Timestamps/Add Timestamps/task.py
index 07ebcd2549d..5d9cc63d0ae 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task.py	
+++ b/learning/katas/python/Streaming/Timestamps/Add Timestamps/task.py	
@@ -42,14 +42,12 @@ class Event:
 
 
 class AddTimestampDoFn(beam.DoFn):
-
     def process(self, element, **kwargs):
         unix_timestamp = element.timestamp.timestamp()
         yield window.TimestampedValue(element, unix_timestamp)
 
 
 with beam.Pipeline() as p:
-
   (p | beam.Create([
           Event('1', 'book-order', datetime.datetime(2020, 3, 4, 0, 0, 0, 0, tzinfo=pytz.UTC)),
           Event('2', 'pencil-order', datetime.datetime(2020, 3, 5, 0, 0, 0, 0, tzinfo=pytz.UTC)),
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Timestamps/Add Timestamps/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Timestamps/Add Timestamps/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Timestamps/Add Timestamps/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Streaming/Timestamps/Add Timestamps/tests/test_task.py b/learning/katas/python/Streaming/Timestamps/Add Timestamps/tests/test_task.py
new file mode 100644
index 00000000000..1ea3d5d0016
--- /dev/null
+++ b/learning/katas/python/Streaming/Timestamps/Add Timestamps/tests/test_task.py	
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            "Event(1, book-order, 2020-03-04 00:00:00+00:00), timestamp='2020-03-04T00:00:00Z'",
+            "Event(2, pencil-order, 2020-03-05 00:00:00+00:00), timestamp='2020-03-05T00:00:00Z'",
+            "Event(3, paper-order, 2020-03-06 00:00:00+00:00), timestamp='2020-03-06T00:00:00Z'",
+            "Event(4, pencil-order, 2020-03-07 00:00:00+00:00), timestamp='2020-03-07T00:00:00Z'",
+            "Event(5, book-order, 2020-03-08 00:00:00+00:00), timestamp='2020-03-08T00:00:00Z'"
+        ]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Assign timestamp based on the Event.date.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Timestamps/lesson-info.yaml
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Timestamps/lesson-info.yaml
index 65b69fc6d92..126c1b6fd06 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Timestamps/lesson-info.yaml
@@ -18,4 +18,4 @@
 #
 
 content:
-- ParDo
+- Add Timestamps
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Triggers/Early Triggers/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Triggers/Early Triggers/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Early Triggers/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Triggers/Early Triggers/Early Triggers/generate_event.py b/learning/katas/python/Streaming/Triggers/Early Triggers/generate_event.py
similarity index 100%
rename from learning/katas/python/Triggers/Early Triggers/Early Triggers/generate_event.py
rename to learning/katas/python/Streaming/Triggers/Early Triggers/generate_event.py
diff --git a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task-info.yaml b/learning/katas/python/Streaming/Triggers/Early Triggers/task-info.yaml
similarity index 90%
rename from learning/katas/python/Triggers/Early Triggers/Early Triggers/task-info.yaml
rename to learning/katas/python/Streaming/Triggers/Early Triggers/task-info.yaml
index 697a5811435..03897738407 100644
--- a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Early Triggers/task-info.yaml	
@@ -25,7 +25,11 @@ files:
   - offset: 1545
     length: 431
     placeholder_text: TODO()
-- name: tests.py
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
+  visible: false
+- name: tests/test_task.py
   visible: false
 - name: generate_event.py
   visible: true
diff --git a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task.md b/learning/katas/python/Streaming/Triggers/Early Triggers/task.md
similarity index 97%
rename from learning/katas/python/Triggers/Early Triggers/Early Triggers/task.md
rename to learning/katas/python/Streaming/Triggers/Early Triggers/task.md
index d3e47cc42e6..86ea168a9d6 100644
--- a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task.md	
+++ b/learning/katas/python/Streaming/Triggers/Early Triggers/task.md	
@@ -29,7 +29,7 @@ element is processed.
 
 <div class="hint">
   Use <a href="https://beam.apache.org/releases/pydoc/current/apache_beam.transforms.trigger.html#apache_beam.transforms.trigger.AfterWatermark">
-  withEarlyFirings</a> to set early firing triggers.
+  the early parameter</a> to set early firing triggers.
 </div>
 
 <div class="hint">
diff --git a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task.py b/learning/katas/python/Streaming/Triggers/Early Triggers/task.py
similarity index 100%
rename from learning/katas/python/Triggers/Early Triggers/Early Triggers/task.py
rename to learning/katas/python/Streaming/Triggers/Early Triggers/task.py
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Triggers/Early Triggers/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Triggers/Early Triggers/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Early Triggers/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Streaming/Triggers/Early Triggers/tests/test_task.py b/learning/katas/python/Streaming/Triggers/Early Triggers/tests/test_task.py
new file mode 100644
index 00000000000..943ffbbcc17
--- /dev/null
+++ b/learning/katas/python/Streaming/Triggers/Early Triggers/tests/test_task.py	
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ["1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "0, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)"]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Count the number of events in each 5 seconds window.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Triggers/Event Time Triggers/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Triggers/Event Time Triggers/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Event Time Triggers/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/generate_event.py b/learning/katas/python/Streaming/Triggers/Event Time Triggers/generate_event.py
similarity index 100%
rename from learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/generate_event.py
rename to learning/katas/python/Streaming/Triggers/Event Time Triggers/generate_event.py
diff --git a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task-info.yaml b/learning/katas/python/Streaming/Triggers/Event Time Triggers/task-info.yaml
similarity index 86%
rename from learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task-info.yaml
rename to learning/katas/python/Streaming/Triggers/Event Time Triggers/task-info.yaml
index 51c72230937..6e662582001 100644
--- a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Event Time Triggers/task-info.yaml	
@@ -27,5 +27,11 @@ files:
     placeholder_text: TODO()
 - name: generate_event.py
   visible: true
-- name: tests.py
+- name: __init__.py
   visible: false
+- name: tests/__init__.py
+  visible: false
+- name: tests/test_task.py
+  visible: false
+- name: generate_event.py
+  visible: true
diff --git a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task.md b/learning/katas/python/Streaming/Triggers/Event Time Triggers/task.md
similarity index 100%
rename from learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task.md
rename to learning/katas/python/Streaming/Triggers/Event Time Triggers/task.md
diff --git a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task.py b/learning/katas/python/Streaming/Triggers/Event Time Triggers/task.py
similarity index 100%
rename from learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task.py
rename to learning/katas/python/Streaming/Triggers/Event Time Triggers/task.py
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Triggers/Event Time Triggers/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Triggers/Event Time Triggers/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Event Time Triggers/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Streaming/Triggers/Event Time Triggers/tests/test_task.py b/learning/katas/python/Streaming/Triggers/Event Time Triggers/tests/test_task.py
new file mode 100644
index 00000000000..44adb6e6c37
--- /dev/null
+++ b/learning/katas/python/Streaming/Triggers/Event Time Triggers/tests/test_task.py	
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            "4, window(start=2021-03-01T00:00:00Z, end=2021-03-01T00:00:05Z)",
+            "5, window(start=2021-03-01T00:00:05Z, end=2021-03-01T00:00:10Z)",
+            "5, window(start=2021-03-01T00:00:10Z, end=2021-03-01T00:00:15Z)",
+            "5, window(start=2021-03-01T00:00:15Z, end=2021-03-01T00:00:20Z)",
+            "1, window(start=2021-03-01T00:00:20Z, end=2021-03-01T00:00:25Z)"
+        ]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Count the number of events in each 5 seconds window.")
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Triggers/Window Accumulation Modes/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/generate_event.py b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/generate_event.py
similarity index 100%
rename from learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/generate_event.py
rename to learning/katas/python/Streaming/Triggers/Window Accumulation Modes/generate_event.py
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task-info.yaml b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task-info.yaml
similarity index 86%
rename from learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task-info.yaml
rename to learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task-info.yaml
index 193fec1ab1a..52347031f35 100644
--- a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task-info.yaml	
@@ -25,7 +25,13 @@ files:
   - offset: 1571
     length: 432
     placeholder_text: TODO()
-- name: tests.py
+- name: generate_event.py
+  visible: true
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
+  visible: false
+- name: tests/test_task.py
   visible: false
 - name: generate_event.py
   visible: true
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task.md b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task.md
similarity index 98%
rename from learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task.md
rename to learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task.md
index 9f7dc9ebe2e..e63d0ed35f8 100644
--- a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task.md	
+++ b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task.md	
@@ -36,7 +36,7 @@ element is processed in accumulating mode.
 
 <div class="hint">
   Use <a href="https://beam.apache.org/releases/pydoc/current/apache_beam.transforms.trigger.html#apache_beam.transforms.trigger.AfterWatermark">
-  withEarlyFirings</a> to set early firing triggers.
+  the early parameter</a> to set early firing triggers.
 </div>
 
 <div class="hint">
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task.py b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task.py
similarity index 100%
rename from learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task.py
rename to learning/katas/python/Streaming/Triggers/Window Accumulation Modes/task.py
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/tests/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Triggers/Window Accumulation Modes/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/tests/test_task.py b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/tests/test_task.py
new file mode 100644
index 00000000000..e3741bf2f12
--- /dev/null
+++ b/learning/katas/python/Streaming/Triggers/Window Accumulation Modes/tests/test_task.py	
@@ -0,0 +1,54 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = ["1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "2, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "3, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "4, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "5, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "6, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "7, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "8, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "9, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "10, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "11, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "12, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "13, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "14, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "15, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "16, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "17, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "18, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "19, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
+                   "20, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)"]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Try using a count early trigger, with accumulating mode.")
diff --git a/learning/katas/python/Triggers/section-info.yaml b/learning/katas/python/Streaming/Triggers/lesson-info.yaml
similarity index 96%
rename from learning/katas/python/Triggers/section-info.yaml
rename to learning/katas/python/Streaming/Triggers/lesson-info.yaml
index f62f31674d4..028365a4b8f 100644
--- a/learning/katas/python/Triggers/section-info.yaml
+++ b/learning/katas/python/Streaming/Triggers/lesson-info.yaml
@@ -20,4 +20,4 @@
 content:
 - Event Time Triggers
 - Early Triggers
-- Window Accumulation Mode
+- Window Accumulation Modes
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Windows/Fixed Windows/__init__.py
similarity index 97%
copy from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
copy to learning/katas/python/Streaming/Windows/Fixed Windows/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Windows/Fixed Windows/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task-info.yaml b/learning/katas/python/Streaming/Windows/Fixed Windows/task-info.yaml
similarity index 89%
rename from learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task-info.yaml
rename to learning/katas/python/Streaming/Windows/Fixed Windows/task-info.yaml
index 27fb1ed7144..16f58b5dfef 100644
--- a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task-info.yaml	
+++ b/learning/katas/python/Streaming/Windows/Fixed Windows/task-info.yaml	
@@ -25,5 +25,9 @@ files:
   - offset: 2363
     length: 87
     placeholder_text: TODO()
-- name: tests.py
+- name: tests/test_task.py
+  visible: false
+- name: __init__.py
+  visible: false
+- name: tests/__init__.py
   visible: false
diff --git a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task.md b/learning/katas/python/Streaming/Windows/Fixed Windows/task.md
similarity index 100%
rename from learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task.md
rename to learning/katas/python/Streaming/Windows/Fixed Windows/task.md
diff --git a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task.py b/learning/katas/python/Streaming/Windows/Fixed Windows/task.py
similarity index 99%
rename from learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task.py
rename to learning/katas/python/Streaming/Windows/Fixed Windows/task.py
index 41d19877edd..3f7886c935e 100644
--- a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task.py	
+++ b/learning/katas/python/Streaming/Windows/Fixed Windows/task.py	
@@ -49,4 +49,3 @@ with beam.Pipeline() as p:
      | beam.WindowInto(window.FixedWindows(24*60*60))
      | beam.combiners.Count.PerElement()
      | LogElements(with_window=True))
-
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml b/learning/katas/python/Streaming/Windows/Fixed Windows/tests/__init__.py
similarity index 97%
rename from learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml
rename to learning/katas/python/Streaming/Windows/Fixed Windows/tests/__init__.py
index 65b69fc6d92..30097ef0481 100644
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Windows/Fixed Windows/tests/__init__.py	
@@ -16,6 +16,3 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-
-content:
-- ParDo
diff --git a/learning/katas/python/Streaming/Windows/Fixed Windows/tests/test_task.py b/learning/katas/python/Streaming/Windows/Fixed Windows/tests/test_task.py
new file mode 100644
index 00000000000..b29e2ca811e
--- /dev/null
+++ b/learning/katas/python/Streaming/Windows/Fixed Windows/tests/test_task.py	
@@ -0,0 +1,40 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest
+
+from test_helper import test_is_not_empty, get_file_output
+
+
+class TestCase(unittest.TestCase):
+    def test_not_empty(self):
+        self.assertTrue(test_is_not_empty(), 'The output is empty')
+
+    def test_output(self):
+        output = get_file_output(path='task.py')
+
+        answers = [
+            "('event', 4), window(start=2020-03-01T00:00:00Z, end=2020-03-02T00:00:00Z)",
+            "('event', 2), window(start=2020-03-05T00:00:00Z, end=2020-03-06T00:00:00Z)",
+            "('event', 3), window(start=2020-03-08T00:00:00Z, end=2020-03-09T00:00:00Z)",
+            "('event', 1), window(start=2020-03-10T00:00:00Z, end=2020-03-11T00:00:00Z)"
+        ]
+
+        for ans in answers:
+            self.assertIn(ans, output, "Incorrect output. Count the number of events per 1-day fixed window.")
diff --git a/learning/katas/python/IO/Built-in IOs/lesson-info.yaml b/learning/katas/python/Streaming/Windows/lesson-info.yaml
similarity index 98%
rename from learning/katas/python/IO/Built-in IOs/lesson-info.yaml
rename to learning/katas/python/Streaming/Windows/lesson-info.yaml
index af969f15c1d..17324cb5008 100644
--- a/learning/katas/python/IO/Built-in IOs/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/Windows/lesson-info.yaml
@@ -18,4 +18,4 @@
 #
 
 content:
-- Built-in IOs
+- Fixed Windows
diff --git a/learning/katas/python/Triggers/Event Time Triggers/lesson-info.yaml b/learning/katas/python/Streaming/section-info.yaml
similarity index 95%
rename from learning/katas/python/Triggers/Event Time Triggers/lesson-info.yaml
rename to learning/katas/python/Streaming/section-info.yaml
index 07e520d0800..7b09069fb06 100644
--- a/learning/katas/python/Triggers/Event Time Triggers/lesson-info.yaml	
+++ b/learning/katas/python/Streaming/section-info.yaml
@@ -18,4 +18,6 @@
 #
 
 content:
-- Event Time Triggers
\ No newline at end of file
+- Timestamps
+- Windows
+- Triggers
diff --git a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task-remote-info.yaml b/learning/katas/python/Triggers/Early Triggers/Early Triggers/task-remote-info.yaml
deleted file mode 100644
index 9eb0d3c6e7a..00000000000
--- a/learning/katas/python/Triggers/Early Triggers/Early Triggers/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 1315717712
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
diff --git a/learning/katas/python/Triggers/Early Triggers/Early Triggers/tests.py b/learning/katas/python/Triggers/Early Triggers/Early Triggers/tests.py
deleted file mode 100644
index 238db7092a5..00000000000
--- a/learning/katas/python/Triggers/Early Triggers/Early Triggers/tests.py	
+++ /dev/null
@@ -1,54 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-from datetime import datetime
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-  answers = ["1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "0, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)"]
-
-  output = get_file_output()
-
-  if all(elem in output for elem in answers) and all(elem in answers for elem in output):
-    passed()
-  else:
-    failed("Try using an early trigger with the AfterWatermark trigger.")
-
-
-if __name__ == '__main__':
-  test_is_not_empty()
-  test_output()
diff --git a/learning/katas/python/Triggers/Early Triggers/lesson-remote-info.yaml b/learning/katas/python/Triggers/Early Triggers/lesson-remote-info.yaml
deleted file mode 100644
index 44f72ca4f2e..00000000000
--- a/learning/katas/python/Triggers/Early Triggers/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 415852098
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
-unit: 0
diff --git a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task-remote-info.yaml b/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task-remote-info.yaml
deleted file mode 100644
index 5f00e885248..00000000000
--- a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 825593025
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
diff --git a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/tests.py b/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/tests.py
deleted file mode 100644
index 6af85b26113..00000000000
--- a/learning/katas/python/Triggers/Event Time Triggers/Event Time Triggers/tests.py	
+++ /dev/null
@@ -1,39 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        "4, window(start=2021-03-01T00:00:00Z, end=2021-03-01T00:00:05Z)",
-        "5, window(start=2021-03-01T00:00:05Z, end=2021-03-01T00:00:10Z)",
-        "5, window(start=2021-03-01T00:00:10Z, end=2021-03-01T00:00:15Z)",
-        "5, window(start=2021-03-01T00:00:15Z, end=2021-03-01T00:00:20Z)",
-        "1, window(start=2021-03-01T00:00:20Z, end=2021-03-01T00:00:25Z)"
-    ]
-
-    if all(line in output for line in answers) and all(line in answers for line in output):
-      passed()
-    else:
-      failed("Incorrect output. Count the number of events in each 5 seconds window.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Triggers/Event Time Triggers/lesson-remote-info.yaml b/learning/katas/python/Triggers/Event Time Triggers/lesson-remote-info.yaml
deleted file mode 100644
index 0cd79c49350..00000000000
--- a/learning/katas/python/Triggers/Event Time Triggers/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 1858884960
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
-unit: 0
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task-remote-info.yaml b/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task-remote-info.yaml
deleted file mode 100644
index 53ffc371231..00000000000
--- a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 84386334
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/tests.py b/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/tests.py
deleted file mode 100644
index aa3bd3bf4c7..00000000000
--- a/learning/katas/python/Triggers/Window Accumulation Mode/Window Accumulation Mode/tests.py	
+++ /dev/null
@@ -1,52 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-  answers = ["1, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "2, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "3, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "4, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "5, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "6, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "7, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "8, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "9, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "10, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "11, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "12, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "13, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "14, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "15, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "16, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "17, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "18, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "19, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)",
-             "20, window(start=2021-03-01T00:00:00Z, end=2021-03-02T00:00:00Z)"]
-
-  output = get_file_output()
-
-  if all(elem in output for elem in answers) and all(elem in answers for elem in output):
-    passed()
-  else:
-    failed("Try using a count early trigger, with accumulating mode.")
-
-
-if __name__ == '__main__':
-  test_is_not_empty()
-  test_output()
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/lesson-info.yaml b/learning/katas/python/Triggers/Window Accumulation Mode/lesson-info.yaml
deleted file mode 100644
index 8a260af023d..00000000000
--- a/learning/katas/python/Triggers/Window Accumulation Mode/lesson-info.yaml	
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#  http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-content:
-- Window Accumulation Mode
diff --git a/learning/katas/python/Triggers/Window Accumulation Mode/lesson-remote-info.yaml b/learning/katas/python/Triggers/Window Accumulation Mode/lesson-remote-info.yaml
deleted file mode 100644
index 8ba56333281..00000000000
--- a/learning/katas/python/Triggers/Window Accumulation Mode/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 158109835
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
-unit: 0
diff --git a/learning/katas/python/Triggers/section-remote-info.yaml b/learning/katas/python/Triggers/section-remote-info.yaml
deleted file mode 100644
index 678a0809a0b..00000000000
--- a/learning/katas/python/Triggers/section-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 334072318
-update_date: Thu, 01 Jan 1970 00:00:00 UTC
diff --git a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task-remote-info.yaml b/learning/katas/python/Windowing/Adding Timestamp/ParDo/task-remote-info.yaml
deleted file mode 100644
index 5e6340beda1..00000000000
--- a/learning/katas/python/Windowing/Adding Timestamp/ParDo/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 1124219
-update_date: Sat, 01 Aug 2020 09:45:39 UTC
diff --git a/learning/katas/python/Windowing/Adding Timestamp/ParDo/tests.py b/learning/katas/python/Windowing/Adding Timestamp/ParDo/tests.py
deleted file mode 100644
index 2a2011f707c..00000000000
--- a/learning/katas/python/Windowing/Adding Timestamp/ParDo/tests.py	
+++ /dev/null
@@ -1,39 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        "Event(1, book-order, 2020-03-04 00:00:00+00:00), timestamp='2020-03-04T00:00:00Z'",
-        "Event(2, pencil-order, 2020-03-05 00:00:00+00:00), timestamp='2020-03-05T00:00:00Z'",
-        "Event(3, paper-order, 2020-03-06 00:00:00+00:00), timestamp='2020-03-06T00:00:00Z'",
-        "Event(4, pencil-order, 2020-03-07 00:00:00+00:00), timestamp='2020-03-07T00:00:00Z'",
-        "Event(5, book-order, 2020-03-08 00:00:00+00:00), timestamp='2020-03-08T00:00:00Z'"
-    ]
-
-    if all(line in output for line in answers):
-        passed()
-    else:
-        failed("Incorrect output. Assign timestamp based on the Event.date.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Adding Timestamp/lesson-remote-info.yaml b/learning/katas/python/Windowing/Adding Timestamp/lesson-remote-info.yaml
deleted file mode 100644
index 4f723ad4c0f..00000000000
--- a/learning/katas/python/Windowing/Adding Timestamp/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 316599
-update_date: Mon, 09 Mar 2020 14:33:51 UTC
-unit: 299307
diff --git a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task-remote-info.yaml b/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task-remote-info.yaml
deleted file mode 100644
index 8441b4de6df..00000000000
--- a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/task-remote-info.yaml	
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 1124220
-update_date: Sat, 01 Aug 2020 09:42:37 UTC
diff --git a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/tests.py b/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/tests.py
deleted file mode 100644
index e627fb5f4ec..00000000000
--- a/learning/katas/python/Windowing/Fixed Time Window/Fixed Time Window/tests.py	
+++ /dev/null
@@ -1,38 +0,0 @@
-#   Licensed to the Apache Software Foundation (ASF) under one
-#   or more contributor license agreements.  See the NOTICE file
-#   distributed with this work for additional information
-#   regarding copyright ownership.  The ASF licenses this file
-#   to you under the Apache License, Version 2.0 (the
-#   "License"); you may not use this file except in compliance
-#   with the License.  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-from test_helper import failed, passed, get_file_output, test_is_not_empty
-
-
-def test_output():
-    output = get_file_output()
-
-    answers = [
-        "('event', 4), window(start=2020-03-01T00:00:00Z, end=2020-03-02T00:00:00Z)",
-        "('event', 2), window(start=2020-03-05T00:00:00Z, end=2020-03-06T00:00:00Z)",
-        "('event', 3), window(start=2020-03-08T00:00:00Z, end=2020-03-09T00:00:00Z)",
-        "('event', 1), window(start=2020-03-10T00:00:00Z, end=2020-03-11T00:00:00Z)"
-    ]
-
-    if all(line in output for line in answers):
-        passed()
-    else:
-        failed("Incorrect output. Count the number of events per 1-day fixed window.")
-
-
-if __name__ == '__main__':
-    test_is_not_empty()
-    test_output()
diff --git a/learning/katas/python/Windowing/Fixed Time Window/lesson-remote-info.yaml b/learning/katas/python/Windowing/Fixed Time Window/lesson-remote-info.yaml
deleted file mode 100644
index 53cd347058f..00000000000
--- a/learning/katas/python/Windowing/Fixed Time Window/lesson-remote-info.yaml	
+++ /dev/null
@@ -1,3 +0,0 @@
-id: 316600
-update_date: Mon, 09 Mar 2020 14:34:04 UTC
-unit: 299308
diff --git a/learning/katas/python/Windowing/section-info.yaml b/learning/katas/python/Windowing/section-info.yaml
deleted file mode 100644
index e5121f41dc3..00000000000
--- a/learning/katas/python/Windowing/section-info.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#  http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-content:
-- Adding Timestamp
-- Fixed Time Window
diff --git a/learning/katas/python/Windowing/section-remote-info.yaml b/learning/katas/python/Windowing/section-remote-info.yaml
deleted file mode 100644
index 54aef929c85..00000000000
--- a/learning/katas/python/Windowing/section-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 119488
-update_date: Mon, 09 Mar 2020 14:33:49 UTC
diff --git a/learning/katas/python/course-info.yaml b/learning/katas/python/course-info.yaml
index 02417047729..472cf10f8a5 100644
--- a/learning/katas/python/course-info.yaml
+++ b/learning/katas/python/course-info.yaml
@@ -17,17 +17,22 @@
 # under the License.
 #
 
+type: marketplace
 title: Beam Katas - Python
 language: English
-summary: "This course provides a series of katas to get familiar with Apache Beam.\
-  \ \n\nApache Beam website – https://beam.apache.org/"
+summary: |-
+  This course provides a series of katas to get familiar with Apache Beam.
+
+  Apache Beam website – https://beam.apache.org/
+vendor:
+  name: Beam Katas
 programming_language: Python
-programming_language_version: 3.7
+programming_language_version: 3.0
+environment: unittest
 content:
 - Introduction
 - Core Transforms
 - Common Transforms
 - IO
-- Windowing
+- Streaming
 - Examples
-- Triggers
diff --git a/learning/katas/python/course-remote-info.yaml b/learning/katas/python/course-remote-info.yaml
deleted file mode 100644
index 6c6a7de9196..00000000000
--- a/learning/katas/python/course-remote-info.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-id: 54532
-update_date: Wed, 01 Jul 2020 22:47:08 UTC
diff --git a/learning/katas/python/requirements.txt b/learning/katas/python/requirements.txt
index 4640940b61e..b200fc185fe 100644
--- a/learning/katas/python/requirements.txt
+++ b/learning/katas/python/requirements.txt
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-apache-beam==2.38.0
-apache-beam[test]==2.38.0
+apache-beam==2.41.0
+apache-beam[test]==2.41.0
 
-pytz~=2022.1
\ No newline at end of file
+pytz~=2022.2.1
\ No newline at end of file
diff --git a/learning/katas/python/test_helper.py b/learning/katas/python/test_helper.py
index 3864a2eb454..0ac400a0e1e 100644
--- a/learning/katas/python/test_helper.py
+++ b/learning/katas/python/test_helper.py
@@ -15,25 +15,26 @@
 #   limitations under the License.
 
 import sys
+import subprocess
 
 
 def get_file_text(path):
-    """ Returns file text by path"""
+    """Returns file text by path."""
     file_io = open(path, "r")
     text = file_io.read()
     file_io.close()
     return text
 
 
-def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""):
+def get_file_output(path=sys.argv[-1], arg_string="", encoding="utf-8", ):
     """
-    Returns answer file output
-    :param encoding: to decode output in python3
+    Returns answer file output.
+
     :param path: path of file to execute
+    :param arg_string: arguments to be passed to the script
+    :param encoding: to decode output in python3
     :return: list of strings
     """
-    import subprocess
-
     proc = subprocess.Popen([sys.executable, path], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
     if arg_string:
         for arg in arg_string.split("\n"):
@@ -43,198 +44,12 @@ def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""):
     return list(map(lambda x: str(x.decode(encoding)), proc.communicate()[0].splitlines()))
 
 
-def test_file_importable():
-    """ Tests there is no obvious syntax errors"""
-    path = sys.argv[-1]
-    if not path.endswith(".py"):
-        import os
-
-        parent = os.path.abspath(os.path.join(path, os.pardir))
-        python_files = [f for f in os.listdir(parent) if os.path.isfile(os.path.join(parent, f)) and f.endswith(".py")]
-        for python_file in python_files:
-            if python_file == "tests.py":
-                continue
-            check_importable_path(os.path.join(parent, python_file))
-        return
-    check_importable_path(path)
-
-
-def check_importable_path(path):
-    """ Checks that file is importable.
-        Reports failure otherwise.
-    """
-    saved_input = patch_input()
-    try:
-        import_file(path)
-    except:
-        failed("The file contains syntax errors", test_file_importable.__name__)
-        return
-    finally:
-        revert_input(saved_input)
-
-    passed(test_file_importable.__name__)
-
-
-def patch_input():
-    def mock_fun(_m=""):
-        return "mock"
-
-    if sys.version_info[0] == 3:
-        import builtins
-        save_input = builtins.input
-        builtins.input = mock_fun
-        return save_input
-    elif sys.version_info[0] == 2:
-        import __builtin__
-        save_input = __builtin__.raw_input
-        __builtin__.raw_input = mock_fun
-        __builtin__.input = mock_fun
-        return save_input
-
-
-def revert_input(saved_input):
-    if sys.version_info[0] == 3:
-        import builtins
-        builtins.input = saved_input
-    elif sys.version_info[0] == 2:
-        import __builtin__
-        __builtin__.raw_input = saved_input
-        __builtin__.input = saved_input
-
-
-def import_file(path):
-    """ Returns imported file """
-    if sys.version_info[0] == 2 or sys.version_info[1] < 3:
-        import imp
-
-        return imp.load_source("tmp", path)
-    elif sys.version_info[0] == 3:
-        import importlib.machinery
-
-        return importlib.machinery.SourceFileLoader("tmp", path).load_module("tmp")
-
-
-def import_task_file():
-    """ Returns imported file.
-        Imports file from which check action was run
-    """
-    path = sys.argv[-1]
-    return import_file(path)
-
-
-def test_is_not_empty():
-    """
-        Checks that file is not empty
-    """
+def test_is_not_empty() -> bool:
+    """Checks that file is not empty."""
     path = sys.argv[-1]
     file_text = get_file_text(path)
 
     if len(file_text) > 0:
-        passed()
+        return True
     else:
-        failed("The file is empty. Please, reload the task and try again.")
-
-
-def test_text_equals(text, error_text):
-    """
-        Checks that answer equals text.
-    """
-    path = sys.argv[-1]
-    file_text = get_file_text(path)
-
-    if file_text.strip() == text:
-        passed()
-    else:
-        failed(error_text)
-
-
-def test_answer_placeholders_text_deleted(
-        error_text="Solution has empty answer prompt(s)."):
-    """
-        Checks that all answer placeholders are not empty
-    """
-    windows = get_answer_placeholders()
-
-    for window in windows:
-        if len(window) == 0:
-            failed(error_text)
-            return
-    passed()
-
-
-def set_congratulation_message(message):
-    """ Overrides default 'Congratulations!' message """
-    print("#educational_plugin CONGRATS_MESSAGE " + message)
-
-
-def failed(message="Please, reload the task and try again.", name=None):
-    """ Reports failure """
-    if not name:
-        name = sys._getframe().f_back.f_code.co_name
-    print("#educational_plugin " + name + " FAILED + " + message)
-
-
-def passed(name=None):
-    """ Reports success """
-    if not name:
-        name = sys._getframe().f_back.f_code.co_name
-    print("#educational_plugin " + name + " test OK")
-
-
-def get_answer_placeholders():
-    """
-        Returns all answer placeholders text
-    """
-    prefix = "#educational_plugin_window = "
-    path = sys.argv[-1]
-    import os
-
-    file_name_without_extension = os.path.splitext(path)[0]
-    windows_path = file_name_without_extension + "_windows"
-    windows = []
-    f = open(windows_path, "r")
-    window_text = ""
-    first = True
-    for line in f.readlines():
-        if line.startswith(prefix):
-            if not first:
-                windows.append(window_text.strip())
-            else:
-                first = False
-            window_text = line[len(prefix):]
-        else:
-            window_text += line
-
-    if window_text:
-        windows.append(window_text.strip())
-
-    f.close()
-    return windows
-
-
-def check_samples(samples=()):
-    """
-      Check script output for all samples. Sample is a two element list, where the first is input and
-      the second is output.
-    """
-    for sample in samples:
-        if len(sample) == 2:
-            output = get_file_output(arg_string=str(sample[0]))
-            if "\n".join(output) != sample[1]:
-                failed(
-                    "Test from samples failed: \n \n"
-                    "Input:\n{}"
-                    "\n \n"
-                    "Expected:\n{}"
-                    "\n \n"
-                    "Your result:\n{}".format(str.strip(sample[0]), str.strip(sample[1]), "\n".join(output)))
-                return
-        set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepik server.")
-
-    passed()
-
-
-def run_common_tests(error_text="Please, reload file and try again"):
-    test_is_not_empty()
-    test_answer_placeholders_text_deleted()
-    test_file_importable()
+        return False