🐍

AtCoderのテストと提出を自動化する【Python × VSCode】

2024/10/26に公開

AtCoderのテストを自動化し、さらに自動で提出できるように環境を整えました。

1. 目的

  • テストをするときにいちいちコピペをするのが面倒なので、テストを自動化したい。
  • あわよくば、提出もコマンドラインで行いたい。
  • できるだけシンプルに(当社比)行いたい。

2. 課題

  • 参考になりそうな記事はどれも3年以上前のもので、古い。
  • 自分の環境にぴったり合う記事はないので、試行錯誤する必要がある。
  • システムケースをダウンロードしたいが、参考になる記事が少ない。

3. 環境

  • Windows 11
  • Visual Studio Code 1.94.2
  • Python 3.11.3 (.venv)

4. テストと提出を自動化する

1. 「AtCoder」フォルダを作る。

  • 解答コードは「abc001_a.py」のように「コンテスト名_問題名.py」としておきます。

    親フォルダ/
    ├── .venv/                    # 仮想環境(ある場合)
    └── AtCoder/
        ├── script/               # メインのプログラムファイルを格納
        └── src/
        │   └── abc001_a.py       # 問題の解答コード
        └── test/                 # コンテストのサンプルテストケースを格納
    

2. online-judge-toolsをインストールする

  • インストール後、AtCoderにログインします。

    pip install online-judge-tools
    oj login -u {ユーザー名} -p {パスワード} "https://atcoder.jp/"
    oj login --check "https://atcoder.jp/"
    

3. scriptフォルダの下に「cptest.ps1」ファイルを作る

  • フォルダ構造は以下のようになります。

    親フォルダ/
    ├── .venv/                    
    └── AtCoder/
        ├── script/
        │   └── cptest.ps1        
        └── src/
        │   └── abc001_a.py       
        └── test/                 
    
  • ここで、Dropboxのアクセストークンを取得する必要があります。

    • 取得方法
    1. Login - Dropbox で新しいアプリを作ります。(名前や設定はなんでもOK)
    2. アプリを作ったら、Permissionsタブへ行き、files.metadata.readsharing.readを有効にします。
    3. Settingsタブへ戻って、OAuth 2 の欄の「Generate」ボタンを押します。
  • cptest.ps1」ファイルには以下のように書きます。

    コードと説明
    1. 仮想環境を有効化する
    2. 引数を取得する
    3. testフォルダ下に対象とするフォルダが存在しない場合、テストケースをダウンロードする。
      • import_test.pyは次のステップで作成します。
    4. 時間制限を取得する。
    5. テストを実行する。
    6. 全てACとなると提出される。(確認メッセージが出る)
    7. 問題が解き終わると、テストケースを削除する。
    # 仮想環境をアクティベート (存在する場合)
    . ".venv/Scripts/Activate.ps1"  # 仮想環境がある場合はアクティベート
    
    # 引数から問題名を取得
    $problem_name = $Args[0]        # スクリプト実行時に渡された問題名を取得
    
    # コンテストURLとテストケース格納ディレクトリを生成
    $base_url = ($problem_name.Replace("_", "-")).Substring(0, ($problem_name.Length) - 2)   # 問題名からベースURLを生成 (例: abc001_a -> abc001)
    $test_dir = "Atcoder/test/$problem_name"                                                 # テストケースを格納するディレクトリのパス
    
    # Dropboxアクセストークン (セキュリティ上、実際のトークンは伏字にしています)
    $dropbox_token = "XXXXX"  # (伏字)
    
    # 問題のURLを生成
    $url = "https://atcoder.jp/contests/$base_url/tasks/$problem_name"
    
    # テストケースのフォルダが存在しなければ作成し、テストケースをダウンロード
    if (! (Test-Path $test_dir)) {
      oj dl -d $test_dir $url  # "oj dl" コマンドでテストケースをダウンロード
      python Atcoder/script/import_test.py $problem_name $base_url $test_dir $dropbox_token  # import_test.py スクリプトで Dropbox からテストケースをダウンロード
    }
    
    # "oj-api" コマンドで問題の詳細を取得 (oj-apiコマンドは別途インストールが必要)
    $ojOutput = (oj-api get-problem $url)
    
    # 取得した出力を JSON オブジェクトに変換
    $jsonObject = ConvertFrom-Json $ojOutput
    
    # タイムリミットを取得 (ミリ秒単位なので秒に変換)
    $tle = $jsonObject.result.timeLimit / 1000
    
    # テストの実行 && 提出 && テストデータのフォルダを削除
    oj t -c "python Atcoder/src/$problem_name.py" -d $test_dir/ --no-print-input -t $tle  && oj s $url Atcoder/src/$problem_name.py --language 5078  && Remove-Item -Recurse -Force $test_dir
    

4. scriptフォルダの下に「import_test.py」ファイルを作る

  • フォルダ構造は以下のようになります。

    親フォルダ/
    ├── .venv/                    
    └── AtCoder/
        ├── script/
        │   ├── cptest.ps1 
        │   └── import_test.py      
        └── src/
        │   └── abc001_a.py       
        └── test/                 
    
  • import_test.py」ファイルには以下のように書きます。

    コードと説明
    1. Power Shellから引数を受け取る。
    2. Dropboxからダウンロードするための設定をする。
    3. ファイル保存のための関数を定義する。
    4. サンプルテスト以外をダウンロードする。
    import dropbox 
    import requests  
    import os  
    import sys
    import concurrent.futures
    
    # コマンドライン引数を受け取る
    problem_name = sys.argv[1]  # ABC001_A
    base_url = sys.argv[2]      # ABC001
    test_dir = sys.argv[3]      # Atcoder/test/ABC001_A
    dropbox_token = sys.argv[4]  # Dropboxのアクセストークン
    
    # Dropboxからダウンロードするための設定
    dbx = dropbox.Dropbox(dropbox_token, scope=["sharing.read", "files.metadata.read"])
    SHARED_URL = "https://www.dropbox.com/sh/arnpe0ef5wds8cv/AAAk_SECQ2Nc6SVGii3rHX6Fa?dl=0"  # テストケースの共有フォルダのURL
    
    def download_file(testcase_name, directory, case_type):
        """指定ファイルをダウンロードして保存する関数"""
        # DropboxからファイルをダウンロードするためのURLを取得
        data = dbx.sharing_get_shared_link_file(
            url=SHARED_URL,
            path=f"/{base_url}/{problem_name[-1]}/{case_type}/{testcase_name}"
        )
        download_url = data[0].url.replace("dl=0", "dl=1")
    
        # ダウンロードしたファイルを保存
        res = requests.get(download_url)
        with open(f"{directory}/{testcase_name}.{case_type}", 'wb') as f:
            f.write(res.content)
    
    # Dropboxからテストケースのリストを取得
    in_testcases = dbx.files_list_folder(
        path=f"/{base_url}/{problem_name[-1]}/in",
        shared_link=dropbox.files.SharedLink(url=SHARED_URL, password=None)
    )
    
    # 並列処理でファイルをダウンロード
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = []
        for testcase in in_testcases.entries:
            # サンプルテスト以外をダウンロード
            if not testcase.name.startwith(00):
                in_file_path = os.path.join(test_dir, f"{testcase.name}.in")
                out_file_path = os.path.join(test_dir, f"{testcase.name}.out")
    
                futures.append(executor.submit(download_file, testcase.name, test_dir, "in"))
                futures.append(executor.submit(download_file, testcase.name, test_dir, "out"))
    
        # 全てのダウンロードタスクが完了するまで待つ
        concurrent.futures.wait(futures)
    

5. 「tasks.json」ファイルを作ってCtrl + Shift + Bテスト実行を割り当てる

  • Terminal(ターミナル)→ Configure Tasks(タスクの構成)→ Otherとすると、「tasks.json」が「.vscode」フォルダ下に作成されます。(「.vscode」フォルダは自動的に作成されます。)

  • その結果、フォルダ構造は以下のようになります。

    親フォルダ/
    ├── .venv/                    
    ├── .vscode/
    │   └── tasks.json            
    └── AtCoder/
        ├── script/
        │   ├── cptest.ps1        
        │   └── import_test.py   
        └── src/
        │   └── abc001_a.py      
        └── test/  
    
  • tasks.json」ファイルには以下のように書きます。

    コードと説明
    1. シェルコマンドを実行する。
      • パスのスラッシュを機能させるために、スラッシュを二つ重ねる必要があります。
    2. コマンドにファイル名を引数として渡す。
    {
      "version": "2.0.0",  // タスクファイルのバージョン
      "tasks": [
        {
          "label": "test_atcoder_sample",  // タスクの表示名
          "group": {
            "kind": "build",  // タスクの種類 (build:ビルド)
            "isDefault": true  // デフォルトで実行されるタスク
          },
          "type": "shell",  // シェルコマンドを実行する
          "command": "${workspaceFolder}\\Atcoder\\script\\cptest.ps1",  // 実行するコマンド
          "args": [
            "${fileBasenameNoExtension}"  // コマンドに渡す引数 (ファイル名)
          ]
        }
      ]
    }
    

お疲れ様です!これで設定は終わりです。

5. 結果

  • いよいよテストを実行します。
  • 先に設定した通り、テストをパスすると自動で提出されます。
  • 解答ファイルを開いてCtrl + Shift + B と入力します。
    スクリーンショット


    このように「test」フォルダ下に新しくフォルダが作成され、テストケースが格納されます。

    ACとなると、ターミナルにはこのように出力されます。

    提出の前に、確認ダイアログが表示されるので、「abcc」と入力します。

    勝手にコンテストの提出ページに飛ばされて、ACとなります。

  • 無事にテストと提出の自動化に成功しました!
  • 必要なパッケージは1つ、ファイルは3つのみということで、かなりシンプルな実装だといえると思います。

コメント

テストめんどくさいなーという怠惰な理由ではじめましたが、思った以上に大変でした。一見すると万能なonline-judge-toolsでも、これを使えばすべて解決するというわけではありませんでした。oj dlコマンドのsystemというオプションでテストケースをすべてダウンロードできるはずだったのにうまくいかず、結局、ゼロからではないものの自分でスクリプトを書くはめになりました。とはいえ、これでしばらくは快適なAtCoder生活がおくれそうなので満足です。 October 25, 2024

参考

  • Chokoryu. 2021. “AtCoderで自動サンプルテストケース&手入力値テスト実行 With VS Code.” Qiita. January 13, 2021.
    • 本記事に近い内容がわかりやすく説明されています。
  • “Login - Dropbox.” n.d. Dropbox.
    • アプリを作成するリンク。
  • Online-Judge-Tools. 2024. “GitHub - Online-judge-tools/Oj: Tools for Various Online Judges. Downloading Sample Cases, Generating Additional Test Cases, Testing Your Code, and Submitting It.” GitHub. March 3, 2024.
    • サンプルケースの取得、追加のテストケースの生成、テストの実行、コードの提出などを自動化するツール。
  • Sugimoto Kaito. 2021. “Dropbox API を駆使して AtCoder のテストケースを自動でダウンロードする.” Zenn. October 14, 2021.
    • テストケースのダウンロードのために参考にしました。

Discussion