테스트 실행을 아래와 같은 흐름으로 최적화
-
tox가 너무 느려,tox -e py3로 테스트 수행 -
tox -e py3또한 너무 느려,export PIP_CACHE_DIR=~/.cache/pip를 추가하여 캐싱 시도 -
이 또한 너무 느려, tox가 stestr을 실행하기위해 다양한 의존성을 가상환경에 설치하는 것이므로, 이를 생략하고자
stestr run 'openstackclient.tests.unit.compute.v2.test_server'실행으로 테스트 최적화
기존 ListServer를 아래와 같이 수정하여 openstack server list 를 구현했었음.
# 25.08.05 - jian
columns += ('project_name', 'user_name')
column_headers += ('Project_name', 'User_name')
...
...
...
# Add project_name & User_name to serverlist - 2025.07.28 jian
project_map = {p.id: p.name for p in identity_client.projects.list()}
user_map = {u.id: u.name for u in identity_client.users.list()}
# Add project_name & User_name to serverlist - 2025.07.28 jian
for s in data:
s.project_name = project_map.get(s.project_id)
s.user_name = user_map.get(s.user_id)
...
...
...
이에 아래와 같은 에러 발생
==============================
Failed 26 tests - output below:
==============================
openstackclient.tests.unit.compute.v2.test_server.TestServerList.test_server_list_all_projects_option
-----------------------------------------------------------------------------------------------------
Captured traceback:
~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/mnt/c/git/project/OSSCA/python-openstackclient/openstackclient/tests/unit/compute/v2/test_server.py", line 4790, in test_server_list_all_projects_option
columns, data = self.cmd.take_action(parsed_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/mnt/c/git/project/OSSCA/python-openstackclient/openstackclient/compute/v2/server.py", line 2932, in take_action
project_map = {p.id: p.name for p in identity_client.projects.list()}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'Mock' object is not iterable
openstackclient.tests.unit.compute.v2.test_server.TestServerList.test_server_list_column_option
-----------------------------------------------------------------------------------------------
...
...
...
목 객체에 이터러블이 적용되어 있지 않은 문제를 발견.
방법1. 목객체에 이터러블 타입 추가
방법2. 목객체를 이터러블로 조회하지 않고, 가짜 값을 반환하기
방법2가 쉬워보여 방법 2를 택했음.
class _TestServerList(TestServer):
# Columns to be listed up.
columns = (
'ID',
'Name',
'Status',
'Networks',
'Image',
'Flavor',
'Project_name', # - 2025.08.04 jian
'User_name' # - 2025.08.04 jian
)
columns_long = (
'ID',
'Name',
'Status',
'Task State',
'Power State',
'Networks',
'Image Name',
'Image ID',
'Flavor Name',
'Flavor ID',
'Availability Zone',
'Pinned Availability Zone',
'Host',
'Properties',
'Scheduler Hints',
'Project_name', # - 2025.08.04 jian
'User_name' # - 2025.08.04 jian
)
columns_all_projects = (
'ID',
'Name',
'Status',
'Networks',
'Image',
'Flavor',
'Project ID',
'Project_name', # - 2025.08.04 jian
'User_name' # - 2025.08.04 jian
)
...
PN, UN을 달아주고
setup()에서 객체에 proj,user id 셋업 후 Mock 이터러블 에러 해결을 위해 가짜값 반환
def setUp(self):
super().setUp()
# Default params of the core function of the command in the case of no
# commandline option specified.
self.kwargs = {
'reservation_id': None,
'ip': None,
'ip6': None,
'name': None,
'status': None,
'flavor': None,
'image': None,
'compute_host': None,
'project_id': None,
'all_projects': False,
'user_id': None,
'deleted': False,
'changes-since': None,
'changes-before': None,
}
# The fake servers' attributes. Use the original attributes names in
# nova, not the ones printed by "server list" command.
self.attrs = {
'status': 'ACTIVE',
'OS-EXT-STS:task_state': 'None',
'OS-EXT-STS:power_state': 0x01, # Running
'networks': {'public': ['10.20.30.40', '2001:db8::5']},
'OS-EXT-AZ:availability_zone': 'availability-zone-xxx',
'OS-EXT-SRV-ATTR:host': 'host-name-xxx',
'Metadata': format_columns.DictColumn({}),
}
self.image = image_fakes.create_one_image()
self.image_client.find_image.return_value = self.image
self.image_client.get_image.return_value = self.image
self.flavor = compute_fakes.create_one_flavor()
self.compute_client.find_flavor.return_value = self.flavor
self.attrs['flavor'] = {'original_name': self.flavor.name}
# The servers to be listed.
self.servers = self.setup_sdk_servers_mock(3)
self.compute_client.servers.return_value = self.servers
# 서버들이 keystone 맵핑에 걸리도록 id를 통일
for s in self.servers: # - 2025.08.04 jian
s.project_id = 'proj1'# - 2025.08.04 jian
s.user_id = 'user1'# - 2025.08.04 jian
# Get the command object to test
self.cmd = server.ListServer(self.app, None)
from collections import namedtuple# - 2025.08.04 jian
Project = namedtuple('Project', 'id name')# - 2025.08.04 jian
User = namedtuple('User', 'id name')# - 2025.08.04 jian
self.identity_client.projects.list.return_value = [Project('proj1', 'Project 1')]# - 2025.08.04 jian
self.identity_client.users.list.return_value = [User('user1', 'User 1')]# - 2025.08.04 jian
이후 모든 함수들의 self.data()에서 가짜값을 통일시켜줌
def test_server_list_long_with_host_status_v216(self):
self.set_compute_api_version('2.16')
self.data1 = tuple(
(
s.id,
s.name,
s.status,
getattr(s, 'task_state'),
server.PowerStateColumn(getattr(s, 'power_state')),
server.AddressesColumn(s.addresses),
# Image will be an empty string if boot-from-volume
self.image.name if s.image else server.IMAGE_STRING_FOR_BFV,
s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV,
self.flavor.name,
s.flavor['id'],
getattr(s, 'availability_zone'),
getattr(s, 'pinned_availability_zone', ''),
server.HostColumn(getattr(s, 'hypervisor_hostname')),
format_columns.DictColumn(s.metadata),
format_columns.DictListColumn(s.scheduler_hints),
'Project 1', 'User 1', # - 25.08.04 jian
)
for s in self.servers
)
TestServerListV273 클래스에서는 expedcted_row튜플에서 PN/UN을 None설정
def test_server_list_v269_with_partial_constructs(self):
self.set_compute_api_version('2.69')
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# include "partial results" from non-responsive part of
# infrastructure.
server_dict = {
"id": "server-id-95a56bfc4xxxxxx28d7e418bfd97813a",
"status": "UNKNOWN",
"tenant_id": "6f70656e737461636b20342065766572",
"created": "2018-12-03T21:06:18Z",
"links": [
{"href": "<http://fake/v2.1/>", "rel": "self"},
{"href": "<http://fake>", "rel": "bookmark"},
],
"networks": {},
}
fake_server = _server.Server(**server_dict)
self.servers.append(fake_server)
columns, data = self.cmd.take_action(parsed_args)
# get the first three servers out since our interest is in the partial
# server.
next(data)
next(data)
next(data)
partial_server = next(data)
expected_row = (
'server-id-95a56bfc4xxxxxx28d7e418bfd97813a',
None,
'UNKNOWN',
server.AddressesColumn(None),
'',
'',
None, None, # - 25.08.04 jian
)
self.assertEqual(expected_row, partial_server)
다시 stestr run 돌렸을때 테스트 값이 같지 않는 에러발생.
에러: AssertionError: Element counts were not equal:
==============================
Failed 4 tests - output below:
==============================
openstackclient.tests.unit.compute.v2.test_server.TestServerListV273.test_server_list_with_changes_before
---------------------------------------------------------------------------------------------------------
Captured traceback:
~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/mnt/c/git/project/OSSCA/python-openstackclient/openstackclient/tests/unit/compute/v2/test_server.py", line 5503, in test_server_list_with_changes_before
self.assertCountEqual(self.data, tuple(data))
File "/usr/lib/python3.12/unittest/case.py", line 1216, in assertCountEqual
self.fail(msg)
File "/usr/lib/python3.12/unittest/case.py", line 715, in fail
raise self.failureException(msg)
AssertionError: Element counts were not equal:
First has 1, Second has 0: ('server-id-bc189e87868a48f2b25f05b3bac201f2', 'server-name-9855255ec1ef41d1b3a0efe4d69b332e', 'ACTIVE', AddressesColumn({}), 'image-name7e04e951297a4d3a973a9f35b6ad806a', 'flavor-name-8c066c28121b4f0eacfe96dafc4abc8e', 'Project 1', 'User 1')
First has 1, Second has 0: ('server-id-6f516480a8db4f4d9874bf8c67394932', 'server-name-42cc232aefd94a6dac6ebf95b80862af', 'ACTIVE', AddressesColumn({}), 'image-name7e04e951297a4d3a973a9f35b6ad806a', 'flavor-name-8c066c28121b4f0eacfe96dafc4abc8e', 'Project 1', 'User 1')
First has 1, Second has 0: ('server-id-cc84b533eec245c09f5de7e949c80329', 'server-name-501ba90a89db479f9f7839a8c204f9e9', 'ACTIVE', AddressesColumn({}), 'image-name7e04e951297a4d3a973a9f35b6ad806a', 'flavor-name-8c066c28121b4f0eacfe96dafc4abc8e', 'Project 1', 'User 1')
First has 0, Second has 1: ('server-id-bc189e87868a48f2b25f05b3bac201f2', 'server-name-9855255ec1ef41d1b3a0efe4d69b332e', 'ACTIVE', AddressesColumn({}), 'image-name7e04e951297a4d3a973a9f35b6ad806a', 'flavor-name-8c066c28121b4f0eacfe96dafc4abc8e', None, None)
First has 0, Second has 1: ('server-id-6f516480a8db4f4d9874bf8c67394932', 'server-name-42cc232aefd94a6dac6ebf95b80862af', 'ACTIVE', AddressesColumn({}), 'image-name7e04e951297a4d3a973a9f35b6ad806a', 'flavor-name-8c066c28121b4f0eacfe96dafc4abc8e', None, None)
First has 0, Second has 1: ('server-id-cc84b533eec245c09f5de7e949c80329', 'server-name-501ba90a89db479f9f7839a8c204f9e9', 'ACTIVE', AddressesColumn({}), 'image-name7e04e951297a4d3a973a9f35b6ad806a', 'flavor-name-8c066c28121b4f0eacfe96dafc4abc8e', None, None)
openstackclient.tests.unit.compute.v2.test_server.TestServerListV273.test_server_list_with_locked
-------------------------------------------------------------------------------------------------
Captured traceback:
~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/mnt/c/git/project/OSSCA/python-openstackclient/openstackclient/tests/unit/compute/v2/test_server.py", line 5455, in test_server_list_with_locked
self.assertCountEqual(self.data, tuple(data))
File "/usr/lib/python3.12/unittest/case.py", line 1216, in assertCountEqual
self.fail(msg)
File "/usr/lib/python3.12/unittest/case.py", line 715, in fail
raise self.failureException(msg)
AssertionError: Element counts were not equal:
First has 1, Second has 0: ('server-id-77400a0
...
...
...
TestServerList273 에도 setup()에서 id주입
class TestServerListV273(_TestServerList):
...
...
def setUp(self):
super().setUp()
# The fake servers' attributes. Use the original attributes names in
# nova, not the ones printed by "server list" command.
self.attrs['flavor'] = {
'vcpus': self.flavor.vcpus,
'ram': self.flavor.ram,
'disk': self.flavor.disk,
'ephemeral': self.flavor.ephemeral,
'swap': self.flavor.swap,
'original_name': self.flavor.name,
'extra_specs': self.flavor.extra_specs,
}
# The servers to be listed.
self.servers = self.setup_sdk_servers_mock(3)
for s in self.servers: # - 25.08.05 -jian
s.project_id = 'proj1'# - 25.08.05 -jian
s.user_id = 'user1'# - 25.08.05 -jian
아직 Fail 1개
==============================
Failed 1 tests - output below:
==============================
openstackclient.tests.unit.compute.v2.test_server.TestServerList.test_server_list_long_with_host_status_v216
------------------------------------------------------------------------------------------------------------
Captured traceback:
~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/mnt/c/git/project/OSSCA/python-openstackclient/openstackclient/tests/unit/compute/v2/test_server.py", line 5354, in test_server_list_long_with_host_status_v216
self.assertEqual(tuple(self.data2), tuple(data))
File "/mnt/c/git/project/OSSCA/.venv/lib/python3.12/site-packages/testtools/testcase.py", line 419, in assertEqual
self.assertThat(observed, matcher, message)
File "/mnt/c/git/project/OSSCA/.venv/lib/python3.12/site-packages/testtools/testcase.py", line 509, in assertThat
raise mismatch_error
testtools.matchers._impl.MismatchError: !=:
...
...
216쪽에서 미스매치 에러.
또 id를 셋업해줬다.
def test_server_list_long_with_host_status_v216(self):
self.set_compute_api_version('2.16')
self.data1 = tuple(
(
s.id,
s.name,
s.status,
getattr(s, 'task_state'),
server.PowerStateColumn(getattr(s, 'power_state')),
server.AddressesColumn(s.addresses),
# Image will be an empty string if boot-from-volume
self.image.name if s.image else server.IMAGE_STRING_FOR_BFV,
s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV,
self.flavor.name,
s.flavor['id'],
getattr(s, 'availability_zone'),
getattr(s, 'pinned_availability_zone', ''),
server.HostColumn(getattr(s, 'hypervisor_hostname')),
format_columns.DictColumn(s.metadata),
format_columns.DictListColumn(s.scheduler_hints),
'Project 1', 'User 1', # - 25.08.04 jian
)
for s in self.servers
)
arglist = ['--long']
verifylist = [
('long', True),
]
# First test without host_status in the data -- the column should not
# be present in this case.
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.compute_client.servers.assert_called_with(**self.kwargs)
self.assertEqual(self.columns_long, columns)
self.assertEqual(tuple(self.data1), tuple(data))
# Next test with host_status in the data -- the column should be
# present in this case.
self.compute_client.servers.reset_mock()
self.attrs['host_status'] = 'UP'
servers = self.setup_sdk_servers_mock(3)
for s in servers: # - 25.08.04 - jian
s.project_id = 'proj1'# - 25.08.04 - jian
s.user_id = 'user1'# - 25.08.04 - jian
하면서 너무 난잡한것같아서 다른사람들 걸 봤는데 이렇게 하는게 아닌 것 같다..
take_action부터 이터러블 아닌걸로 고치고 다시 해보겠습니다.
