Comment convaincre un game designer de faire des tests?

Je suppose que ce n'est un secret pour personne que de nombreux spécialistes sont impliqués dans le développement de jeux, et pas seulement des programmeurs. La sortie d'un jeu est impossible sans artistes, modélisateurs, artistes VFX et, bien sûr, concepteurs de jeux. À propos de ce dernier. Nous les aimons beaucoup, mais ils cassent souvent les ressources. Non pas qu'ils veuillent le faire, mais en raison de la nature du travail dont ils ont besoin pour faire beaucoup de petites modifications, et la chance de se tromper est plus élevée. Et après tout, de nombreuses erreurs sont des fautes de frappe triviales, une ligne incomplète ou, au contraire, une ligne supplémentaire supprimée. Tout cela peut être corrigé sans quitter la caisse. Mais comment faire ça? Écrivez dans les règlements que vous devez exécuter% my_folder% / scripts / mega_checker avant de valider? Nous avons vérifié - cela ne fonctionne pas. L'homme est une créature complexe et oublieuse. Et je veux vérifier les ressources.



Mais nous avons trouvé un moyen de sortir - il est maintenant impossible de s'engager dans le référentiel sans tests. Au moins imperceptiblement et en toute impunité.







Système de test



La première chose dont nous avons besoin est un système de test. Nous l'avons déjà décrit ici. Rappelez-vous que vous avez besoin du même code pour s'exécuter sur le serveur Ci, et localement, afin qu'il n'y ait aucune difficulté à maintenir. Il est souhaitable que le projet puisse définir divers paramètres pour des tests courants, ou mieux encore, l'étendre avec les leurs. Bien sûr, les bonbons ne sont pas sortis tout de suite.



Première étape- vous pouvez courir, mais ça fait mal. Que faire avec le code python est toujours clair, mais avec toutes sortes d'utilitaires comme CppCheck, Bloaty, optipng, nos béquilles internes, des vélos - non. Pour fonctionner correctement, nous avons besoin de fichiers exécutables pour toutes les plateformes sur lesquelles travaillent nos collègues (mac, windows et linux). À ce stade, tous les binaires nécessaires se trouvaient dans le référentiel et le chemin relatif du dossier binaires était indiqué dans les paramètres du système de test.



<CppCheck bin_folder=”utils/cppcheck”>...</CppCheck>
      
      





Cela pose plusieurs problèmes:



  • du cĂ´tĂ© du projet, vous devez stocker les fichiers inutiles dans le rĂ©fĂ©rentiel, car ils sont nĂ©cessaires sur l'ordinateur de chaque dĂ©veloppeur. Naturellement, le rĂ©fĂ©rentiel est plus grand Ă  cause de cela.
  • lorsqu'un problème survient, il est difficile de comprendre de quelle version le projet a, si la structure requise se trouve dans le dossier.
  • oĂą trouver les binaires nĂ©cessaires? Compilez-vous, tĂ©lĂ©chargez sur Internet?


Deuxième Ă©tape - nous mettons les choses en ordre dans les utilitaires. Mais que se passe-t-il si vous Ă©crivez tous les utilitaires nĂ©cessaires et les rassemblez dans un rĂ©fĂ©rentiel? L'idĂ©e est que sur le serveur, il existe dĂ©jĂ  des utilitaires assemblĂ©s pour toutes les plates-formes nĂ©cessaires, qui sont Ă©galement versionnĂ©es. Nous avons dĂ©jĂ  utilisĂ© Nexus Sonatype, nous sommes donc allĂ©s au dĂ©partement suivant et nous nous sommes mis d'accord sur les fichiers. Le rĂ©sultat est une structure: 





Pour commencer, vous avez besoin d'un script qui connaît l'adresse secrète où se trouvent les binaires, peut les télécharger et également s'exécuter, selon la plate-forme, avec les paramètres passés.



Omettre les subtilités de la mise en œuvre
def get_tools_info(project_tools_xml, available_tools_xml):
    # Parse available tools at first and feel up dictionary
    root = etree.parse(available_tools_xml).getroot()
    tools = {}

    # Parse xml and find current installed version ...
    return tools

def update_tool(tool_info: ToolInfo):
    if tool_info.current_version == tool_info.needed_version:
        return
    if tool_info.needed_version not in tool_info.versions:
        raise RuntimeError(f'Tool "{tool_info.tool_id}" has no version "{tool_info.needed_version}"')
    if os.path.isdir(tool_info.output_folder):
        shutil.rmtree(tool_info.output_folder)
    g_server_interface.download(tool_id=tool_info.tool_id, version=tool_info.needed_version,
                                output_folder=tool_info.output_folder)

def run_tool(tool_info: ToolInfo, tool_args):
    system_name = platform.system().lower()
    tool_bin = tool_info.exe_infos[system_name].executable
    full_path = os.path.join(tool_info.output_folder, tool_bin)
    command = [full_path] + tool_args
    try:
        print(f'Run tool: "{tool_info.tool_id}" with commands: "{" ".join(tool_args)}"')
        output = subprocess.check_output(command)
        print(output)
    except Exception as e:
        print(f'Fail with: {e}')
        return 1
    return 0

def run(project_tools_xml, available_tools_xml, tool_id, tool_args):
    tools = get_tools_info(project_tools_xml=project_tools_xml, available_tools_xml=available_tools_xml)
    update_tool(tools[tool_id])
    return run_tool(tool_info, tool_args)

      
      





Sur le serveur, nous avons ajouté un fichier avec une description des utilitaires. L'adresse de ce fichier est inchangée, donc la première chose que nous faisons est d'y aller et de voir ce que nous avons en stock. En omettant les subtilités, il s'agit des noms de package et du chemin d'accès au fichier exécutable à l'intérieur du package pour chaque plateforme.



xml "sur le serveur"
<?xml version='1.0' encoding='utf-8'?>
<Tools>
	<CppCheck>
		<windows executable="cppcheck.exe" />
		<darwin executable="cppcheck" />
		<linux executable="cppcheck" />
	</CppCheck>
</Tools>

      
      







Et sur le projet, ajoutez un fichier avec une description de ce dont vous avez besoin.



projet xml

<?xml version='1.0' encoding='utf-8'?>
<Tools>
	<CppCheck version="1.89" />
</Tools>
      
      





, , , . .



python -m utility_runner --available-source D:\Playrix\![habr]\gd_hooks\available_source.xml --project-tools D:\Playrix\![habr]\gd_hooks\project\project_tools.xml --run-tool CppCheck -- --version

      
      





:



  • , ,
  • , , . .




, — .



- ?



- , , ? -, . — , . : git.



-, — bash-, git: pull push, , git-.



, :



  • pre-commit — . , .
  • prepare-commit-msg — , . , rebase.
  • commit-msg — . , . , .


, , , .git/hooks. — . , ( Windows Mac), . , .



, . , .



. , , git-bash Windows. FAQ.



: , , dns . , curl [ .





. , . , FAQ. , .git/hooks . , :



git rev-parse
git rev-parse --git-path hooks

      
      





, , :



.git/hooks
      
      



Worktree
%repo_abs%/.git/hooks
      
      



submodule
%repo_abs%/.git/modules/hooks
      
      





— . .git/hooks, . . , .git/hooks , .



,   , - . , -. — . , — . :



  1.   pre-commit , . pre-commit-tmp
  2. commit-msg pre-commit pre-commit-tmp


, : , . , .





<spoiler title=« :> : 32- ; , 64-; pip install , . - 32- — .


Mais quand mĂŞme, comment se lancer?



Tout d'abord, nous avons créé une instruction de plusieurs pages sur les croissants les plus savoureux , le python à installer. Mais nous souvenons-nous des concepteurs de jeux et des œufs brouillés? Il a toujours été brûlé: soit python du mauvais bitness, soit 2.7 au lieu de 3.7. Et tout cela est également multiplié par deux plates-formes où les utilisateurs travaillent: Windows et Mac. (Les utilisateurs de Linux avec nous, soit des gourous et installent tout eux-mêmes, tapotant tranquillement au son d'un tambourin, soit ils ont passé le problème.)



Nous avons résolu le problème radicalement - nous avons collecté le python de la version et le bitness requis. Et à la question «comment le mettre et où le stocker», ils ont répondu: Nexus! Le seul problème: nous n'avons pas encore de python pour exécuter le script python que nous avons créé pour exécuter les utilitaires du Nexus.



Et c'est là que bash entre en jeu! Il n'est pas si effrayant, et même bon quand on s'y habitue. Et cela fonctionne partout: sous unix, tout va déjà bien, et sous Windows, il est installé avec git-bash (c'est notre seule exigence pour le système local). L'algorithme d'installation est très simple:



  1. Téléchargez l'archive python compilée pour la plate-forme requise. Le moyen le plus simple de le faire est d'utiliser curl - il est presque partout (même sous Windows ).



    Télécharger python
    mkdir -p "$PYTHON_PRIMARY_DIR"
    	curl "$PYTHON_NEXUS_URL" --output "$PYTHON_PRIMARY_DIR/ci_python.zip" --insecure || exit 1
          
          





  2. Décompressez-le, créez un environnement virtuel lié au binaire téléchargé. Ne répétez pas nos erreurs: n'oubliez pas de clouer la version virtualenv.



    echo "Unzip python..."
    unzip "$PYTHON_PRIMARY_DIR/ci_python.zip" -d "$PYTHON_PRIMARY_DIR" > "unzip.log"
    	rm -f "$PYTHON_PRIMARY_DIR/ci_python.zip"
    
    	echo "Create virtual environment..."
    "$PYTHON_EXECUTABLE" -m pip install virtualenv==16.7.9 --disable-pip-version-check --no-warn-script-location
          
          



  3. Si vous avez besoin de bibliothèques de lib / *, vous devez les copier vous-même. virtualenv n'y pense pas.
  4. Installez tous les packages requis. Ici, nous avons convenu avec les projets qu'ils auront un fichier ci / required.txt, qui contiendra toutes les dépendances au format pip .


Installer les dépendances
OUT_FILE="$VENV_DIR/pip_log.txt"
"$PYTHON_VENV_EXECUTABLE" -m pip install -r "$REQUIRED_FILE" >> "$OUT_FILE" 2>&1
result=$?
if [[ "$result" != "0" ]]; then
	var2=$(grep ERROR "$OUT_FILE")
	echo "$(tput setaf 3)" "$var2" "$(tput sgr 0)"
	echo -e "\e[1;31m" "Error while installing requirements. More details in: $OUT_FILE" "\e[0m"
	result=$ERR_PIP
fi
exit $result

      
      





Exemple Required.txt
pywin32==225;sys_platform == "win32"
cryptography==3.0.0
google-api-python-client==1.7.11

      
      





Lorsqu'ils résolvent un problème, ils joignent généralement une capture d'écran de la console où les erreurs ont été affichées. Pour faciliter notre travail, nous stockons non seulement la sortie de la dernière exécution de l' installation de pip , mais nous avons également ajouté des couleurs à la vie, affichant les erreurs de couleur du journal directement sur la console. Vive grep!



À quoi ça ressemble




À première vue, il peut sembler que nous n'avons pas besoin d'un environnement virtuel. Après tout, nous avons déjà téléchargé un binaire séparé, dans un répertoire séparé. Même s'il existe plusieurs dossiers dans lesquels notre système est déployé, les binaires sont toujours différents. Mais! Virtualenv a un script d' activation qui permet d'appeler python comme s'il se trouvait dans l'environnement global. Cela isole l'exécution des scripts et facilite leur lancement.



Imaginez: vous devez exécuter un fichier batch à partir duquel un script python s'exécute, à partir duquel un autre script python s'exécute. Ce n'est pas un exemple fictif - c'est ainsi que les événements post-build sont exécutés lors de la création d'une application. Sans virtualenv, vous auriez à calculer les chemins nécessaires partout à la volée, mais avec activatenous utilisons juste python partout . Plus précisément, vpython - nous avons ajouté notre propre wrapper pour faciliter l'exécution à la fois de la console et des scripts. Dans le shell, nous vérifions si nous sommes déjà dans l'environnement activé ou non, si nous fonctionnons sur TeamCity (où se trouve notre environnement virtuel), et en même temps nous préparons l'environnement.



vpython.cmd
set CUR_DIR=%~dp0
set "REPO_DIR=%CUR_DIR%\."

rem VIRTUAL_ENV is the variable from activate.bat and is set automatically
rem TEAMCITY - if we are running from agent we need no virtualenv activation
if "%VIRTUAL_ENV%"=="" IF "%TEAMCITY%"=="" (
	set RETURN=if_state
	goto prepare
	:if_state
	if %ERRORLEVEL% neq 0 (
		echo [31m Error while prepare environment. Run ci\PrepareAll.cmd via command line [0m
		exit /b 1
	)
	call "%REPO_DIR%\.venv\Scripts\activate.bat"
	rem special variable to check if venv activated from this script
	set VENV_FROM_CURRENT=true
)

rem Run simple python and forward args to it
python %*

SET result=%errorlevel%

if "%VENV_FROM_CURRENT%"=="true" (
	call "%REPO_DIR%\.venv\Scripts\deactivate.bat"
	set CI_VENV_RUN=
	set VENV_FROM_CURRENT=
)

:eof
exit /b %result%

:prepare
setlocal
set RUN_FROM_SCRIPT=true
call "%REPO_DIR%\ci\PrepareEnvironment.cmd" > NUL
endlocal
goto %RETURN%

      
      







Tanakan, ou n'oubliez pas de faire des tests



Nous avons résolu le problème de l'oubli lors de l'exécution des tests, mais même un script peut être ignoré. Par conséquent, ils ont fabriqué une pilule pour l'oubli. Il comporte deux parties.



Lorsque notre système démarre, il modifie le commentaire de validation et le marque comme "approuvé". En tant qu'étiquette, nous avons décidé de ne pas philosopher et d'ajouter [+] ou [-] à la fin du commentaire au commit.



Un script est en cours d'exécution sur le serveur qui analyse les messages, et s'il ne trouve pas le jeu de caractères convoité, il crée une tâche pour l'auteur. C'est la solution la plus simple et la plus élégante. Les caractères non imprimables ne sont pas évidents. Pour exécuter des hooks de serveur, vous avez besoin d'un plan tarifaire différent sur GitHub, et personne n'achètera de prime pour une fonctionnalité. Parcourir l'historique des commits, rechercher un symbole et définir une tâche est évident et pas si cher.



Oui, vous pouvez mettre un symbole avec vos propres stylos, mais êtes-vous sûr de ne pas casser l'assemblage sur le serveur? Et si vous le cassez ... oui, l'homme chauve de Homescapes vous suit déjà.



Quelle est la ligne de fond



Il est assez difficile de suivre le nombre d'erreurs détectées par les hooks - ils n'atteignent pas le serveur. Il n'y a qu'une opinion subjective qu'il y a beaucoup plus d'assemblées vertes. Cependant, il y a aussi un côté négatif - la validation a commencé à prendre un temps assez long. Dans certains cas, cela peut prendre jusqu'à 10 minutes, mais c'est une autre histoire sur l'optimisation.



All Articles