|
"""
|
|
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)
|
|
|
|
with pytest.raises(AssertionError, match="Division by zero: denominator"):
|
|
solve_proportion(2, None, 0, 3)
|
|
|
|
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"):
|
|
|
|
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."""
|
|
|
|
percent = percent_of(25, 100)
|
|
|
|
|
|
result = solve_proportion(percent, 100, None, 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)
|
|
|
|
|
|
ratio1 = a / b
|
|
ratio2 = c / d
|
|
assert abs(ratio1 - ratio2) < 1e-15
|
|
|
|
def test_resize_and_scale_workflow(self):
|
|
"""Test resizing and scaling workflow."""
|
|
|
|
width, height = 100, 50
|
|
|
|
|
|
new_width, new_height = resize_dimensions(width, height, 2.0)
|
|
assert new_width == 200.0
|
|
assert new_height == 100.0
|
|
|
|
|
|
original_area = width * height
|
|
new_area = new_width * new_height
|
|
area_ratio = new_area / original_area
|
|
assert area_ratio == 4.0
|
|
|
|
def test_percentage_and_proportion_workflow(self):
|
|
"""Test workflow combining percentages and proportions."""
|
|
|
|
percent = percent_of(15, 60)
|
|
|
|
|
|
result = solve_proportion(percent, 100, 30, None)
|
|
assert result == 120.0
|
|
|
|
def test_scaling_chain(self):
|
|
"""Test chain of scaling operations."""
|
|
value = 100
|
|
|
|
|
|
value = scale_by_ratio(value, 1.5)
|
|
|
|
|
|
value = scale_by_ratio(value, 2/3)
|
|
|
|
|
|
assert abs(value - 100.0) < 1e-15
|
|
|
|
def test_real_world_recipe_scaling(self):
|
|
"""Test real-world recipe scaling scenario."""
|
|
|
|
|
|
original_servings = 4
|
|
new_servings = 6
|
|
original_flour = 2
|
|
|
|
|
|
ratio = new_servings / original_servings
|
|
|
|
|
|
new_flour = scale_by_ratio(original_flour, ratio)
|
|
assert new_flour == 3.0
|
|
|
|
|
|
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."""
|
|
|
|
initial = 1000
|
|
final = 1250
|
|
|
|
|
|
growth_amount = final - initial
|
|
growth_percent = percent_of(growth_amount, initial)
|
|
|
|
|
|
growth_factor = direct_k(initial, final)
|
|
|
|
|
|
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."""
|
|
|
|
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."""
|
|
|
|
with pytest.raises(AssertionError):
|
|
percent_of(10, 0)
|
|
|
|
|
|
result = percent_of(10, 1e-100)
|
|
assert result == 1e103
|
|
|
|
|
|
result = percent_of(1e50, 1e60)
|
|
assert abs(result - 1e-8) < 1e-15
|
|
|
|
def test_floating_point_precision_limits(self):
|
|
"""Test floating point precision limits."""
|
|
|
|
result = scale_by_ratio(1e-300, 1e-300)
|
|
|
|
assert isinstance(result, float)
|
|
|
|
|
|
result = scale_by_ratio(1e100, 1e100)
|
|
|
|
assert isinstance(result, float) |