Improved test architecture #139

This commit is contained in:
Sven Heidemann 2022-11-27 00:42:05 +01:00
parent de78cec96c
commit 75c316f2d2
10 changed files with 89 additions and 17 deletions

View File

@ -9,6 +9,7 @@ from cpl_core.dependency_injection import ServiceProviderABC, ServiceCollectionA
from cpl_core.environment import ApplicationEnvironment from cpl_core.environment import ApplicationEnvironment
from cpl_discord import get_discord_collection from cpl_discord import get_discord_collection
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
from dotenv import load_dotenv
from ui_tests.test_on_ready_event import TestOnReadyEvent from ui_tests.test_on_ready_event import TestOnReadyEvent
@ -21,6 +22,7 @@ class Startup(StartupABC):
self._config: Optional[ConfigurationABC] = None self._config: Optional[ConfigurationABC] = None
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC: def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
load_dotenv()
configuration.add_environment_variables('KDB_TEST_') configuration.add_environment_variables('KDB_TEST_')
configuration.add_environment_variables('DISCORD_') configuration.add_environment_variables('DISCORD_')

View File

@ -1,9 +1,12 @@
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.application import DiscordBotApplicationABC from cpl_discord.application import DiscordBotApplicationABC
from cpl_discord.configuration import DiscordBotSettings
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslationSettings, TranslationServiceABC from cpl_translation import TranslationSettings, TranslationServiceABC
from bot_core.configuration.bot_settings import BotSettings
class TestApplication(DiscordBotApplicationABC): class TestApplication(DiscordBotApplicationABC):
@ -16,6 +19,28 @@ class TestApplication(DiscordBotApplicationABC):
self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC) self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
discord_bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings)
self._discord_settings = self._get_settings(discord_bot_settings)
def _get_settings(self, settings_from_config: DiscordBotSettings) -> DiscordBotSettings:
new_settings = DiscordBotSettings()
token = None if settings_from_config is None else settings_from_config.token
prefix = None if settings_from_config is None else settings_from_config.prefix
env_token = self._config.get_configuration('TOKEN')
env_prefix = self._config.get_configuration('PREFIX')
new_settings.from_dict({
'Token': env_token if token is None or token == '' else token,
'Prefix': ('! ' if self._is_string_invalid(env_prefix) else env_prefix) if self._is_string_invalid(prefix) else prefix
})
if new_settings.token is None or new_settings.token == '':
raise Exception('You have to configure discord token by appsettings or environment variables')
return new_settings
@staticmethod
def _is_string_invalid(x):
return x is None or x == ''
@property @property
def config(self) -> ConfigurationABC: def config(self) -> ConfigurationABC:
return self._config return self._config
@ -28,6 +53,11 @@ class TestApplication(DiscordBotApplicationABC):
self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings)) self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings))
async def main(self): async def main(self):
if self._config.get_configuration('IS_UNITTEST'):
await self._bot.login(self._discord_settings.token)
self._bot.loop.create_task(self._bot.connect())
return
await self._bot.start_async() await self._bot.start_async()
async def stop_async(self): async def stop_async(self):

View File

@ -1,6 +1,7 @@
import os import os
import unittest import unittest
from cpl_core.configuration import ConfigurationABC
from cpl_core.console import Console, ForegroundColorEnum from cpl_core.console import Console, ForegroundColorEnum
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC from cpl_core.logging import LoggerABC
@ -15,7 +16,7 @@ class TestOnReadyEvent(OnReadyABC):
def __init__( def __init__(
self, self,
logger: LoggerABC, config: ConfigurationABC,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
services: ServiceProviderABC, services: ServiceProviderABC,
client_utils: ClientUtilsServiceABC, client_utils: ClientUtilsServiceABC,
@ -23,13 +24,16 @@ class TestOnReadyEvent(OnReadyABC):
): ):
OnReadyABC.__init__(self) OnReadyABC.__init__(self)
self._logger = logger self._config = config
self._bot = bot self._bot = bot
self._services = services self._services = services
self._client_utils = client_utils self._client_utils = client_utils
self._t = t self._t = t
async def on_ready(self): async def on_ready(self):
if self._config.get_configuration('IS_UNITTEST'):
return
Console.write_line('\nStarting tests:\n') Console.write_line('\nStarting tests:\n')
loader = unittest.TestLoader() loader = unittest.TestLoader()
path = f'{os.path.dirname(os.path.realpath(__file__))}/../' path = f'{os.path.dirname(os.path.realpath(__file__))}/../'

View File

@ -20,7 +20,6 @@ class CommandTestCaseWithApp(TestCaseWithApp):
_bot: Optional[DiscordBotServiceABC] = None _bot: Optional[DiscordBotServiceABC] = None
_t: Optional[TranslatePipe] = None _t: Optional[TranslatePipe] = None
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
TestCaseWithApp.setUpClass() TestCaseWithApp.setUpClass()
@ -45,4 +44,5 @@ class CommandTestCaseWithApp(TestCaseWithApp):
)) ))
).click() ).click()
time.sleep(2) time.sleep(2)
UI.driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div/main/form/div/div[2]/div/div[2]/div/div').send_keys(Keys.ENTER) UI.driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div/main/form/div/div[2]/div/div[2]/div/div').send_keys(
Keys.ENTER)

View File

@ -7,6 +7,6 @@ from ui_tests_shared.configuration.test_settings import TestSettings
class Declarations: class Declarations:
app: Optional[TestApplication] app: Optional[TestApplication] = None
env: Optional[ApplicationEnvironmentABC] env: Optional[ApplicationEnvironmentABC] = None
test_settings: Optional[TestSettings] test_settings: Optional[TestSettings] = None

View File

@ -1,9 +1,15 @@
import asyncio import asyncio
def async_test(coro): class Async:
def wrapper(*args, **kwargs): _loop = None
loop = asyncio.get_running_loop()
return loop.run_until_complete(coro(*args, **kwargs))
return wrapper @classmethod
def test(cls, coro):
def wrapper(*args, **kwargs):
if cls._loop is None:
cls._loop = asyncio.get_event_loop()
return cls._loop.run_until_complete(coro(*args, **kwargs))
return wrapper

View File

@ -1,3 +1,5 @@
import asyncio
import time
import unittest import unittest
from typing import Optional from typing import Optional
@ -18,10 +20,20 @@ class TestCaseWithApp(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
if Declarations.app is None: if Declarations.app is None:
get_app() app = get_app()
import nest_asyncio
nest_asyncio.apply()
asyncio.run(app.run_async())
cls._config = Declarations.app.config cls._config = Declarations.app.config
cls._services = Declarations.app.services cls._services = Declarations.app.services
cls._test_settings: TestSettings = cls._config.get_configuration(TestSettings) cls._test_settings: TestSettings = cls._config.get_configuration(TestSettings)
UI.set_settings(cls._test_settings) UI.set_settings(cls._test_settings)
UI.login(cls._config.get_configuration('DISCORD_MAIL'), cls._config.get_configuration('DISCORD_PASSWORD')) UI.login(cls._config.get_configuration('DISCORD_MAIL'), cls._config.get_configuration('DISCORD_PASSWORD'))
@classmethod
def tearDownClass(cls):
if cls._config.get_configuration('IS_UNITTEST'):
loop = asyncio.get_event_loop()
loop.run_until_complete(Declarations.app.stop_async())

View File

@ -54,3 +54,4 @@ class UI:
WebDriverWait(cls.driver, 20).until(expected_conditions.url_matches(cls._test_settings.me_page_url)) WebDriverWait(cls.driver, 20).until(expected_conditions.url_matches(cls._test_settings.me_page_url))
time.sleep(4) time.sleep(4)
cls._is_logged_in = True

View File

@ -0,0 +1,19 @@
import discord
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class HelpTestCase(CommandTestCaseWithApp):
@Async.test
async def test_help(self):
correct_response = 'https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot/wiki/Befehle'
self.send_command('help')
def check(m: discord.Message):
return m.content == correct_response and m.author.id == 998159802393964594
response = await self._bot.wait_for('message', check=check)
self.assertEqual(response.content, correct_response)
await self._bot.close()

View File

@ -1,14 +1,12 @@
import unittest
import discord import discord
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import async_test from ui_tests_shared.decorators import Async
class PingTestCase(CommandTestCaseWithApp): class PingTestCase(CommandTestCaseWithApp):
@async_test @Async.test
async def test_ping(self): async def test_ping(self):
correct_response = self._t.transform('modules.base.pong') correct_response = self._t.transform('modules.base.pong')
self.assertIsNotNone(correct_response) self.assertIsNotNone(correct_response)