Skip to content

Commit 3531cad

Browse files
grayfox88lucsorel
authored andcommitted
Add test cases to test method extraction.
Add test_py2puml_with_methods to test py2puml on class containing methods. Refactor test_py2puml_with_subdomain by moving expected result to file.
1 parent f6cdec3 commit 3531cad

File tree

8 files changed

+129
-36
lines changed

8 files changed

+129
-36
lines changed

py2puml/asserts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def assert_py2puml_is_stringio(domain_path: str, domain_module: str, expected_co
2020
def assert_multilines(actual_multilines: List[str], expected_multilines: Iterable[str]):
2121
line_index = 0
2222
for line_index, (actual_line, expected_line) in enumerate(zip(actual_multilines, expected_multilines)):
23+
# print(actual_line)
2324
assert actual_line == expected_line, f'actual and expected contents have changed at line {line_index + 1}: {actual_line=}, {expected_line=}'
2425

2526
assert line_index + 1 == len(actual_multilines), f'actual and expected diagrams have {line_index + 1} lines'

py2puml/domain/umlclass.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ class UmlMethod(object):
1919
@dataclass
2020
class UmlClass(UmlItem):
2121
attributes: List[UmlAttribute]
22+
# TODO move to UmlItem?
2223
methods: List[UmlMethod]
2324
is_abstract: bool = False

py2puml/inspection/inspectclass.py

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ def handle_inheritance_relation(
2929
)
3030

3131
def inspect_static_attributes(
32-
class_type_fqn: str,
33-
definition_attrs: List[UmlAttribute],
3432
class_type: Type,
33+
class_type_fqn: str,
3534
root_module_name: str,
35+
domain_items_by_fqn: Dict[str, UmlItem],
3636
domain_relations: List[UmlRelation]
3737
) -> List[UmlAttribute]:
3838
'''
@@ -46,7 +46,8 @@ def inspect_static_attributes(
4646
name=class_type.__name__,
4747
fqn=class_type_fqn,
4848
attributes=definition_attrs,
49-
is_abstract=isabstract(class_type)
49+
is_abstract=isabstract(class_type),
50+
methods=[]
5051
)
5152
domain_items_by_fqn[class_type_fqn] = uml_class
5253
# investigate_domain_definition(class_type)
@@ -91,49 +92,33 @@ def inspect_static_attributes(
9192
def inspect_methods(
9293
definition_methods, class_type,
9394
):
94-
no_dunder = lambda x: not (x[0].startswith('__') or x[0].endswith('__'))
95+
no_dunder = lambda method_name: not (method_name[0].startswith('__') or method_name[0].endswith('__'))
9596
methods = filter(no_dunder, getmembers(class_type, callable))
9697
for name, method in methods:
97-
signature = signature(method)
98+
method_signature = signature(method)
9899
uml_method = UmlMethod(
99100
name=name,
100-
signature=str(signature),
101+
signature=str(method_signature),
101102
)
102103
definition_methods.append(uml_method)
103104

104105

105-
def handle_class_type(
106-
class_type: Type,
107-
class_type_fqn: str,
108-
domain_items_by_fqn: Dict[str, UmlItem],
109-
) -> UmlClass:
110-
definition_attrs: List[UmlAttribute] = []
111-
definition_methods: List[UmlMethod] = []
112-
uml_class = UmlClass(
113-
name=class_type.__name__,
114-
fqn=class_type_fqn,
115-
attributes=definition_attrs,
116-
methods=definition_methods,
117-
is_abstract=isabstract(class_type)
118-
)
119-
domain_items_by_fqn[class_type_fqn] = uml_class
120-
return uml_class
121-
122106
def inspect_class_type(
123107
class_type: Type,
124108
class_type_fqn: str,
125109
root_module_name: str,
126110
domain_items_by_fqn: Dict[str, UmlItem],
127111
domain_relations: List[UmlRelation]
128112
):
129-
uml_class = handle_class_type(class_type, class_type_fqn, domain_items_by_fqn)
130-
inspect_static_attributes(
131-
class_type_fqn, uml_class.attributes, class_type, root_module_name, domain_relations
113+
attributes = inspect_static_attributes(
114+
class_type, class_type_fqn, root_module_name,
115+
domain_items_by_fqn, domain_relations
132116
)
133-
inspect_methods(uml_class.methods, class_type)
134117
instance_attributes, compositions = parse_class_constructor(class_type, class_type_fqn, root_module_name)
135-
uml_class.attributes.extend(instance_attributes)
118+
attributes.extend(instance_attributes)
136119
domain_relations.extend(compositions.values())
120+
121+
inspect_methods(domain_items_by_fqn[class_type_fqn].methods, class_type)
137122

138123
handle_inheritance_relation(class_type, class_type_fqn, root_module_name, domain_relations)
139124

@@ -144,12 +129,13 @@ def inspect_dataclass_type(
144129
domain_items_by_fqn: Dict[str, UmlItem],
145130
domain_relations: List[UmlRelation]
146131
):
147-
uml_class = handle_class_type(class_type, class_type_fqn, domain_items_by_fqn)
148-
inspect_static_attributes(
149-
class_type_fqn, uml_class.attributes, class_type, root_module_name, domain_relations
150-
)
151-
inspect_methods(uml_class.methods, class_type)
152-
for attribute in uml_class.attributes:
132+
for attribute in inspect_static_attributes(
133+
class_type,
134+
class_type_fqn,
135+
root_module_name,
136+
domain_items_by_fqn,
137+
domain_relations
138+
):
153139
attribute.static = False
154140

155-
handle_inheritance_relation(class_type, class_type_fqn, root_module_name, domain_relations)
141+
handle_inheritance_relation(class_type, class_type_fqn, root_module_name, domain_relations)

tests/asserts/method.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from py2puml.domain.umlclass import UmlMethod
2+
3+
4+
def assert_method(method: UmlMethod, expected_name: str, expected_signature: str):
5+
assert method.name == expected_name
6+
assert method.signature == expected_signature
7+
#TODO: add 'is_static' attribute to UmlMethod for static methods
8+
#TODO: add 'is_class' attribute to UmlMethod for class methods
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import List, Tuple
2+
from math import pi
3+
4+
from tests.modules import withenum
5+
from tests import modules
6+
from tests.modules.withenum import TimeUnit
7+
8+
9+
class Coordinates:
10+
def __init__(self, x: float, y: float):
11+
self.x = x
12+
self.y = y
13+
14+
15+
class Point:
16+
PI: float = pi
17+
origin = Coordinates(0, 0)
18+
19+
@staticmethod
20+
def from_values(x: int, y: str, u: float, z: List[int]) -> 'Point':
21+
return Point(x, y, u, z)
22+
23+
def get_coordinates(self):
24+
return self.x, self.y
25+
26+
def __init__(self, x: int, y: str, unit: str, u: float, z: List[int]):
27+
self.coordinates: Coordinates = Coordinates(x, float(y))
28+
# al the different imports of TimeUnit must be handled and result in the same 'short type' to display
29+
self.day_unit: withenum.TimeUnit = withenum.TimeUnit.DAYS
30+
self.hour_unit: modules.withenum.TimeUnit = modules.withenum.TimeUnit.HOURS
31+
self.time_resolution: Tuple[str, withenum.TimeUnit] = 'minute', TimeUnit.MINUTE
32+
self.x = x
33+
self.y = y

tests/puml_files/with_methods.puml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@startuml tests.modules.withmethods
2+
namespace tests.modules.withmethods.withmethods {}
3+
class tests.modules.withmethods.withmethods.Coordinates {
4+
x: float
5+
y: float
6+
}
7+
class tests.modules.withmethods.withmethods.Point {
8+
PI: float {static}
9+
coordinates: Coordinates
10+
day_unit: TimeUnit
11+
hour_unit: TimeUnit
12+
time_resolution: Tuple[str, TimeUnit]
13+
x: int
14+
y: str
15+
from_values(x: int, y: str, u: float, z: List[int]) -> 'Point'
16+
get_coordinates(self)
17+
}
18+
tests.modules.withmethods.withmethods.Point *-- tests.modules.withmethods.withmethods.Coordinates
19+
footer Generated by //py2puml//
20+
@enduml

tests/py2puml/inspection/test_inspectclass.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from tests.asserts.attribute import assert_attribute
1010
from tests.asserts.relation import assert_relation
11+
from tests.asserts.method import assert_method
1112

1213

1314
def test_inspect_module_should_find_static_and_instance_attributes(
@@ -167,3 +168,35 @@ def test_inspect_module_should_handle_compound_types_with_numbers_in_their_name(
167168
assert len(multicast_umlitem.attributes) == 1, '1 attributes of Multicast must be inspected'
168169
address_attribute = multicast_umlitem.attributes[0]
169170
assert_attribute(address_attribute, 'addresses', 'List[IPv6]', expected_staticity=False)
171+
172+
def test_inspect_module_should_find_methods(
173+
domain_items_by_fqn: Dict[str, UmlItem], domain_relations: List[UmlRelation]
174+
):
175+
'''
176+
Test that methods are detected including static methods
177+
178+
This test case assumes that the methods will be sorted by type as follow:
179+
1a - instance methods (special methods aka "dunder")
180+
1b - all other instance methods
181+
2 - static methods
182+
'''
183+
184+
inspect_module(
185+
import_module('tests.modules.withmethods.withmethods'),
186+
'tests.modules.withmethods.withmethods',
187+
domain_items_by_fqn, domain_relations
188+
)
189+
190+
# Coordinates UmlClass
191+
coordinates_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withmethods.withmethods.Coordinates']
192+
assert len(coordinates_umlitem.methods) == 0
193+
194+
# Point UmlClass
195+
point_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withmethods.withmethods.Point']
196+
assert len(point_umlitem.methods) == 2
197+
198+
# FIXME dunder methods are filtered out for now
199+
# assert point_umlitem.methods[0].name == '__init__' # 1a - Instance method (special)
200+
assert point_umlitem.methods[0].name == 'from_values' # 2 - Static method
201+
assert point_umlitem.methods[1].name == 'get_coordinates' # 1b - Instance method (regular)
202+
# FIXME: use 'assert_method' once UmlMethod restructured

tests/py2puml/test_py2puml.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from py2puml.asserts import assert_py2puml_is_file_content, assert_py2puml_is_stringio
55

66

7+
from tests import TESTS_PATH
8+
9+
710
CURRENT_DIR = Path(__file__).parent
811

912
def test_py2puml_model_on_py2uml_domain():
@@ -38,5 +41,13 @@ class tests.modules.withsubdomain.withsubdomain.Car {
3841
footer Generated by //py2puml//
3942
@enduml
4043
"""
41-
4244
assert_py2puml_is_stringio('tests/modules/withsubdomain/', 'tests.modules.withsubdomain', StringIO(expected))
45+
46+
47+
def test_py2puml_with_methods():
48+
'''
49+
Test py2puml with a class containing methods
50+
'''
51+
with_methods_diagram_file_path = TESTS_PATH / 'puml_files' / 'with_methods.puml'
52+
53+
assert_py2puml_is_file_content('tests/modules/withmethods', 'tests.modules.withmethods', with_methods_diagram_file_path)

0 commit comments

Comments
 (0)