Wie kann man einen Spieledesigner davon überzeugen, Tests durchzuführen?

Ich denke, es ist für niemanden ein Geheimnis, dass viele Spezialisten an der Entwicklung von Spielen beteiligt sind und nicht nur Programmierer. Die Veröffentlichung eines Spiels ist ohne Künstler, Modellierer, VFX-Künstler und natürlich Spieledesigner nicht möglich. Übrigens über Letzteres. Wir lieben sie sehr, aber sie brechen oft Ressourcen. Nicht, dass sie es tun wollen, aber aufgrund der Art der Arbeit müssen sie viele kleine Änderungen vornehmen, und die Chance, Fehler zu machen, ist höher. Und schließlich sind viele Fehler triviale Tippfehler, eine unvollständige oder umgekehrt eine zusätzliche gelöschte Zeile. All dies kann korrigiert werden, ohne die Kasse zu verlassen. Aber wie geht das? Schreiben Sie in die Vorschriften, dass Sie% my_folder% / scripts / mega_checker ausführen müssen, bevor Sie sich verpflichten? Wir haben nachgesehen - es funktioniert nicht. Der Mensch ist eine komplexe und vergessliche Kreatur. Und ich möchte die Ressourcen überprüfen.



Aber wir haben einen Ausweg gefunden - jetzt ist es unmöglich, sich ohne Tests auf das Repository festzulegen. Zumindest unmerklich und ungestraft.







Testsystem



Das erste, was wir brauchen, ist ein Testsystem. Wir haben es hier bereits beschrieben. Denken Sie daran, dass Sie denselben Code benötigen, um auf dem Ci-Server und lokal ausgeführt zu werden, damit die Wartung problemlos möglich ist. Es ist wünschenswert, dass das Projekt verschiedene Parameter für gemeinsame Tests festlegen oder noch besser - mit eigenen erweitern kann. Natürlich kamen die Süßigkeiten nicht sofort heraus.



Bühne eins- Du kannst rennen, aber es tut weh. Was mit Python-Code zu tun ist, ist noch klar, aber mit allen möglichen Dienstprogrammen wie CppCheck, Bloaty, Optipng, unseren internen Krücken, Fahrrädern - nein. Um korrekt ausgeführt zu werden, benötigen wir ausführbare Dateien für alle Plattformen, auf denen unsere Kollegen arbeiten (Mac, Windows und Linux). Zu diesem Zeitpunkt befanden sich alle erforderlichen Binärdateien im Repository, und der relative Pfad zum Ordner "Binärdateien" wurde in den Einstellungen des Testsystems angegeben.



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





Dies wirft mehrere Probleme auf:



  • Auf der Projektseite müssen Sie unnötige Dateien im Repository speichern, da sie auf dem Computer jedes Entwicklers benötigt werden. Aus diesem Grund ist das Repository natürlich größer.
  • Wenn ein Problem auftritt, ist es schwierig zu verstehen, welche Version das Projekt hat und ob sich die erforderliche Struktur im Ordner befindet.
  • Woher bekomme ich die notwendigen Binärdateien? Kompilieren Sie sich, laden Sie im Internet herunter?


Stufe zwei - wir ordnen die Dinge in den Versorgungsunternehmen. Aber was ist, wenn Sie alle erforderlichen Dienstprogramme aufschreiben und in einem Repository sammeln? Die Idee ist, dass auf dem Server bereits Dienstprogramme für alle erforderlichen Plattformen zusammengestellt sind, die ebenfalls versioniert sind. Wir haben bereits Nexus Sonatype verwendet, sind also zur nächsten Abteilung gegangen und haben uns auf die Dateien geeinigt. Das Ergebnis ist eine Struktur: 





Zu Beginn benötigen Sie ein Skript, das die geheime Adresse der Binärdateien kennt, diese herunterladen und je nach Plattform mit den übergebenen Parametern ausführen kann.



Die Feinheiten der Implementierung weglassen
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)

      
      





Auf dem Server haben wir eine Datei mit einer Beschreibung der Dienstprogramme hinzugefügt. Die Adresse dieser Datei bleibt unverändert. Als erstes gehen wir dorthin und sehen, was wir auf Lager haben. Ohne Feinheiten sind dies die Paketnamen und der Pfad zur ausführbaren Datei im Paket für jede Plattform.



xml "auf dem Server"
<?xml version='1.0' encoding='utf-8'?>
<Tools>
	<CppCheck>
		<windows executable="cppcheck.exe" />
		<darwin executable="cppcheck" />
		<linux executable="cppcheck" />
	</CppCheck>
</Tools>

      
      







Fügen Sie dem Projekt eine Datei mit einer Beschreibung Ihrer Anforderungen hinzu.



XML-Projekt

<?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- — .


Aber wie starte ich?



Zuerst haben wir eine mehrseitige Anleitung erstellt, welche Croissants schmackhafter sind und welche Python installiert werden soll. Aber erinnern wir uns an Spieledesigner und Rührei? Es wurde immer ausgebrannt: entweder Python mit der falschen Bitness oder 2.7 statt 3.7. Und all dies wird auch mit zwei Plattformen multipliziert, auf denen Benutzer arbeiten: Windows und Mac. (Linux-Benutzer mit uns haben entweder Gurus und richten alles selbst ein, indem sie leise auf den Klang eines Tamburins tippen, oder sie haben das Problem



gelöst .) Wir haben das Problem radikal gelöst - wir haben Python mit der erforderlichen Version und Bitterkeit gesammelt. Und auf die Frage "Wie legen wir es ab und wo lagern wir es?" Antworteten sie: Nexus! Das einzige Problem: Wir haben noch kein Python, um das Python-Skript auszuführen, das wir zum Ausführen der Dienstprogramme vom Nexus aus erstellt haben.



Und hier kommt Bash ins Spiel! Er ist nicht so beängstigend und sogar gut, wenn man sich an ihn gewöhnt. Und es funktioniert überall: Unter Unix ist bereits alles in Ordnung, und unter Windows wird es zusammen mit Git-Bash installiert (dies ist unsere einzige Voraussetzung für das lokale System). Der Installationsalgorithmus ist sehr einfach:



  1. Laden Sie das kompilierte Python-Archiv für die gewünschte Plattform herunter. Der einfachste Weg, dies zu tun, ist durch Locken - es ist fast überall (sogar unter Windows ).



    Python herunterladen
    mkdir -p "$PYTHON_PRIMARY_DIR"
    	curl "$PYTHON_NEXUS_URL" --output "$PYTHON_PRIMARY_DIR/ci_python.zip" --insecure || exit 1
          
          





  2. Entpacken Sie es und erstellen Sie eine virtuelle Umgebung, die mit der heruntergeladenen Binärdatei verknüpft ist. Wiederholen Sie unsere Fehler nicht: Vergessen Sie nicht, die virtualenv-Version festzunageln.



    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. Wenn Sie Bibliotheken aus lib / * benötigen, müssen Sie diese selbst kopieren. virtualenv denkt nicht darüber nach.
  4. Installieren Sie alle erforderlichen Pakete. Hier haben wir uns mit den Projekten darauf geeinigt, dass sie eine ci / required.txt-Datei haben werden, die alle Abhängigkeiten im Pip- Format enthält .


Abhängigkeiten installieren
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

      
      





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

      
      





Wenn sie ein Problem beheben, fügen sie normalerweise einen Screenshot der Konsole hinzu, auf der die Fehler angezeigt wurden. Um unsere Arbeit zu vereinfachen, speichern wir nicht nur die Ausgabe des letzten Pip-Installationslaufs , sondern fügen dem Leben auch Farben hinzu, wobei Farbfehler aus dem Protokoll direkt auf der Konsole angezeigt werden. Es lebe grep!



Wie es aussieht




Auf den ersten Blick scheint es, dass wir keine virtuelle Umgebung benötigen. Immerhin haben wir bereits eine separate Binärdatei in ein separates Verzeichnis heruntergeladen. Selbst wenn es mehrere Ordner gibt, in denen unser System bereitgestellt wird, unterscheiden sich die Binärdateien immer noch. Aber! Virtualenv verfügt über ein Aktivierungsskript , mit dem Python so aufgerufen werden kann, als wäre es in der globalen Umgebung. Dies isoliert die Ausführung von Skripten und erleichtert das Starten.



Stellen Sie sich vor: Sie müssen eine Batchdatei ausführen, aus der ein Python-Skript ausgeführt wird, aus der ein anderes Python-Skript ausgeführt wird. Dies ist kein fiktives Beispiel. Auf diese Weise werden Post-Build-Ereignisse beim Erstellen einer Anwendung ausgeführt. Ohne virtualenv müssten Sie die erforderlichen Pfade überall im laufenden Betrieb berechnen, jedoch mit enableWir verwenden einfach überall Python . Genauer gesagt, vpython - wir haben unseren eigenen Wrapper hinzugefügt, um die Ausführung sowohl über die Konsole als auch über Skripte zu vereinfachen. In der Shell prüfen wir, ob wir uns bereits in der aktivierten Umgebung befinden oder nicht, ob wir in TeamCity (wo sich unsere virtuelle Umgebung befindet) ausgeführt werden, und bereiten gleichzeitig die Umgebung vor.



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, oder vergessen Sie nicht, Tests zu machen



Wir haben das Problem der Vergesslichkeit beim Ausführen von Tests gelöst, aber auch ein Skript kann übersehen werden. Deshalb machten sie eine Pille gegen Vergesslichkeit. Es besteht aus zwei Teilen.



Wenn unser System gestartet wird, ändert es den Festschreibungskommentar und markiert ihn als "genehmigt". Als Label haben wir beschlossen, nicht zu philosophieren und am Ende des Kommentars [+] oder [-] zum Commit hinzuzufügen.



Auf dem Server wird ein Skript ausgeführt, das Nachrichten analysiert. Wenn der begehrte Zeichensatz nicht gefunden wird, wird eine Aufgabe für den Autor erstellt. Dies ist die einfachste und eleganteste Lösung. Nicht druckbare Zeichen sind nicht offensichtlich. Um Server-Hooks auszuführen, benötigen Sie einen anderen Tarifplan für GitHub, und niemand kauft eine Prämie für eine Funktion. Es ist offensichtlich und nicht so teuer, die Geschichte der Commits durchzugehen, nach einem Symbol zu suchen und eine Aufgabe zu stellen.



Ja, Sie können ein Symbol mit Ihren eigenen Stiften platzieren, aber sind Sie sicher, dass Sie die Baugruppe auf dem Server nicht beschädigen werden? Und wenn du es kaputt machst ... ja, der Glatzkopf aus Homescapes folgt dir bereits.



Was ist das Endergebnis



Es ist ziemlich schwierig, die Anzahl der gefundenen Fehler zu verfolgen - sie gelangen nicht zum Server. Es gibt nur eine subjektive Meinung, dass es viel mehr grüne Versammlungen gibt. Es gibt jedoch auch eine negative Seite - das Commit begann ziemlich lange zu dauern. In einigen Fällen kann es bis zu 10 Minuten dauern, aber das ist eine andere Geschichte über die Optimierung.



All Articles