sw6 testing hands on
This commit is contained in:
parent
5d0aa9c876
commit
f089317e86
@ -13,6 +13,6 @@ repos:
|
|||||||
- id: ruff # Linting
|
- id: ruff # Linting
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: stable
|
rev: 26.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black # formatting
|
- id: black # formatting
|
||||||
|
|||||||
6
src/exercises/pricing.py
Normal file
6
src/exercises/pricing.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
def discount_price(price: float, discount: float) -> float:
|
||||||
|
if price < 0:
|
||||||
|
raise ValueError("price cannot be negative")
|
||||||
|
if not 0 <= discount <= 100:
|
||||||
|
raise ValueError("discount must be between 0 and 100")
|
||||||
|
return price - (price * discount / 100)
|
||||||
0
src/testing/__init__.py
Normal file
0
src/testing/__init__.py
Normal file
16
src/testing/bmi.py
Normal file
16
src/testing/bmi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Task: Schreibt für die folgende Aufgabe einige Unit-Test. Nutzt dazu für den Happy-Path einen parametrisierten Test für
|
||||||
|
# einige valide Inputs und Outputs
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_bmi(weight_kg: float, height_m: float) -> float:
|
||||||
|
"""
|
||||||
|
Berechnet den Body Mass Index (BMI).
|
||||||
|
BMI = Gewicht (kg) / Grösse (m)^2
|
||||||
|
"""
|
||||||
|
if weight_kg <= 0 or height_m <= 0:
|
||||||
|
raise ValueError("Gewicht und Grösse müssen positiv sein.")
|
||||||
|
return weight_kg / (height_m**2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(calculate_bmi(weight_kg=72, height_m=1.84))
|
||||||
29
src/testing/catching_exeptions.py
Normal file
29
src/testing/catching_exeptions.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# TASK: Schreibe sinnvolle Tests für die folgende Funktion
|
||||||
|
# Schreibe Tests für die korrekte Struktur des Outputs der Funktion
|
||||||
|
# Teste, ob bei Übergabe von 'falschem' Parameter data eine Exception geworfen wird
|
||||||
|
|
||||||
|
# https://docs.python.org/3/library/exceptions.html
|
||||||
|
# Passing arguments of the wrong type (e.g. passing a list when an int is expected) should result in a TypeError,
|
||||||
|
# but passing arguments with the wrong value (e.g. a number outside expected boundaries) should result in a ValueError.
|
||||||
|
|
||||||
|
|
||||||
|
def double_integers(data: list[int]) -> list[int]:
|
||||||
|
"""Doubles a list of given integers
|
||||||
|
|
||||||
|
:param data: list of integers
|
||||||
|
:return: list of doubled integers
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
raise TypeError("data must be a list")
|
||||||
|
if not all([isinstance(i, int) for i in data]):
|
||||||
|
raise TypeError("data may contain only integers")
|
||||||
|
|
||||||
|
return [i * 2 for i in data]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
data = [1, 2, 3]
|
||||||
|
result = double_integers(data)
|
||||||
|
print(result)
|
||||||
16
src/testing/kata_list_filtering.py
Normal file
16
src/testing/kata_list_filtering.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# https://www.codewars.com/kata/53dbd5315a3c69eed20002dd
|
||||||
|
# level: 7 kyu
|
||||||
|
|
||||||
|
# In this kata you will create a function that takes a list of non-negative integers and strings and returns a new
|
||||||
|
# list with the strings filtered out.
|
||||||
|
#
|
||||||
|
# Example
|
||||||
|
# filter_list([1,2,'a','b']) == [1,2]
|
||||||
|
# filter_list([1,'a','b',0,15]) == [1,0,15]
|
||||||
|
# filter_list([1,2,'aasf','1','123',123]) == [1,2,123]
|
||||||
|
|
||||||
|
|
||||||
|
def filter_list(input_list):
|
||||||
|
if not isinstance(input_list, list):
|
||||||
|
return []
|
||||||
|
return [i for i in input_list if not isinstance(i, bool) and isinstance(i, int)]
|
||||||
41
src/testing/pandas_filter_rows.py
Normal file
41
src/testing/pandas_filter_rows.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# https://www.codewars.com/kata/5ea2baed9345eb001e8ce394
|
||||||
|
# 7 kyu
|
||||||
|
|
||||||
|
# Input parameters
|
||||||
|
|
||||||
|
# dataframe: pandas.DataFrame object
|
||||||
|
# col: target column
|
||||||
|
# func: filter function
|
||||||
|
|
||||||
|
# Task
|
||||||
|
# Your function must return a new pandas.DataFrame object with the same columns as the original input. However,
|
||||||
|
# include only the rows whose cell values in the designated column evaluate to False by func.
|
||||||
|
#
|
||||||
|
# Input DataFrame will never be empty. The target column will always be one of the dataframe columns. Filter function
|
||||||
|
# will be a valid one. Index value must remain the same.
|
||||||
|
|
||||||
|
# REMARK: leichte Modifikation -> es wird ausgegeben, was in der Funktion definiert wurde (True) und nicht umgekehrt.
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def filter_dataframe(df, col, func):
|
||||||
|
if col not in df.columns:
|
||||||
|
raise ValueError(f"Column '{col}' is not present in the DataFrame.")
|
||||||
|
mask = df[col].apply(func)
|
||||||
|
return df[mask]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
df = pd.DataFrame(
|
||||||
|
{
|
||||||
|
"A": list(range(0, 10)),
|
||||||
|
"B": list(range(-5, 5)),
|
||||||
|
"C": list(range(-2, 8)),
|
||||||
|
"D": list(range(10, 20)),
|
||||||
|
"E": list(range(-20, -10)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
print(filter_dataframe(df, "A", lambda x: x >= 4))
|
||||||
18
src/testing/vowel_count.py
Normal file
18
src/testing/vowel_count.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# https://www.codewars.com/kata/54ff3102c1bad923760001f3
|
||||||
|
# level: 7 kyu
|
||||||
|
|
||||||
|
# Return the number (count) of vowels in the given string.
|
||||||
|
# We will consider a, e, i, o, u as vowels for this Kata (but not y).
|
||||||
|
# The input string will only consist of lower case letters and/or spaces.
|
||||||
|
|
||||||
|
|
||||||
|
def get_count(inputStr):
|
||||||
|
num_vowels = 0
|
||||||
|
for char in inputStr:
|
||||||
|
if char in "aeiouAEIOU":
|
||||||
|
num_vowels = num_vowels + 1
|
||||||
|
return num_vowels
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(get_count("Hello World"))
|
||||||
28
tests/exercises/test_pricing.py
Normal file
28
tests/exercises/test_pricing.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.exercises.pricing import discount_price
|
||||||
|
|
||||||
|
|
||||||
|
def test_discount_price_happy_path():
|
||||||
|
result = discount_price(100.0, 20.0)
|
||||||
|
assert result == 80.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_discount_price_edge_case_1():
|
||||||
|
result = discount_price(100.0, 100.0)
|
||||||
|
assert result == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_discount_price_edge_case_2():
|
||||||
|
result = discount_price(100.0, 0.0)
|
||||||
|
assert result == 100.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_discount_price_negative_price():
|
||||||
|
with pytest.raises(ValueError, match="price cannot be negative"):
|
||||||
|
discount_price(-1.0, 20.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_discount_price_invalid_discount():
|
||||||
|
with pytest.raises(ValueError, match="discount must be between 0 and 100"):
|
||||||
|
discount_price(100.0, 120.0)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from src.moduleA import addition
|
from src.exercises.moduleA import addition
|
||||||
|
|
||||||
|
|
||||||
def test_a():
|
def test_a():
|
||||||
|
|||||||
25
tests/testing/test_catching_exceptions.py
Normal file
25
tests/testing/test_catching_exceptions.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import pytest
|
||||||
|
from testing.catching_exeptions import double_integers
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data, expected_error",
|
||||||
|
[
|
||||||
|
(123, "data must be a list"),
|
||||||
|
([1, "2", 3, 4, 5], "data may contain only integers"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_double_integers_unhappy_path(data, expected_error) -> None:
|
||||||
|
with pytest.raises(TypeError, match=expected_error):
|
||||||
|
double_integers(data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data, expected_result",
|
||||||
|
[
|
||||||
|
([1, 2, 3, 4, 5], [2, 4, 6, 8, 10]),
|
||||||
|
([0, -1, -2, -3, -4, -5], [0, -2, -4, -6, -8, -10]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_double_integers_happy_path(data, expected_result) -> None:
|
||||||
|
assert double_integers(data) == expected_result
|
||||||
18
tests/testing/test_kata_list_filtering.py
Normal file
18
tests/testing/test_kata_list_filtering.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import pytest
|
||||||
|
from testing.kata_list_filtering import filter_list
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"input_list,expected_result",
|
||||||
|
[
|
||||||
|
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
|
||||||
|
([], []),
|
||||||
|
([1 / 2, 1, "2", 3, "4", 5], [1, 3, 5]),
|
||||||
|
(["1", "2", "3", "4", "5"], []),
|
||||||
|
([True, False], []),
|
||||||
|
(1, []),
|
||||||
|
("asd123", []),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_filter_list(input_list, expected_result) -> None:
|
||||||
|
assert filter_list(input_list) == expected_result
|
||||||
115
tests/testing/test_pandas_filter_rows.py
Normal file
115
tests/testing/test_pandas_filter_rows.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import pytest
|
||||||
|
import pandas as pd
|
||||||
|
from testing.pandas_filter_rows import filter_dataframe
|
||||||
|
|
||||||
|
# --- Fixtures ---
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_df():
|
||||||
|
return pd.DataFrame(
|
||||||
|
{
|
||||||
|
"age": [10, 25, 35, 45],
|
||||||
|
"name": ["Alice", "Bob", "Charlie", "Diana"],
|
||||||
|
"score": [88.5, 92.0, 70.0, 55.5],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Happy path tests ---
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_numeric_column(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "age", lambda x: x > 20)
|
||||||
|
assert list(result["age"]) == [25, 35, 45]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_returns_dataframe(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "age", lambda x: x > 0)
|
||||||
|
assert isinstance(result, pd.DataFrame)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_string_column(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "name", lambda x: x.startswith("A"))
|
||||||
|
assert list(result["name"]) == ["Alice"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_float_column(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "score", lambda x: x >= 88.5)
|
||||||
|
assert list(result["score"]) == [88.5, 92.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_preserves_all_columns(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "age", lambda x: x == 25)
|
||||||
|
assert list(result.columns) == ["age", "name", "score"]
|
||||||
|
assert result.iloc[0]["name"] == "Bob"
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_preserves_original_index(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "age", lambda x: x > 30)
|
||||||
|
assert list(result.index) == [2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_does_not_mutate_original(sample_df):
|
||||||
|
original_len = len(sample_df)
|
||||||
|
filter_dataframe(sample_df, "age", lambda x: x > 20)
|
||||||
|
assert len(sample_df) == original_len
|
||||||
|
|
||||||
|
|
||||||
|
# --- Edge cases ---
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_all_rows_pass(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "age", lambda x: x > 0)
|
||||||
|
assert len(result) == len(sample_df)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_no_rows_pass(sample_df):
|
||||||
|
result = filter_dataframe(sample_df, "age", lambda x: x > 1000)
|
||||||
|
assert len(result) == 0
|
||||||
|
assert list(result.columns) == ["age", "name", "score"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_on_empty_dataframe():
|
||||||
|
empty_df = pd.DataFrame({"age": [], "name": []})
|
||||||
|
result = filter_dataframe(empty_df, "age", lambda x: x > 10)
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_with_none_values():
|
||||||
|
df = pd.DataFrame({"value": [1, None, 3, None]})
|
||||||
|
result = filter_dataframe(df, "value", lambda x: x is not None and x > 1)
|
||||||
|
assert list(result["value"]) == [3.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_single_row_match():
|
||||||
|
df = pd.DataFrame({"x": [42]})
|
||||||
|
result = filter_dataframe(df, "x", lambda x: x == 42)
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_single_row_no_match():
|
||||||
|
df = pd.DataFrame({"x": [42]})
|
||||||
|
result = filter_dataframe(df, "x", lambda x: x == 0)
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
# --- Error handling ---
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_column_raises_value_error(sample_df):
|
||||||
|
with pytest.raises(ValueError, match="Column 'missing' is not present"):
|
||||||
|
filter_dataframe(sample_df, "missing", lambda x: x > 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_column_error_message_includes_column_name(sample_df):
|
||||||
|
with pytest.raises(ValueError, match="nonexistent"):
|
||||||
|
filter_dataframe(sample_df, "nonexistent", lambda x: True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_func_exception_propagates(sample_df):
|
||||||
|
def bad_func(x):
|
||||||
|
raise RuntimeError("Intentional error")
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="Intentional error"):
|
||||||
|
filter_dataframe(sample_df, "age", bad_func)
|
||||||
Loading…
x
Reference in New Issue
Block a user