Skip to content
This repository has been archived by the owner on Dec 5, 2024. It is now read-only.

Commit

Permalink
test: improve test coverage
Browse files Browse the repository at this point in the history
This improves test coverage specifically in the analysis module; plots omitted.
  • Loading branch information
trumully committed Apr 19, 2024
1 parent 4103897 commit 7a083b9
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 60 deletions.
93 changes: 89 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ sphinx-rtd-theme = "^2.0.0"
ghp-import = "^2.1.0"
ruff = "^0.4.0"
hypothesis = "^6.100.1"
pytest-cov = "^5.0.0"

[tool.ruff]
line-length = 88
Expand Down Expand Up @@ -117,6 +118,10 @@ ignore = [
[tool.pytest.ini_options]
minversion = "8.0.0"
testpaths = [ "tests/" ]
addopts = "--cov=src/artipy --cov-report term-missing"

[tool.coverage.run]
omit = ["plots.py"]

[tool.semantic_release]
version_variable = [ "src/artipy/__init__.py:__version__" ]
Expand Down
42 changes: 1 addition & 41 deletions src/artipy/analysis/analyse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import itertools
import math
from decimal import Decimal
from enum import StrEnum, auto

from artipy.artifacts import Artifact
from artipy.artifacts.upgrade_strategy import UPGRADE_STEP
from artipy.stats import SubStat
from artipy.stats.utils import possible_substat_values
from artipy.types import StatType
from artipy.types import RollMagnitude, StatType

ROLL_MULTIPLIERS: dict[int, tuple[float, ...]] = {
1: (0.8, 1.0),
Expand All @@ -20,45 +19,6 @@
}


class RollMagnitude(StrEnum):
"""The roll magnitude of a substat. This is a measure of how much the substat has
been increased in relation to its maximum potential value."""

LOW = auto()
MEDIUM = auto()
HIGH = auto()
MAX = auto()

@property
def magnitude(self) -> Decimal:
"""Get the magnitude of the roll magnitude.
Returns:
Decimal: The magnitude of the roll magnitude.
"""
if self == RollMagnitude.LOW:
return Decimal("0.7")
elif self == RollMagnitude.MEDIUM:
return Decimal("0.8")
elif self == RollMagnitude.HIGH:
return Decimal("0.9")
return Decimal("1.0")

@classmethod
def closest(cls, value: Decimal | float | int) -> "RollMagnitude":
"""The closest roll magnitude to a value.
Args:
value (Decimal | float | int): The value to find the closest roll magnitude
Returns:
RollMagnitude: The closest roll magnitude to the value.
"""
return RollMagnitude(
min(cls, key=lambda x: abs(RollMagnitude(x).magnitude - Decimal(value)))
)


def calculate_substat_roll_value(substat: SubStat) -> Decimal:
"""Calculate the substat roll value. This is the value of the substat divided by the
highest possible value of the substat.
Expand Down
13 changes: 7 additions & 6 deletions src/artipy/analysis/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@

import random

from artipy.artifacts import Artifact, ArtifactBuilder
from artipy.artifacts.utils import choose
from artipy.artifacts import Artifact, ArtifactBuilder, utils
from artipy.types import VALID_MAINSTATS, ArtifactSlot


def create_random_artifact(slot: ArtifactSlot) -> Artifact:
def create_random_artifact(slot: ArtifactSlot, rarity: int = 5) -> Artifact:
"""Create a random artifact.
Args:
slot (artipy.types.ArtifactSlot): The slot of the artifact.
rarity (int, optional): The rarity of the artifact. Defaults to 5.
Returns:
artipy.artifacts.Artifact: The random artifact.
"""

substat_count = 4 if random.random() < 0.2 else 3
max_substats = rarity - 1
substat_count = max(0, max_substats if random.random() < 0.2 else max_substats - 1)
mainstats, mainstat_weights = zip(*VALID_MAINSTATS[slot].items())
return (
ArtifactBuilder()
.with_mainstat(choose(mainstats, mainstat_weights))
.with_rarity(5)
.with_mainstat(utils.choose(mainstats, mainstat_weights))
.with_rarity(rarity)
.with_substats(amount=substat_count)
.with_slot(slot)
.build()
Expand Down
16 changes: 8 additions & 8 deletions src/artipy/artifacts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@


def choose(population: tuple[Any], weights: tuple[float | int]) -> Any:
"""Shortcut function for random.choices to choose an individual item from a
population based on weights.
"""Helper function to choose a random element from a population with weights.
This skips having to do slicing of the result of random.choices.
:param population: The population to choose from.
:type population: tuple[Any]
:param weights: The weights of the population.
:type weights: tuple[float | int]
:return: The chosen item.
:rtype: Any
Args:
population (tuple[Any]): The population to choose from.
weights (tuple[float | int]): The weights of the population.
Returns:
Any: The chosen element.
"""
return random.choices(population, weights)[0]
40 changes: 40 additions & 0 deletions src/artipy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import re
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum, StrEnum, auto
from typing import Iterator

Expand Down Expand Up @@ -65,6 +66,45 @@ def make_artifact_sets() -> Iterator[ArtifactSetData]:
}


class RollMagnitude(StrEnum):
"""The roll magnitude of a substat. This is a measure of how much the substat has
been increased in relation to its maximum potential value."""

LOW = auto()
MEDIUM = auto()
HIGH = auto()
MAX = auto()

@property
def magnitude(self) -> Decimal:
"""Get the magnitude of the roll magnitude.
Returns:
Decimal: The magnitude of the roll magnitude.
"""
if self == RollMagnitude.LOW:
return Decimal("0.7")
elif self == RollMagnitude.MEDIUM:
return Decimal("0.8")
elif self == RollMagnitude.HIGH:
return Decimal("0.9")
return Decimal("1.0")

@classmethod
def closest(cls, value: Decimal | float | int) -> "RollMagnitude":
"""The closest roll magnitude to a value.
Args:
value (Decimal | float | int): The value to find the closest roll magnitude
Returns:
RollMagnitude: The closest roll magnitude to the value.
"""
return RollMagnitude(
min(cls, key=lambda x: abs(RollMagnitude(x).magnitude - Decimal(value)))
)


# ---------- Stat Types ---------- #
class StatType(StrEnum):
"""Enumeration of stat types in Genshin Impact."""
Expand Down
39 changes: 38 additions & 1 deletion tests/test_analysis.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import math
from decimal import Decimal

import artipy.analysis.simulate as simulate
import pytest
from artipy.analysis import (
calculate_artifact_crit_value,
Expand All @@ -11,7 +12,7 @@
)
from artipy.artifacts import Artifact, ArtifactBuilder
from artipy.stats import SubStat
from artipy.types import ArtifactSet, ArtifactSlot, StatType
from artipy.types import ArtifactSet, ArtifactSlot, RollMagnitude, StatType


@pytest.fixture
Expand Down Expand Up @@ -85,3 +86,39 @@ def test_calculate_artifact_crit_value(artifact) -> None:
"""This test verifies the crit value of the artifact"""
crit_value = calculate_artifact_crit_value(artifact)
assert math.isclose(crit_value, Decimal(7.8), rel_tol=1e-2)


def test_create_random_artifact() -> None:
"""This test verifies the creation of a random artifact"""
artifact_a = simulate.create_random_artifact(ArtifactSlot.SANDS)
assert artifact_a.artifact_slot == ArtifactSlot.SANDS
assert artifact_a.rarity == 5

artifact_b = simulate.create_random_artifact(ArtifactSlot.PLUME, 4)
assert artifact_b.artifact_slot == ArtifactSlot.PLUME
assert artifact_b.rarity == 4


def test_upgrade_artifact_to_max(artifact) -> None:
"""This test verifies the upgrade of an artifact to its maximum level"""
artifact = simulate.upgrade_artifact_to_max(artifact)
assert artifact.level == 20


def test_create_multiple_random_artifacts() -> None:
"""This test verifies the creation of multiple random artifacts"""
artifacts = simulate.create_multiple_random_artifacts(5)
assert len(artifacts) == 5
for artifact in artifacts:
assert artifact.artifact_slot in list(ArtifactSlot)
assert artifact.rarity == 5


def test_RollMagnitude() -> None:
"""This test verifies the RollMagnitude class functionality"""
assert RollMagnitude.closest(0.7) == RollMagnitude.LOW
assert RollMagnitude.closest(1) == RollMagnitude.MAX
assert RollMagnitude.closest(0.85) == RollMagnitude.MEDIUM
assert RollMagnitude.closest(0.8) == RollMagnitude.MEDIUM
assert RollMagnitude.closest(0.9) == RollMagnitude.HIGH
assert RollMagnitude.closest(0.94) == RollMagnitude.HIGH

0 comments on commit 7a083b9

Please sign in to comment.