본문 바로가기
스터디 발표자료

코딩 테스트 서버 만들기

by qjatjs123123 2025. 2. 10.

 

 

안녕하세요!

 

이번에는 개발자라면 한 번쯤 해봤을 법한 백준, 리트코드, 프로그래머스의 동작 과정을 분석해보았습니다.

 

코딩 테스트 채점 서버 관련 로직을 찾기 어려워, 제가 생각한 코딩 테스트 채점 서버의 동작 과정을 공유해보려고 합니다.

 

글을 쓰기 앞서, 제가 제시하는 아이디어는 정답이 아니라 공유를 위한 것이며, 다양한 의견을 나누는 것이 목적입니다.

 


동작 플로우

제가 생각한 코딩 테스트 채점 서버 수도 코드를 텍스트로 작성해보았습니다.

 

1. 사용자가 코딩 테스트 문제를 푼다.

2. 사용자가 제출 버튼을 누르면 사용자가 입력한 코드, 작성 언어가 전송된다.

3. 채점 서버에서 사용자 코드를 해당 언어 파일로 변환한다.

4. 채점 서버에 저장된 입력 테스트케이스 읽어온다.

5. 사용자 코드 실행 파일에 입력 테스트케이스를 넣는다.

6. 해당 파일 결과와 정답 테스트케이스를 비교한다.

 

 

좀 더 자세히 알아볼까요? 

 

 

 

1. 사용자가 코딩 테스트 문제를 푼다.

 

이 부분은 사용자가 문제를 보고 푸는 과정이므로, 서버에서 하는 과정과는 관련이 없어 넘어가겠습니다.

 

 


 

 

2. 사용자가 제출 버튼을 누르면 사용자가 입력한 코드, 작성 언어가 전송된다.

 

 

위 이미지 사용자 동작 플로우는 다음과 같습니다.

1. 소스코드를 입력한다.

2. 언어를 선택한다.

3. 제출버튼을 클릭한다.

 

 


 

 

3. 채점 서버에서 사용자 코드를 해당 언어 파일로 변환한다.

 

사용자가 선택한 언어가 python이라면 py확장자를 가진 언어 파일로 변환합니다.

c라면 c확장자를 가진 언어 파일로 변환합니다.

java라면 java확장자를 가진 언어 파일로 변환합니다.

 

 


 

 

 

4. 채점 서버에 저장된 입력 테스트케이스 읽어온다.

 

서버에 저장된 해당 문제의 입력 테스트케이스를 읽어옵니다.

 

 

 


 

 

 

5. 사용자 코드 실행 파일에 입력 테스트케이스를 넣는다.

 

코딩 테스트 풀면 입력 테스트케이스를 읽기 위해서 IO 관련 함수를 사용합니다.

 

  • Java에서는 Scanner 클래스나 BufferedReader 클래스를 사용하여 입력을 받습니다.
  • Python에서는 기본적으로 input() 함수를 사용하고, 필요한 경우 타입을 변환하여 사용합니다.
  • C에서는 scanf 함수를 통해 입력을 받을 수 있습니다.

이러한 함수를 통해서 사용자 코드 실행 파일에 입력 테스트케이스를 넣습니다.

 

 

 


 

 

 

6. 해당 파일 결과와 정답 테스트케이스를 비교한다.

 

 

해당 파일 결과와 정답 테스트 케이스를 비교해서 정답, 오답 여부를 판별하는 과정입니다.

 

 

 


동작 플로우를 토대로 코드로 작성해보았습니다.

 

package com.ssafy.algo.controller;

import com.ssafy.algo.dto.UserCode;
import org.springframework.web.bind.annotation.*;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/test")
@CrossOrigin("*")
public class TestController {
    
    private static final String TEST_CASES_DIR = "testcase/";
    private static final String USER_CODE_FILE = "user_python_code.py";

    @PostMapping("/evaluate")
    public String evaluateQuestion(@RequestBody UserCode userCode) throws IOException, InterruptedException {
        
    	isCreateFileValid(userCode.getCode(), USER_CODE_FILE);     
        File[] testFiles = getTestCases();
        
        for (File testCase : testFiles) {
            String result = executeTestCase(testCase);
            if (!"정답".equals(result)) {
                return result;
            }
        }
        return "정답";
    }
    
    // 주어진 테스트 케이스 파일에 대해 Python 코드 실행 후 결과를 확인하는 메서드
    private String executeTestCase(File testCase) throws IOException, InterruptedException {
        String input = Files.readString(testCase.toPath());

        Process process = runPythonScript(USER_CODE_FILE, input);
        
        if (!process.waitFor(2, TimeUnit.SECONDS)) {
            process.destroy();
            return "시간 초과";
        }
        
        if (process.exitValue() != 0) {
            return "컴파일 오류";
        }

        
        String expectedOutput = Files.readString(Path.of(testCase.getAbsolutePath().replace(".in", ".out")));
        String actualOutput = readProcessOutput(process).trim();

        return expectedOutput.trim().equals(actualOutput) ? "정답" : "오답";
    }

    // 주어진 Python 스크립트를 실행하고, 해당 스크립트에 입력 데이터를 전달하는 메서드
    private Process runPythonScript(String scriptFile, String input) throws IOException {
        ProcessBuilder processBuilder = new ProcessBuilder("python", scriptFile);
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();

        try (BufferedWriter processInput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) {
            processInput.write(input);
            processInput.flush();
        }

        return process;
    }
    
    // 프로세스의 출력 스트림을 읽어들여 결과를 문자열로 반환하는 메서드
    private String readProcessOutput(Process process) throws IOException {
        StringBuilder outputBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                outputBuilder.append(line).append("\n");
            }
        }
        return outputBuilder.toString();
    }

    // 테스트 케이스 경로에 있는 .in 확장자 파일을 반환하는 메서드
    private File[] getTestCases() {
        File testCasesDir = new File(TEST_CASES_DIR);
        File[] testFiles = testCasesDir.listFiles((dir, name) -> name.endsWith(".in"));

        if (testFiles == null || testFiles.length == 0) {
            throw new Error("테스트 케이스 파일을 찾을 수 없습니다.");
        }

        return testFiles;
    }
    
    // PrintWriter 함수를 통해 파일을 만드는 함수입니다.
    // 파일을 만들지 못하면 에러를 던집니다.
    private void isCreateFileValid(String code, String fileName) {
        try (PrintWriter writer = new PrintWriter(fileName)) {
            writer.println(code);
        } catch (FileNotFoundException e) {
            throw new Error("서버 오류입니다.");
        }
    }
}

 

파이썬 언어로 핵심 기능만 간단하게 다듬은 예제 코드입니다.

 

 


 

실행 결과입니다.


print(A + B 
닫는 괄호가 없으므로 컴파일 오류가 발생합니다.

 

 

 

 

A + B가 정답이지만 A - B를 했으므로 

오답이 출력됩니다.

 

 

 

 

정답 코드이므로

정답이 출력됩니다.

 

 

 

코딩 테스트 채점 서버를 동작 플로우, 코드, 실행결과 까지 알아보았습니다.

 

 

지적 및 질문을 댓글로 부탁드립니다.

감사합니다.

 

'스터디 발표자료' 카테고리의 다른 글

React 렌더링 최적화  (0) 2025.02.10
OSI 7계층 Wireshark로 분석하기  (0) 2025.02.10