
开发具有 RestAPI 集成的 MQL5 强化学习代理(第 3 部分):在 MQL5 中创建自动移动和测试脚本
概述
本文是系列文章的第三篇,我们将深入探讨 REST API 在系统中的实际应用。我们已经看到 MQL5 函数的开发及其通过 FastAPI 与 Python 井字游戏的集成。在这篇新文章中,我们可以取得重大进展。我们将重点实现自动井字棋的移动,以提高游戏的难度和互动性。我们还将特别关注 MQL5 测试脚本的开发,以确保集成系统的可靠性和效率。
考虑到该项目的复杂性,似乎有必要提供明确的安装和执行指南。我很抱歉没有在之前的部分中提供这些信息,并感谢您的理解。因此,我将提供一个分步指南,包括针对可能遇到脚本限制的 Windows 用户的建议。
安装和实现指南
先决条件:
- Python 3.6 或更高版本。
- 电脑上已安装 MetaTrader 5。
- 在 Windows 系统中,确保允许使用脚本。如有必要,以管理员身份在 PowerShell 中运行Set-ExecutionPolicy RemoteSigned,以允许脚本运行。
安装和实现步骤:
- 下载并解压项目。下载文章项目后,将文件解压到所需文件夹。
- 将其复制到 Experts 文件夹。将提取的文件夹移至 MetaTrader 终端中的 Experts 文件夹。
- 在终端中打开文件夹。
- 在 Windows 系统中,您可以在 "开始" 菜单中搜索 "命令提示符" 或 PowerShell,打开程序并使用 cd folder_path 命令导航到相应的项目文件夹。
- 在 MacOS 或 Linux 上,打开终端并运行cd folder_path 命令。
- 创建虚拟环境。在终端中打开项目文件夹,运行python -m venv env创建虚拟环境。
- 激活虚拟环境。在 Windows 系统中,运行env\Scripts\activate 命令。在 MacOS 或 Linux 上,运行 env/bin/activate 命令。
- 安装依赖项。启用虚拟环境后,运行pip install -r requirements.txt。
项目实现:
- 要运行井字游戏 API,请打开项目文件夹下的终端,运行python AppTicTacToe.py。
- 要在 MetaTrader 中执行,请打开 MetaEditor,进入菜单 "工具">"选项">"编译器"。将虚拟环境中 "script" 文件夹的路径粘贴到 "外部编译位置",点击编译按钮和/或将脚本拖到图表上。
- 访问 Swagger UI。在浏览器中访问 localhost:8000/docs,通过 Swagger UI 与 API 交互。
我们目前的工作有两个主要目标。首先,改进 Python 中的井字游戏,使其能够利用智能决策算法自主执行移动。其次,在 MQL5 中开发和实现单元测试,检查并确保 MQL5 代码与 REST API 之间交互的可靠性。
本文由三个部分组成:
- 开发井字游戏中的自动移动,介绍了修改游戏以添加自动移动逻辑的过程,包括使用的编程方法和遇到的困难。
- 用 MQL5 语言创建测试脚本,我们将了解用 MQL5 开发单元测试的过程,重点是测试 MQL5 与 REST API 之间交互的方法。
- 实际和集成测试,在此期间我们将看到已实施改进的集成,包括测试和结果评估。
因此,使用 MQL5 开发一个能与井字游戏互动的代理就成了下一个合乎逻辑的步骤。该代理可以模仿真实用户,在游戏中执行动作并对动作做出反应,从而创建一个接近现实的测试环境。通过这一策略,我们不仅可以测试游戏和 API 的功能,还可以研究(并改进)自动游戏中的决策算法,从而提供更复杂、更吸引人的游戏体验。
把 MQL5 中的自动井字游戏和单元测试相结合,创造了一个强大的开发周期,游戏中的每项改进都要经过严格的测试和完善。持续的开发和测试过程可确保创建一个可靠、高效的集成系统,该系统不仅能提供更好的游戏体验,还能为未来集成和开发需要集成的系统提供有价值的见解。
开发自动移动
本节将重点了解现有游戏代码的结构和逻辑。由于游戏没有图形界面,也不会使用复杂的决策算法来实现自动移动,我们的目标是简化和优化这一过程。
第一步是分析游戏如何处理手动移动,研究驱动移动的逻辑以及如何决定状态:赢、输或平局。要在不破坏现有游戏机制的情况下集成自动移动功能,就必须了解这一过程。
今后,自动移动将以更简单的方式实现。与复杂的算法相比,我们可以选择一种更直接的方法,比如在棋盘上随机选择一个空闲位置进行自动比赛。这种方法虽然简单,但足以模仿对手,使游戏充满活力。
让我们仔细研究一下旧游戏的代码,试着了解它是如何工作的。这对于计划如何在不使游戏复杂化的情况下增加自动移动非常必要。我们的目标是在坚持游戏已有简洁风格的同时,让一切都变得用户友好。
游戏初始化:
def __init__(self): self.board = [[' ' for _ in range(3)] for _ in range(3)] self.player_turn = True
在这里,游戏以空棋盘(self.board)进行初始化,而变量 self.player_turn 则表示玩家的回合。简约的设计可以为集成自动游戏逻辑提供一个理想的起点,而不会使现有代码复杂化。
棋盘的显示:
def print_board(self): for row in self.board: print("|".join(row)) print("-" * 5)print_board 方法用于处理游戏棋盘的显示。游戏状态的呈现方式对于保持游戏的可用性和易懂性至关重要,尤其是在引入自动移动之后。
检查获胜者
def check_winner(self): for i in range(3): if self.board[i][0] == self.board[i][1] == self.board[i][2] != ' ': return self.board[i][0] if self.board[0][i] == self.board[1][i] == self.board[2][i] != ' ': return self.board[0][i] if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ': return self.board[0][0] if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ': return self.board[0][2] return None
每场比赛后,都需要用这个方法来判断胜负。无论是手动游戏还是自动游戏,在检查游戏是否完成时都需要它的逻辑。
执行移动:
def make_move(self, row, col): if self.board[row][col] == ' ': if self.player_turn: self.board[row][col] = 'X' else: self.board[row][col] = 'O' self.player_turn = not self.player_turn else: print("Invalid move. Try again.")
make_move 方法负责玩家的移动。要实现自动移动,就必须修改这个方法,以便在手动和自动移动之间进行切换。
执行自动移动:
在井字游戏中实现自动移动是一项新的挑战和乐趣。让我们尝试使用名为 machine_move 的新方法来实现它。首先,它会想办法获胜或阻止对方获胜。如果没有这样的机会,它会在棋盘上随机选择一个空位。
def machine_move(self): for i in range(3): for j in range(3): if self.board[i][j] == ' ': # First try to find a winning move for 'O' self.board[i][j] = 'O' if self.check_winner() == 'O': return (i, j) # Return position for win self.board[i][j] = ' ' # Then try to block a winning move for 'X' self.board[i][j] = 'X' if self.check_winner() == 'X': self.board[i][j] = 'O' # Block the win of the player return (i, j) self.board[i][j] = ' ' # If there are no winning moves, randomly choose a free position available_moves = self.available_moves() if available_moves: move = random.choice(available_moves) self.board[move["row"]][move["col"]] = 'O' return (move["row"], move["col"])
我们还将有一个方法 available_moves。这个方法非常重要,因为它可以让您查看棋盘并显示所有空位。这样,我们就能保证计算机只在空单元格中移动。
def available_moves(self): moves = [] for i in range(3): for j in range(3): if self.board[i][j] == ' ': moves.append({"row": i, "col": j}) return moves
由于这些变化,井字游戏变得更加有趣。游戏依然简单,但由于多了自动移动,现在又增加了惊喜和策略元素。这让比赛变得更加精彩。
将自动移动整合到 Python 井字游戏中,增加了游戏的复杂性和互动性,为未来的高级实现(如 MQL5 代理的实现)铺平了道路。我们的想法是,在这一开发阶段结束时,游戏不仅对玩家来说会变得更加复杂,而且还能与其他外部代理进行更复杂的互动。
虽然自动移动的逻辑最初很简单,但它为游戏奠定了坚实的基础。由于该系统能够执行自主移动并对游戏环境做出动态响应,因此适合模拟真实的对手。这是测试游戏在各种情况下的有效性和稳定性所必需的,特别是为将来实现 MQL5 代理做准备。
在计划实现该代理时,自动移动的井字游戏已经具备了模拟真实游戏环境的能力。代理将能够与游戏互动,通过一个近似于两个人类玩家之间游戏的脚本,做出动作并对自动操作做出回应。通过这种互动,我们不仅可以评估游戏和 API 的功能,还可以评估所使用的决策算法的有效性,从而为改进和修正开辟道路。
此外,MQL5 代理的存在将提供更高级、更真实的测试环境。这将使我们能够模拟不同的游戏场景,测试系统在不同条件下的反应,并确保游戏的稳定性和可靠性。
以下是完整的游戏代码:
class TicTacToe: def __init__(self): self.board = [[' ' for _ in range(3)] for _ in range(3)] self.player_turn = True def print_board(self): for row in self.board: print("|".join(row)) print("-" * 5) print(f"Player {self.player_turn}'s turn") def check_winner(self): for i in range(3): if self.board[i][0] == self.board[i][1] == self.board[i][2] != ' ': return self.board[i][0] if self.board[0][i] == self.board[1][i] == self.board[2][i] != ' ': return self.board[0][i] if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ': return self.board[0][0] if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ': return self.board[0][2] return None def machine_move(self): for i in range(3): for j in range(3): if self.board[i][j] == ' ': self.board[i][j] = 'O' if self.check_winner() == 'O': return (i, j) self.board[i][j] = ' ' self.board[i][j] = 'X' if self.check_winner() == 'X': self.board[i][j] = 'O' return (i, j) self.board[i][j] = ' ' for i in range(3): for j in range(3): if self.board[i][j] == ' ': self.board[i][j] = 'O' return (i, j) def available_moves(self): moves = [] for i in range(3): for j in range(3): if self.board[i][j] == ' ': moves.append({"row": i, "col": j}) return moves
既然我们已经用 Python 实现了井字游戏中的自动移动,我们就需要将此功能与我们的 FastAPI 相集成。这一步骤对于确保游戏与后台之间高效、无缝的互动十分必要,它为未来的集成(如实施 MQL5 代理)铺平了道路。
为了让 API 支持自动移动,我们需要对代码进行一些重要的修改。现在让我们详细介绍此集成所需的步骤。
-
改进的轮次管理:API 应能正确判断轮到谁玩,是玩家还是机器。在每个棋手走完一步后,API 必须检查是否轮到机器,如果是,则启动自动回合逻辑。
-
与 machine_move 逻辑整合:井字游戏代码中的 machine_move 函数是自动移动所必需的。因此,每次玩家移动后,API 必须调用此方法来确定机器的反应。
-
持续更新游戏状态信息:每次回合结束后,无论是玩家还是机器,都必须更新 API,以准确反映游戏棋盘的状态。这样就能确保玩家始终获得最新、最准确的游戏进度信息。
-
游戏结果处理:API 应能确定游戏的结束(是胜还是平),并做出相应的报告。非常重要的一点是,API 必须提供明确的胜负信息,或者在无法获胜的情况下宣布和棋。
-
明确而有意义的回复:API 必须提供所有必要的响应数据,如游戏场地的当前状态、机器所走的棋步以及游戏结果(如有)。所有这一切都确保了流畅和信息丰富的用户体验。
API 实现示例
为了适应这些变化,我们将更改 API 中的 play 函数:
@app.post("/play/{game_id}/") def play(game_id: int, move: PlayerMove): game = games.get(game_id) if not game: raise HTTPException(status_code=404, detail="Game not found") board = game.board if board[move.row][move.col] == ' ': board[move.row][move.col] = 'X' else: raise HTTPException(status_code=400, detail="Invalid move") player_move = {"row": move.row, "col": move.col, "symbol": 'X'} winner = game.check_winner() machine_move_result = None if not winner: game.player_turn = not game.player_turn if not game.player_turn: move_result = game.machine_move() if move_result: row, col = move_result machine_move_result = {"row": row, "col": col, "symbol": 'O'} winner = game.check_winner() game.player_turn = not game.player_turn return { "board": board, "player_move": player_move, "machine_move": machine_move_result, "winner": winner, "available_moves": game.available_moves() }
我们用 Python 实现了井字游戏中的自动移动功能,并调整了 FastAPI 以支持该功能,从而创建了一个完整的互动游戏系统。现在,API 已配置为处理自动移动,玩家可以以更动态的方式与游戏互动。当玩家下棋时,API 会检查是否轮到机器下棋,如果是,则激活自动下棋逻辑。这就创造了一种持续互动的游戏体验,人类与人工智能竞争以获得胜利。
此外,API 还提供有关游戏当前状态的详细信息,包括更新过的棋盘、机器走过的棋步,以及游戏结果,即一方获胜还是平局。这让玩家的体验更具吸引力,信息量更大。
在 MQL5 中创建测试脚本
在上一篇文章中,我们学习了如何在 MQL5 中创建和管理 HTTP 请求。现在,我们将运用这些知识来开发稳健的单元测试。我们设计每个测试函数来模拟 MQL5 代码与 REST API 之间的实际交互场景,确保交互的各个方面都得到测试和验证。
这些测试包括从游戏初始化到执行正确和错误的移动,以及检查获胜条件等各个方面。这也将为今后的扩展和整合奠定坚实的基础。
在本主题结束时,读者将清楚地了解单元测试在 MQL5 中的结构和实现方式,以及单元测试对系统开发的重要性。
测试结构
MQL5 中的测试代码主要分为三个文件:
- Tests.mqh 包含测试函数。
- Request.mqh 处理 HTTP 请求。
- Tests.mq5 是运行测试的主脚本。
Tests.mqh
- Assert():该函数用于检查某个条件是否为真。确认预期的测试结果非常重要。
代码和解释:
void Assert(bool condition, const string message) { if(!condition) { Print("Test error: ", message); } }
如果传递给 Assert 的条件为假,则会打印错误信息。这有助于您快速识别测试失败。
- TestGameInitialisation():检查新游戏的初始化,确保 API 响应正确。
void TestGameInitialization() { string url = "http://localhost:8000/start-game/"; string response; int result = Request("GET", response, url); Assert(result == 200, "Game initialization failed"); Assert(StringLen(response) > 0, "game_id missing in game initialization response"); }This function makes a GET request to start a game and checks if the response code is 200 (OK) and if game_id is returned in the response.
该函数发出 GET 请求以启动游戏,并检查响应代码是否为 200(OK),以及 game_id 是否在响应中返回。
-
TestPlayerMove():测试玩家正确移动的函数。
// Test function to check player's move void TestPlayerMove() { string url = "http://localhost:8000/start-game/"; string response; int result = -1; int game_id = -1; Request("GET", response, url); js.Deserialize(response); game_id = js["game_id"].ToStr(); // Make a valid player move url = StringFormat("http://localhost:8000/play/%d/", game_id); string payload = "{\"row\": 0, \"col\": 0}"; result = Request("POST", response, url, payload); // Check if the HTTP response code is 200 (OK) Assert(result == 200, "Player move failed"); // Check if the response contains information about the player's move // (you can adjust this based on the actual response structure) Assert(StringFind(response, "player_move") != -1, "Player move response incomplete"); }
游戏开始后,该函数会发出有效的移动,并检查应用程序接口是否正确处理了移动,返回代码 200,并在响应主体中返回移动信息。
- TestInvalidPlayerMove():检查 API 响应是否存在无效移动。
代码和解释:
// Test function to check an invalid player move void TestInvalidPlayerMove() { string url = "http://localhost:8000/start-game/"; string response; int result = -1; int game_id = -1; Request("GET", response, url); js.Deserialize(response); game_id = js["game_id"].ToStr(); // Make an invalid player move (e.g., on an occupied position) url = StringFormat("http://localhost:8000/play/%d/", game_id); string payload = "{\"row\": 0, \"col\": 0}"; Request("POST", response, url, payload); //repeat payload = "{\"row\": 0, \"col\": 0}"; result = Request("POST", response, url, payload); // Check if the HTTP response code is 400 (Bad Request) Assert(result == 400, "Invalid player move not handled correctly"); }
-
TestPlayerWin():模拟棋手获胜的一系列棋步。
// Test function to check player's victory void TestPlayerWin() { string url = "http://localhost:8000/start-game/"; string response; int result = -1; int game_id = -1; Request("GET", response, url); js.Deserialize(response); game_id = js["game_id"].ToStr(); // Make moves for player X to win url = StringFormat("http://localhost:8000/play/%d/", game_id); string payload = "{\"row\": 0, \"col\": 0}"; result = Request("POST", response, url, payload); Assert(result == 200, "Player X move 1 failed"); payload = "{\"row\": 0, \"col\": 2}"; result = Request("POST", response, url, payload); Assert(result == 200, "Player X move 2 failed"); payload = "{\"row\": 2, \"col\": 2}"; result = Request("POST", response, url, payload); Assert(result == 200, "Player X move 3 failed"); payload = "{\"row\": 1, \"col\": 2}"; result = Request("POST", response, url, payload); Assert(result == 200, "Player X move 4 failed"); // Check if the response contains information about the winner js.Deserialize(response); // Deserialize the updated game state // Check if the HTTP response code is 200 (OK) after move 5 Assert(result == 200 && js["winner"].ToStr() == "X", "Player X victory failed"); }
该函数会执行一系列会导致玩家获胜的移动,同时还会检查 API 是否能正确识别获胜条件。
- RunTests():运行所有已定义测试的聚合函数。
// Function to run all tests void RunTests() { TestGameInitialization(); TestPlayerMove(); TestInvalidPlayerMove(); TestPlayerWin(); }
该函数只需调用之前描述的所有测试函数,一次性运行所有测试。
之前我们创建了 Requests 库,用于 MQL5 代码和 REST API 之间的交互。在本文中,我们将继续使用该库,但会对其进行一些重大改进,以提高其功能和效率。
对 Requests 库所作的更改
新实现的 Requests 库有所改进,主要是在灵活性和错误诊断方面。让我们来看看主要的变化:
-
启用调试选项:
- 旧实现:在以前的版本中,该库不提供调试选项。任何调试输出都必须手动执行。
- SendGetRequest 和 SendPostRequest 函数的实现现在都包含一个调试选项。启用该选项后,可以打印调试数据,从而更容易跟踪和排除故障。
-
改进了错误处理:
- 旧实现:错误处理比较简单,只返回错误代码或直接打印到控制台。
- 新版本提供了更复杂的错误处理功能,能让你更好地诊断 HTTP 通信问题。
为什么要做这些改动?对 Requests 库进行改动的动机是:
- 改进调试:打开或关闭调试功能可让开发人员快速分析 API 响应并发现问题,从而使开发和维护工作变得更轻松。
- 改进错误管理:更有效的错误处理对于可靠的系统至关重要,尤其是在网络通信方面,因为在网络通信中可能会出现各种各样的问题。
改动的影响
这些改进使请求库的功能更全面、更可靠。借助调试功能,开发人员在开发和测试应用程序时可以更快地获得反馈。此外,更高效的错误处理有助于识别和解决 API 交互问题,从而实现更顺畅、更可靠的集成。
改动 1:启用调试选项
SendGetRequest和SendPostRequest 以前的实现:
// Example of old SendGetRequest implementation int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000) { // ... code ... out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8); return (0); }
带调试功能的新实现方案:
// Example of new SendGetRequest implementation int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false) { // ... code ... out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8); if(debug) { Print(out); } return res; }
新实现方案包括一个调试选项。如果为真,函数就会打印结果,从而方便调试。
改动 2:改进错误处理
SendGetRequest 和 SendPostRequest 以前的实现:
// Example of old implementation of SendGetRequest int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000) { // ... code ... if(res == -1) { return (_LastError); } else { // HTTP error handling out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); Print(out); return (ERR_HTTP_ERROR_FIRST + res); } }
改进了错误处理的新实现:
// Example if new implementation of SendGetRequest int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false) { // ... code ... if(res == -1) { return (_LastError); } else { // HTTP error handling out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); if(debug) { Print(out); } return res; } }
在新的实现中,HTTP 错误处理变得更加精确,只有在启用调试模式时才会输出数据。
集成与实践测试
在项目的这一关键阶段,我们将演示在 Requests 库中实现的改进与 Python 井字游戏中自动移动的集成。此外,我们还将进行实际测试,以评估整个系统的效率和可靠性。这一步骤对于确保我们系统的所有部分都能顺利可靠地工作是必要的。
集成阶段
在开始测试之前,了解新功能是如何集成的非常重要。下面介绍为确保顺利集成而采取的主要步骤:
-
更新 Requests 库:首先,我们更新了 Requests 库,以包含我们实现的改进。这使得通信更加高效。
-
井字游戏改进:其中重要的一步是将自动移动功能集成到 Python 的井字游戏中。我们修改了游戏代码,使其能够识别机器的回合并启动自动移动逻辑。
-
实现 API 集成:接下来,我们对 FastAPI 进行了调整,以支持自动移动。这包括改进回合管理、与 machine_move 函数集成、持续更新游戏状态以及正确处理游戏结果。
进行测试
现在,集成已经完成,是时候进行一些实际测试,以确保一切按预期运行。我们进行了一系列测试,涵盖了实现工作的各个方面:
-
新游戏创建测试(Swagger 和测试脚本):为确保新游戏创建工作顺利进行,我们将通过 Swagger 界面和自动测试脚本进行测试。这确保了两种方式都能提供功能。
-
玩家移动测试(Swagger 和测试脚本):将使用 Swagger 和测试脚本测试玩家有效移动的功能。
-
玩家无效移动测试:为确保 API 能正确处理无效移动,我们将运行一个测试,尝试向已占据的位置移动,检查 API 是否返回相应的错误代码。
-
玩家获胜测试:我们将模拟一连串的移动,让玩家获胜,以确保 API 能正确识别获胜条件。
成果与评估
在进行这些测试后,我们预计将获得可靠的结果,以确认我们的集成系统的完整性。我们将评估自动游戏是否按预期运行,是否按预期处理错误,以及 API 是否提供清晰翔实的响应。
这些实际测试对于确保我们的系统为现实世界的互操作性和未来扩展做好准备十分必要。通过成功的集成和全面的测试,我们离创建一个可靠、高效的游戏系统越来越近了。
在上面的图片中,您可以看到创建新游戏的功能测试过程,这在我们的应用程序中起着重要作用。测试以两种不同的方式全面而连贯地进行:通过 Swagger 界面和我们的自动测试脚本,解决功能的可访问性问题。
Swagger 是一个 API 文档和测试工具,它允许开发人员直观有效地与 API 进行交互。在图片中,您可以看到如何直接通过 Swagger 界面启动和测试新游戏的创建,从而确保功能易于访问并按预期运行。
此外,测试过程还包括使用自动测试脚本,执行严格的检查,以确保功能的一致性和可靠性。这表明,无论采用哪种测试方法,我们都致力于保持系统质量。
结论
这篇文章是 REST API 系列的续篇,主要介绍用 Python 实现和测试井字游戏中的自动移动,并与 MQL5 函数的开发集成。主要任务有两个方向:使用自动化游戏改进井字游戏;使用 MQL5 开发单元测试,以测试与 REST API 的交互。集成这些功能不仅能改进游戏本身,还能为更完整、更有效的测试奠定基础。
在开发自动移动时,了解游戏的现有逻辑非常重要,这样才能有效、和谐地实现新功能。所选择的策略是一种简单而有效的方法,可以让游戏变得更加复杂和动态。此外,为模拟真实用户的 MQL5 代理的实现准备环境,也是实现更逼真测试的重要一步。
在测试方面,MQL5 中测试脚本的创建和实施确保了 MQL5 代码与 REST API 之间的可靠连接。测试包括初始化游戏、执行正确和错误的移动,以及检查获胜条件:这对于确保系统的可靠性和稳定性十分必要。
在 Requests 库中实现的改进,如启用调试选项和更复杂的错误处理,大大提高了交互效率,也使诊断问题变得更容易。
最后,集成和实际测试阶段证实了在井字游戏和 Requests 库中实施的改进措施的有效性。使用 Swagger 界面和自动测试脚本进行的测试证实了整个系统的功能性和可靠性。
本文展示了 MQL5 如何将自动游戏和单元测试结合起来,创建一个强大的开发周期,提供一个可靠、高效的集成系统,能够提供增强的游戏体验,并为未来的集成和开发提供有价值的见解。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/13813

