proportio / tests /test_tools.py
grasant's picture
Upload 15 files
a0debed verified
"""
Comprehensive test suite for proportion calculation tools.
Tests all functions with various inputs, edge cases, and error conditions.
"""
import pytest
from proportion_server import (
percent_of, solve_proportion, scale_by_ratio,
direct_k, resize_dimensions
)
class TestPercentOf:
"""Test the percent_of function."""
def test_basic_percentage(self):
"""Test basic percentage calculations."""
assert percent_of(25, 100) == 25.0
assert percent_of(1, 4) == 25.0
assert percent_of(3, 4) == 75.0
def test_zero_part(self):
"""Test with zero part."""
assert percent_of(0, 100) == 0.0
assert percent_of(0, 1) == 0.0
def test_negative_values(self):
"""Test with negative values."""
assert percent_of(-25, 100) == -25.0
assert percent_of(25, -100) == -25.0
assert percent_of(-25, -100) == 25.0
def test_decimal_precision(self):
"""Test decimal precision in calculations."""
result = percent_of(1, 3)
assert abs(result - 33.333333333333336) < 1e-10
def test_large_numbers(self):
"""Test with large numbers."""
assert percent_of(1000000, 4000000) == 25.0
def test_very_small_numbers(self):
"""Test with very small numbers."""
result = percent_of(1e-10, 1e-8)
assert abs(result - 1.0) < 1e-10
def test_zero_whole_error(self):
"""Test assertion error when whole is zero."""
with pytest.raises(AssertionError, match="Division by zero: whole cannot be zero"):
percent_of(10, 0)
def test_negative_whole_edge_cases(self):
"""Test edge cases with negative whole values."""
assert percent_of(10, -5) == -200.0
assert percent_of(-10, -5) == 200.0
class TestSolveProportion:
"""Test the solve_proportion function."""
def test_solve_a(self):
"""Test solving for 'a' in proportion a/b = c/d."""
result = solve_proportion(None, 4, 6, 8)
assert result == 3.0
def test_solve_b(self):
"""Test solving for 'b' in proportion a/b = c/d."""
result = solve_proportion(3, None, 6, 8)
assert result == 4.0
def test_solve_c(self):
"""Test solving for 'c' in proportion a/b = c/d."""
result = solve_proportion(3, 4, None, 8)
assert result == 6.0
def test_solve_d(self):
"""Test solving for 'd' in proportion a/b = c/d."""
result = solve_proportion(3, 4, 6, None)
assert result == 8.0
def test_negative_values(self):
"""Test with negative values."""
result = solve_proportion(-3, 4, 6, None)
assert result == -8.0
def test_decimal_results(self):
"""Test with decimal results."""
result = solve_proportion(1, 3, 2, None)
assert abs(result - 6.0) < 1e-10
def test_multiple_none_error(self):
"""Test assertion error when multiple values are None."""
with pytest.raises(AssertionError, match="Exactly one value must be None"):
solve_proportion(None, None, 6, 8)
with pytest.raises(AssertionError, match="Exactly one value must be None"):
solve_proportion(None, 4, None, 8)
with pytest.raises(AssertionError, match="Exactly one value must be None"):
solve_proportion(None, None, None, None)
def test_no_none_error(self):
"""Test assertion error when no values are None."""
with pytest.raises(AssertionError, match="Exactly one value must be None"):
solve_proportion(3, 4, 6, 8)
def test_zero_denominator_error(self):
"""Test assertion error when denominators are zero."""
with pytest.raises(AssertionError, match="Division by zero: denominator"):
solve_proportion(None, 2, 3, 0) # Solving for a, divides by d=0
with pytest.raises(AssertionError, match="Division by zero: denominator"):
solve_proportion(2, None, 0, 3) # Solving for b, divides by c=0
def test_division_by_zero_calculation(self):
"""Test assertion error for division by zero during calculation."""
with pytest.raises(AssertionError, match="Division by zero: denominator"):
# This would require dividing by zero in calculation
solve_proportion(None, 4, 0, 0)
def test_large_numbers(self):
"""Test with large numbers."""
result = solve_proportion(1000000, 2000000, 500000, None)
assert result == 1000000.0
def test_very_small_numbers(self):
"""Test with very small numbers."""
result = solve_proportion(1e-10, 2e-10, 5e-11, None)
assert abs(result - 1e-10) < 1e-20
class TestScaleByRatio:
"""Test the scale_by_ratio function."""
def test_positive_scaling(self):
"""Test scaling with positive ratios."""
assert scale_by_ratio(10, 2.5) == 25.0
assert scale_by_ratio(100, 0.5) == 50.0
def test_negative_values(self):
"""Test scaling with negative values."""
assert scale_by_ratio(-10, 2.0) == -20.0
assert scale_by_ratio(10, -2.0) == -20.0
assert scale_by_ratio(-10, -2.0) == 20.0
def test_zero_scaling(self):
"""Test scaling with zero ratio."""
assert scale_by_ratio(100, 0) == 0.0
def test_zero_value(self):
"""Test scaling zero value."""
assert scale_by_ratio(0, 5.0) == 0.0
def test_identity_scaling(self):
"""Test scaling with ratio of 1."""
assert scale_by_ratio(42, 1.0) == 42.0
def test_fractional_scaling(self):
"""Test scaling with fractional ratios."""
assert scale_by_ratio(9, 1/3) == 3.0
def test_large_numbers(self):
"""Test scaling with large numbers."""
result = scale_by_ratio(1e6, 1e-3)
assert result == 1000.0
def test_very_small_numbers(self):
"""Test scaling with very small numbers."""
result = scale_by_ratio(1e-10, 1e10)
assert result == 1.0
def test_precision_edge_cases(self):
"""Test precision with edge cases."""
result = scale_by_ratio(1/3, 3)
assert abs(result - 1.0) < 1e-15
class TestDirectK:
"""Test the direct_k function."""
def test_positive_values(self):
"""Test with positive values."""
assert direct_k(5, 15) == 3.0
assert direct_k(2, 10) == 5.0
def test_negative_values(self):
"""Test with negative values."""
assert direct_k(-4, 12) == -3.0
assert direct_k(4, -12) == -3.0
assert direct_k(-4, -12) == 3.0
def test_zero_y(self):
"""Test with zero y value."""
assert direct_k(5, 0) == 0.0
def test_zero_x_error(self):
"""Test assertion error when x is zero."""
with pytest.raises(AssertionError, match="Division by zero: x cannot be zero"):
direct_k(0, 10)
def test_decimal_precision(self):
"""Test decimal precision."""
result = direct_k(3, 1)
assert abs(result - 0.3333333333333333) < 1e-15
def test_large_numbers(self):
"""Test with large numbers."""
assert direct_k(1e6, 2e6) == 2.0
def test_very_small_numbers(self):
"""Test with very small numbers."""
result = direct_k(1e-10, 5e-10)
assert abs(result - 5.0) < 1e-10
def test_fractional_results(self):
"""Test fractional results."""
result = direct_k(7, 2)
assert abs(result - 2/7) < 1e-15
class TestResizeDimensions:
"""Test the resize_dimensions function."""
def test_uniform_scaling(self):
"""Test uniform scaling of dimensions."""
result = resize_dimensions(100, 50, 2.0)
assert result == (200.0, 100.0)
def test_zero_dimensions(self):
"""Test with zero dimensions."""
result = resize_dimensions(0, 0, 3.0)
assert result == (0.0, 0.0)
result = resize_dimensions(100, 0, 2.0)
assert result == (200.0, 0.0)
def test_identity_scaling(self):
"""Test scaling with factor of 1."""
result = resize_dimensions(100, 50, 1.0)
assert result == (100.0, 50.0)
def test_large_scaling(self):
"""Test with large scale factors."""
result = resize_dimensions(10, 20, 100.0)
assert result == (1000.0, 2000.0)
def test_small_scaling(self):
"""Test with small scale factors."""
result = resize_dimensions(100, 200, 0.1)
assert result == (10.0, 20.0)
def test_decimal_dimensions(self):
"""Test with decimal dimensions."""
result = resize_dimensions(10.5, 20.7, 2.0)
assert result == (21.0, 41.4)
def test_negative_dimensions_error(self):
"""Test assertion error with negative dimensions."""
with pytest.raises(AssertionError, match="Width must be non-negative"):
resize_dimensions(-100, 50, 2.0)
with pytest.raises(AssertionError, match="Height must be non-negative"):
resize_dimensions(100, -50, 2.0)
with pytest.raises(AssertionError, match="Width must be non-negative"):
resize_dimensions(-100, -50, 2.0)
def test_zero_scale_error(self):
"""Test assertion error with zero scale factor."""
with pytest.raises(AssertionError, match="Scale factor must be positive"):
resize_dimensions(100, 50, 0)
def test_negative_scale_error(self):
"""Test assertion error with negative scale factor."""
with pytest.raises(AssertionError, match="Scale factor must be positive"):
resize_dimensions(100, 50, -1.5)
with pytest.raises(AssertionError, match="Scale factor must be positive"):
resize_dimensions(100, 50, -0.1)
def test_very_large_dimensions(self):
"""Test with very large dimensions."""
result = resize_dimensions(1e6, 1e7, 0.001)
assert result == (1000.0, 10000.0)
def test_precision_edge_cases(self):
"""Test precision edge cases."""
result = resize_dimensions(1/3, 2/3, 3.0)
expected_width = (1/3) * 3.0
expected_height = (2/3) * 3.0
assert abs(result[0] - expected_width) < 1e-15
assert abs(result[1] - expected_height) < 1e-15
class TestIntegration:
"""Integration tests combining multiple functions."""
def test_chained_calculations(self):
"""Test chaining multiple calculations."""
# Start with a percentage
percent = percent_of(25, 100) # 25%
# Use result in proportion
result = solve_proportion(percent, 100, None, 200) # 25/100 = x/200
assert result == 50.0
def test_proportion_verification(self):
"""Test proportion verification using cross multiplication."""
a, b, c = 3, 4, 6
d = solve_proportion(a, b, c, None)
# Verify: a/b should equal c/d
ratio1 = a / b
ratio2 = c / d
assert abs(ratio1 - ratio2) < 1e-15
def test_resize_and_scale_workflow(self):
"""Test resizing and scaling workflow."""
# Original dimensions
width, height = 100, 50
# Resize by factor of 2
new_width, new_height = resize_dimensions(width, height, 2.0)
assert new_width == 200.0
assert new_height == 100.0
# Scale the area by ratio
original_area = width * height
new_area = new_width * new_height
area_ratio = new_area / original_area
assert area_ratio == 4.0 # 2^2 = 4
def test_percentage_and_proportion_workflow(self):
"""Test workflow combining percentages and proportions."""
# What percentage is 15 of 60?
percent = percent_of(15, 60) # 25%
# If 25% of some number is 30, what's the number?
result = solve_proportion(percent, 100, 30, None)
assert result == 120.0
def test_scaling_chain(self):
"""Test chain of scaling operations."""
value = 100
# Scale by 1.5
value = scale_by_ratio(value, 1.5) # 150
# Scale by 2/3
value = scale_by_ratio(value, 2/3) # 100
# Should be back to original
assert abs(value - 100.0) < 1e-15
def test_real_world_recipe_scaling(self):
"""Test real-world recipe scaling scenario."""
# Original recipe serves 4, we want to serve 6
# Original calls for 2 cups flour
original_servings = 4
new_servings = 6
original_flour = 2
# Find scaling ratio
ratio = new_servings / original_servings # 1.5
# Scale flour amount
new_flour = scale_by_ratio(original_flour, ratio)
assert new_flour == 3.0
# Verify using proportion
flour_check = solve_proportion(original_flour, original_servings, None, new_servings)
assert flour_check == 3.0
def test_financial_calculation_workflow(self):
"""Test financial calculation workflow."""
# Investment grows from $1000 to $1250
initial = 1000
final = 1250
# What's the growth percentage?
growth_amount = final - initial # 250
growth_percent = percent_of(growth_amount, initial) # 25%
# Find the growth factor
growth_factor = direct_k(initial, final) # 1.25
# Verify by scaling
verification = scale_by_ratio(initial, growth_factor)
assert verification == final
class TestErrorHandling:
"""Test error handling and edge cases."""
def test_type_validation_errors(self):
"""Test that functions handle type validation properly."""
# These should work with int inputs (converted to float)
assert percent_of(1, 4) == 25.0
assert solve_proportion(1, 2, 3, None) == 6.0
assert scale_by_ratio(10, 2) == 20.0
assert direct_k(2, 6) == 3.0
assert resize_dimensions(10, 20, 2) == (20.0, 40.0)
def test_boundary_conditions(self):
"""Test mathematical boundary conditions."""
# Actual zero denominators should raise error
with pytest.raises(AssertionError):
percent_of(10, 0)
# Very small but non-zero numbers should work
result = percent_of(10, 1e-100)
assert result == 1e103
# Large numbers should work
result = percent_of(1e50, 1e60)
assert abs(result - 1e-8) < 1e-15
def test_floating_point_precision_limits(self):
"""Test floating point precision limits."""
# Very small numbers
result = scale_by_ratio(1e-300, 1e-300)
# Should not raise error, might be 0.0 due to underflow
assert isinstance(result, float)
# Very large numbers
result = scale_by_ratio(1e100, 1e100)
# Should not raise error, might be inf due to overflow
assert isinstance(result, float)