Como convencer um designer de jogos a fazer testes?

Acho que não é segredo para ninguém que muitos especialistas estão envolvidos no desenvolvimento de jogos, e não apenas programadores. O lançamento de um jogo é impossível sem artistas, modeladores, artistas VFX e, claro, designers de jogos. A propósito, sobre o último. Nós os amamos muito, mas muitas vezes eles quebram recursos. Não que eles queiram fazer isso, mas devido à natureza do trabalho, eles precisam fazer várias pequenas edições, e a chance de estragar é maior. E, afinal, muitos erros são erros de digitação triviais, uma linha incompleta ou, ao contrário, uma linha extra excluída. Tudo isso pode ser corrigido sem sair do checkout. Mas como fazer isso? Escreva nos regulamentos que você deve executar% my_folder% / scripts / mega_checker antes de confirmar? Verificamos - não funciona. O homem é uma criatura complexa e esquecida. E eu quero verificar os recursos.



Mas encontramos uma saída - agora é impossível fazer commit no repositório sem testes. Pelo menos imperceptivelmente e com impunidade.







Sistema de teste



A primeira coisa de que precisamos é um sistema de teste. Já o descrevemos aqui. Lembre-se de que você precisa do mesmo código para ser executado no servidor Ci, e localmente, para que não haja dificuldade na manutenção. É desejável que o projeto seja capaz de definir vários parâmetros para testes comuns, ou ainda melhor - estendê-los com os seus próprios. Claro, o doce não saiu imediatamente.



Estágio um- você pode correr, mas dói. O que fazer com o código python ainda está claro, mas com todos os tipos de utilitários como CppCheck, Bloaty, optipng, nossas muletas internas, bicicletas - não. Para funcionar corretamente, precisamos de arquivos executáveis ​​para todas as plataformas em que nossos colegas trabalham (mac, windows e linux). Neste estágio, todos os binários necessários estavam no repositório e o caminho relativo para a pasta de binários foi indicado nas configurações do sistema de teste.



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





Isso apresenta vários problemas:



  • do lado do projeto, é necessário armazenar arquivos desnecessários no repositório, pois são necessários no computador de cada desenvolvedor. Naturalmente, o repositório é maior por causa disso.
  • quando surge um problema, é difícil entender qual versão o projeto possui, se a estrutura necessária está na pasta.
  • onde obter os binários necessários? Compile-se, baixe na Internet?


Estágio dois - colocamos as coisas em ordem nos utilitários. Mas e se você escrever todos os utilitários necessários e reuni-los em um repositório? A ideia é que no servidor já existam utilitários montados para todas as plataformas necessárias, que também são versionadas. Já usamos Nexus Sonatype, então fomos para o próximo departamento e combinamos os arquivos. O resultado é uma estrutura: 





Para começar, você precisa de um script que conheça o endereço secreto onde estão os binários, possa baixá-los e também executar, dependendo da plataforma, com os parâmetros passados.



Omitindo os meandros da implementação
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)

      
      





No servidor, adicionamos um arquivo com a descrição dos utilitários. O endereço desse arquivo não foi alterado, então a primeira coisa que fazemos é ir até lá e ver o que temos em estoque. Omitindo sutilezas, esses são os nomes dos pacotes e o caminho para o arquivo executável dentro do pacote para cada plataforma.



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

      
      







E no projeto, adicione um arquivo com a descrição do que você precisa.



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


Mas ainda assim, como lançar?



Primeiro, fizemos uma instrução de várias páginas sobre quais croissants são mais saborosos e quais python devem ser instalados. Mas será que nos lembramos dos designers de jogos e dos ovos mexidos? Sempre foi queimado: ou python da bitness errada ou 2.7 em vez de 3.7. E tudo isso também é multiplicado por duas plataformas onde os usuários trabalham: windows e mac. (Os usuários de Linux conosco ou gurus e configuram tudo sozinhos, batendo silenciosamente ao som de um pandeiro, ou passam o problema.)



Resolvemos o problema radicalmente - coletamos python da versão necessária e bitness. E à pergunta "como o colocamos e onde armazená-lo" eles responderam: Nexus! O único problema: não temos o python ainda para executar o script python que fizemos para executar os utilitários do Nexus.



E é aí que entra o bash! Ele não é tão assustador e até bom quando você se acostuma com ele. E funciona em qualquer lugar: no unix já está tudo bem, e no Windows é instalado junto com o git-bash (este é nosso único requisito para o sistema local). O algoritmo de instalação é muito simples:



  1. Baixe o arquivo python compilado para a plataforma necessária. A maneira mais fácil de fazer isso é por meio do curl - está quase em todos os lugares (até no Windows ).



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





  2. Descompacte-o e crie um ambiente virtual com um link para o binário baixado. Não repita os nossos erros: não se esqueça de acertar na versão 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. Se você precisar de alguma biblioteca de lib / *, será necessário copiá-la você mesmo. virtualenv não pensa nisso.
  4. Instale todos os pacotes necessários. Aqui concordamos com os projetos que eles terão um arquivo ci / required.txt, que conterá todas as dependências no formato pip .


Instalando dependências
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

      
      





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

      
      





Quando eles abordam um problema, geralmente anexam uma captura de tela do console onde os erros foram exibidos. Para tornar nosso trabalho mais fácil, não apenas armazenamos a saída da última execução de instalação do pip , mas também adicionamos cores à vida, exibindo os erros de cor do log diretamente para o console. Viva o grep!



Como fica




À primeira vista, pode parecer que não precisamos de um ambiente virtual. Afinal, já baixamos um binário separado, em um diretório separado. Mesmo se houver várias pastas onde nosso sistema está implantado, os binários ainda são diferentes. Mas! Virtualenv possui um script de ativação que o torna capaz de chamar o python como se estivesse no ambiente global. Isso isola a execução de scripts e facilita sua inicialização.



Imagine: você precisa executar um arquivo em lote a partir do qual um script Python é executado, a partir do qual outro script Python é executado. Este não é um exemplo fictício - é assim que os eventos pós-construção são executados durante a construção de um aplicativo. Sem o virtualenv, você teria que calcular os caminhos necessários em todos os lugares na hora, mas com ativarnós apenas usamos python em todos os lugares . Mais precisamente, vpython - adicionamos nosso próprio wrapper para facilitar a execução tanto do console quanto de scripts. No shell, verificamos se já estamos no ambiente ativado ou não, se estamos rodando no TeamCity (onde está nosso ambiente virtual), e ao mesmo tempo preparamos o ambiente.



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ão se esqueça de fazer testes



Resolvemos o problema de esquecimento para a execução de testes, mas até mesmo um script pode ser esquecido. Portanto, eles fizeram uma pílula para o esquecimento. Tem duas partes.



Quando nosso sistema é inicializado, ele modifica o comentário de confirmação e o marca como "aprovado". Como rótulo, decidimos não filosofar e adicionar [+] ou [-] no final do comentário ao commit.



No servidor que analisa as mensagens está rodando um script e, se não encontrar o desejado conjunto de caracteres, cria uma tarefa para o autor.Esta é a solução mais simples e elegante. Os caracteres não imprimíveis não são óbvios. Para executar ganchos de servidor, você precisa de um plano de tarifa diferente no GitHub, e ninguém vai comprar premium para um recurso. Percorrer a história dos commits, procurar um símbolo e definir uma tarefa é óbvio e não tão caro.



Sim, você pode colocar um símbolo com suas próprias canetas, mas tem certeza de que não quebrará a montagem no servidor? E se quebrar ... sim, o careca do Homescapes já está te seguindo.



Qual é o resultado final



É bastante difícil rastrear o número de erros que os ganchos encontraram - eles não chegam ao servidor. Existe apenas uma opinião subjetiva de que existem muito mais assembleias verdes. Porém, também há um lado negativo - o commit começou a demorar muito. Em alguns casos, pode levar até 10 minutos, mas essa é uma outra história sobre otimização.



All Articles