본문 바로가기
자동매매 일지/시스템 운영

테스트가 전부 통과했는데 버그였습니다 — 내 검사가 나를 속인 4가지 방법

by 아트스탁 2026. 8. 31.
한눈에
  • 「9종 중 9종 잡아냄」이 거짓이었습니다. 검사가 이미 깨져 있어서 아무것도 안 심어도 「잡음」이 나왔습니다.
  • 한 군데인지 확인하려고 셌더니 2가 나왔습니다. if를 세는데 elif가 같이 세어졌고, 다음엔 제가 쓴 주석이 세어졌습니다.
  • 27개 검사가 전부 통과했는데, 안전장치가 있는 그 줄을 아예 안 태우고 있었습니다.
  • 토요일 아침에 검사 50개 중 8개만 돌고 멈췄습니다. 코드는 멀쩡했고 검사가 「오늘」에 기대고 있었습니다.
  • 거짓 실패는 빨갛게 떠서 바로 알지만, 거짓 통과는 초록 글씨로 조용히 지나갑니다.

주식 자동매매 프로그램을 직접 만들어 실제 돈으로 돌리고 있습니다. 돈이 걸려 있으니 검사를 꽤 많이 만들어 뒀습니다. 고칠 때마다 전부 돌려서 「다 통과」를 확인하고 넘어갑니다.

그런데 그 「다 통과」가 저를 여러 번 속였습니다.

이 글은 버그를 못 잡은 이야기가 아닙니다. 검사가 「잡았다」고 말했는데 사실은 안 잡고 있던 네 가지입니다.

내 검사가 나를 속인 네 가지 — 9종 중 9종 잡음이 거짓, 1이어야 할 것이 2로, 27개 통과했으나 그 줄 미실행, 토요일에 50개 중 8개만 실행

① 「9종 중 9종 잡아냄」이 거짓말이었습니다

검사를 검사하는 방법 — 일부러 결함을 심고 잡히는지 본다, 9종 다 잡혔으나 기준선 자체가 이미 깨져 있었다

먼저 검사를 검사하는 방법부터 말씀드려야 합니다.

검사를 아무리 많이 만들어도 그게 실제로 버그를 잡는지는 알 수 없습니다. 지금 버그가 없으니 다 통과할 뿐이니까요.

그래서 이렇게 확인합니다. 코드에 일부러 결함을 심고, 검사가 실패하는지 봅니다. 안 실패하면 그 검사는 있으나 마나입니다.

주문 수량을 정하는 계산식을 슬쩍 바꾼다  →  검사가 실패해야 정상
정렬 순서를 거꾸로 뒤집는다  →  검사가 실패해야 정상
「이 시간엔 주문 금지」 가드를 통째로 지운다  →  검사가 실패해야 정상

이런 결함 9종을 심었더니 9종 다 검사가 실패했습니다. 만점입니다. 저는 「내 검사는 튼튼하구나」라고 생각했습니다.

그런데 만점이 나온 이유가 달랐습니다

그날 저는 한도 계산을 고치고 있었습니다. 「주식을 총자산보다 많이 담지 마라」를 재는 부분인데, 위아래 기준이 달라서 실제 90.5%가 125.7%로 읽히던 그 사고를 고치는 중이었습니다. (지난 글 ③에 자세히 적었습니다)

고치면서 한도를 재는 함수를 새로 하나 만들었습니다. 그리고 검사용 가짜 프로그램에는 그 함수를 안 붙였습니다. 현재가를 담아두는 자리도 안 만들었습니다.

그러니 결함을 심기 전부터 검사가 이미 죽고 있었습니다. 없는 함수를 부르니 당연합니다.

여기가 함정입니다. 이 방식은 「검사가 실패하면 = 결함을 잡았다」로 판정합니다.
그런데 검사가 처음부터 죽고 있으면 무엇을 심든 실패합니다. 아무것도 안 심어도 실패합니다. 그래서 9종 전부가 자동으로 「잡힘」이 됐습니다.
만점은 검사가 좋아서가 아니라 검사가 고장 나서 나온 숫자였습니다.

하마터면 그대로 넘어갈 뻔했습니다. 9/9는 제가 보고 싶었던 숫자였으니까요.

고친 방법 — 아무것도 안 심고 한 번 돌립니다

① 결함을 하나도 안 심고 돌린다  →  여기서 실패하면 그 자리에서 중단
② 통과했을 때만 결함을 하나씩 심는다

①에서 실패하면 프로그램이 아예 멈추게 해뒀습니다. 「9종 다 잡음」이라는 거짓 만점을 낼 기회를 없앤 것입니다.

⚠ 여기서 하지 말아야 할 유혹이 하나 있습니다

가짜 프로그램이 죽지 않게 하려면 「그 함수가 없으면 대충 넘어가라」고 한 줄 넣으면 됩니다. 그러면 에러가 안 납니다.

그건 정확히 반대로 가는 것입니다. 그렇게 하면 검사가 새로 고친 코드가 아니라 옛날 동작을 조용히 시험하면서 통과합니다.
진짜 프로그램에서는 그 함수가 항상 있습니다. 없는 건 가짜 쪽이니 시끄럽게 죽는 게 맞습니다. 그래서 그 방어를 일부러 안 넣었습니다.

② 한 군데인지 셌더니 2가 나왔습니다

제 프로그램에는 「위기 상황이면 새로 사지 마라」는 잠금이 하나 있습니다. 이 잠금을 여러 군데서 켰다 껐다 하면 위험합니다. 한 군데에서만 다뤄야 합니다.

그래서 「이걸 만지는 자리가 몇 군데인가」를 검사로 세게 해뒀습니다. 가장 쉬운 방법은 파일에서 그 글자를 찾는 것입니다.

그런데 이게 계속 틀렸습니다.

세려던 것 같이 세어진 것
if 이 잠금 이 몇 군데? 고치면서 새로 넣은 elif 이 잠금
글자가 안에 들어 있어서 1이어야 할 것이 2
그럼 줄 전체로 세자 줄 끝에 붙인 주석함수 설명글
코드가 아닌데 「사용처」로 잡힘

제가 쓴 주석이 저를 속였습니다

다른 검사에서는 「현재가를 조회하는 함수를 어디서 부르나」를 확인하려 했습니다. 조회가 판정보다 앞에 오면 안 되는 자리였습니다.

그런데 저는 바로 그 위 주석에 그 함수 이름을 적어뒀습니다. 「여기서는 이 함수를 부르지 않는다」는 설명이었습니다.

검사가 그 주석을 진짜 호출로 세었습니다. 그러고는 저에게 「조회가 판정보다 앞에 있다」는 지적을 내놨습니다.
코드는 멀쩡했고, 제가 쓴 설명이 저를 물었습니다.

비슷하게 「이 잠금을 켜고 끄는 스위치가 몇 개인가」를 셀 때는, 스위치를 만드는 줄까지 같이 세어졌습니다. 만드는 것과 실제로 값을 읽는 것은 다른데도요.

반대로 놓치기도 합니다

이번엔 못 세는 쪽입니다. 화면에 상자를 그리는 함수를 어디서 부르는지 찾는 검사였는데, 「괄호 열고 첫 값이 이것」을 전제로 찾게 해뒀습니다.

그런데 줄이 길어서 여러 줄에 걸쳐 쓴 호출들이 있었습니다.

8개 중 5개를 놓쳤습니다. 그런데 그건 「검사 안 됨」으로 안 보입니다. 「통과」로 보입니다.
안 센 것은 문제로 안 뜨니까요. 그게 이 함정의 성질이고, 그래서 열 번 넘게 밟고도 매번 새로 놀랐습니다.

고친 방법 — 글자가 아니라 구조로 셉니다

파이썬에는 코드를 「글자」가 아니라 「구조」로 읽어주는 기능이 있습니다. 그걸 쓰면 주석과 설명글은 애초에 안 들어옵니다. 코드가 아니니까요.

⛔ 전  —  파일에서 이 글자가 몇 번 나오나?
✅ 후  —  실행되는 자리에 이게 몇 번 있나?

판정 자체도 바꿨습니다. 「몇 개냐」가 아니라 「어느 함수가 이걸 만지느냐」로요. 지금은 딱 네 함수만 만지는 게 맞고, 다른 데서 만지면 그때 걸립니다.

개수는 코드가 조금만 바뀌어도 흔들립니다. 위에서 elif 하나 때문에 1이 2가 된 것처럼요. 「어디서 만지나」는 뜻이 분명해서 안 흔들립니다.

③ 27개가 전부 통과했는데, 그 줄을 안 태웠습니다

검사를 돌리려면 진짜 프로그램 대신 쓸 가짜가 필요합니다. 진짜를 돌리면 실제 주문이 나가니까요.

어느 날 저는 이미 산 종목을 조건이 맞으면 한 번 더 사는 기능을 넣었습니다. 돈이 한 번 더 나가는 기능이라 검사를 27개나 만들었습니다. 전부 통과했습니다.

그런데 가짜에 그 정보가 아예 없었습니다

이 기능은 주문을 내기 전에 두 가지를 봐야 합니다.

봐야 하는 것 안 보면 벌어지는 일
지금 프로그램이 돌고 있나 「중지」를 눌러도 주문이 나갑니다
사용자가 예약을 걸어뒀나 사용자가 따로 쓰려고 잡아둔 현금을 가져다 씁니다

그런데 가짜 프로그램에는 그 두 가지가 아예 안 만들어져 있었습니다.

여기서 이상한 점을 눈치챘어야 했습니다. 코드가 그 두 가지를 실제로 봤다면 「그런 거 없는데?」 하고 죽었어야 합니다.
안 죽고 27개가 전부 통과했다는 것은, 코드가 그 줄을 아예 안 지나갔다는 뜻이었습니다.
통과는 「맞다」의 증거가 아니라 「안 봤다」의 증거였습니다.

「중지」는 뭔가 잘못됐다고 느낄 때 누르는 버튼입니다. 하필 그때 주문이 나가면 최악입니다.

제대로 고친 뒤 다시 돌리니 검사가 「그런 거 없는데?」 하고 죽었습니다. 그게 안전장치가 실제로 돈다는 증거였습니다. 가짜에 그 두 가지를 만들어 넣고서야 진짜 검사가 됐습니다.

같은 함정을 반대 방향으로도 밟았습니다

다른 검사에서는 어떤 설정값을 0으로 바꿔놓고 「이러면 어떻게 되나」를 보려 했습니다. 그런데 제가 값을 넣은 자리와 프로그램이 값을 읽는 자리가 서로 달랐습니다.

제 프로그램에는 설정값을 담는 자리가 두 갈래입니다. 어떤 부분은 화면의 입력칸을 직접 읽고, 어떤 부분은 따로 복사해둔 목록을 읽습니다. 저는 복사본에 0을 넣었는데, 그 코드는 화면 쪽을 읽고 있었습니다.

무대가 안 세워진 것입니다. 저는 0으로 시험한다고 믿었는데 프로그램은 원래 값 그대로 돌고 있었습니다.
그래서 「0일 때」와 「원래 값일 때」를 나란히 비교한 두 검사가 사실은 같은 판을 두 번 잰 것이었습니다. 둘 다 통과했으니 저는 「양쪽 다 확인했다」고 믿었습니다.

지금은 무대를 세운 직후에 「무대가 정말 섰나」를 그 자리에서 되물어봅니다. 값을 넣었으면 프로그램이 읽는 쪽에서 그 값이 나오는지 확인하고, 안 나오면 거기서 멈춥니다.

④ 토요일 아침에 50개 중 8개만 돌고 멈췄습니다

어느 토요일에 검사를 돌렸더니 네 뭉치가 무더기로 깨졌습니다. 그중 하나는 검사 50개 중 8개만 돌고 나머지는 시작도 못 한 채 죽었습니다.

코드를 한참 들여다봤습니다. 아무 문제가 없었습니다.

원인은 「오늘이 토요일」이었습니다

며칠 전 저는 거래 시간을 늘리는 작업을 했습니다. 대체거래소가 생겨서 아침 8시부터 밤 8시까지 주문을 낼 수 있게 된 부분입니다.

그 검사들은 「지금 시각」을 그대로 갖다 쓰고 있었습니다. 오늘 날짜에 시각만 바꿔서 무대를 세우는 방식이었습니다.

그런데 거래 시간을 판정하는 코드는 주말이면 무조건 닫습니다. 당연합니다 — 장이 안 열리니까요.

그래서 토요일엔 검사가 통째로 못 돌았습니다. 코드는 멀쩡했고, 제 검사가 「오늘이 평일이다」에 기대고 있었을 뿐입니다.
평일에만 돌리는 동안은 계속 멀쩡해 보였습니다. 그리고 거래 시간을 늘린 그 작업이 검사의 전제까지 바꿔놨다는 걸 저는 몰랐습니다.

고친 방법 — 무대를 제가 정합니다

「지금」을 쓰지 않고 특정 평일 하루를 못 박았습니다. 검사 안에서는 언제 돌리든 그날입니다.

그리고 하나 더 만들었습니다. 「지금」에 기대는 검사가 또 있는지 전부 훑는 검사입니다. 다음 주말에 같은 일이 나면 그게 먼저 잡습니다.

검사가 「지금」에 기대면 그건 검사가 아니라 그날의 운입니다. 무대는 오늘이 정하는 게 아니라 제가 정하는 것이어야 합니다.

네 가지의 공통점

고친 방법 — 고장 난 검사도 통과함을 먼저 확인, 글자가 아니라 구조로 세기, 무대는 오늘이 아니라 내가 정하기
검사가 말한 것 실제로 벌어진 것
「9종 중 9종 잡았다」 검사가 고장 나서 전부 자동으로 잡힘 처리
「두 군데에 있다」 elif주석을 셌다 — 실제로는 한 군데
「27개 전부 통과」 안전장치가 있는 그 줄을 아예 안 태웠다
「50개 중 8개만 돌았다」 코드는 멀쩡하고 그날이 토요일이었다

네 가지가 두 방향으로 나뉩니다. ①②③은 「실패해야 하는데 통과」였고, ④는 「통과해야 하는데 실패」였습니다.

거짓 실패는 시끄럽고, 거짓 통과는 조용합니다
토요일에 깨진 것은 화면에 빨갛게 떠서 그날 바로 알았습니다. 반나절 헤맸지만 어쨌든 알았습니다.
그런데 거짓 통과는 아무 말도 안 합니다. 초록 글씨로 「다 통과」만 보여줍니다. 27개가 헛돌고 있다는 걸 안 것은 한참 뒤였고, 9종 만점이 거짓이라는 건 하마터면 못 볼 뻔했습니다.
그래서 검사를 만들면 「이게 실제로 실패하는 걸 본 적이 있나」를 물어야 합니다. 한 번도 실패한 적 없는 검사는 튼튼한 게 아니라 안 돌고 있는 것일 수 있습니다.

덧 — 검사를 만들 때 물어볼 네 가지

  • 멀쩡할 때 통과하나? — 「몇 개 잡았나」보다 이게 먼저입니다.
  • 내가 세는 게 코드인가, 글자인가? — 주석은 실행되지 않습니다.
  • 그 줄을 실제로 태웠나? — 통과했다고 지나간 게 아닙니다.
  • 무대를 내가 정했나? — 「지금」에 기대면 언젠가 깨집니다.

넷 다 제가 만든 검사가 저를 속인 뒤에야 알게 된 것들입니다. 검사를 늘리는 것보다 검사를 의심하는 게 먼저였습니다.

※ 이 글은 개인이 만든 프로그램에서 실제로 겪은 일을 정리한 기록입니다. 매매 전략이나 수익률은 담지 않았고, 특정 종목의 매수·매도를 권유하지 않습니다.