작년부터 회사에서 제품의 뼈대부터 다시 설계하는 재개발 작업이 활발하게(?) 이루어지고 있다.

덕분에 자연스럽게 UI 자동화 테스트 프로젝트는 중단됐고,

그래도 덕분에 (물론 기존 제품을 리뉴얼한 거지만, 거의 새로운 앱이라고 할 수 있는) 신규 앱 QA를 주도해보는 경험을 얻었다.

APPIUM을 사용하지 않은지도 어느덧 1년이 다 되어가는 중이다.

 

그리고 잠시 다음 QA가 착수 되기 전까지 여유가 생긴 요즘, 다시 테스트 자동화로 눈독을 들이고 있다.

그 사이 APPIUM은 v2.0 Major Update와 함께 v1.x 지원을 중단했고,

내가 익숙하게 사용하던 Desktop APP은 v2.0 릴리즈와 함께 지원을 중단했다.

APPIUM Github ReadME

그래서 이왕 다시 APPIUM을 공부하는 마음으로 설치부터 다시해보기로.


Node.js & npm 설치하기

APPIUM은 Node.js 기반으로 동작하기 때문에 Node.js를 먼저 설치해야 한다.

 

방법 1. Node.js 공식 홈페이지에서 패키지 파일을 다운로드하고, 설치한다.

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

Node.js 설치하기

방법 2. brew 명령어로 설치한다.

brew install node

다음은 APPIUM을 다운로드하기 위한 npm을 설치한다.

  • npm 설치하니까 node도 함께 설치되는 듯 ?
brew install npm

 

APPIUM 설치

이제 진짜 목적인 APPIUM을 설치한다.

npm install -g appium@next
  • 여기서 주의해야 할 점은 appium@next 를 appium 으로 입력하면 v1.22.3 버전이 설치된다. (...)
  • 그럼 얌전히 다시 uninstall 해주면 된다.
npm uninstall -g appium

설치가 완료되면 제대로 된 버전을 설치했는지 확인한다.

appium -v

v2.x.x로 나오면 성공

APPIUM 서버 실행 
appium

아무 옵션없이 실행하면 디폴트값으로 실행된다.

  • HOST: http://0.0.0.0
  • PORT: 4723

매우 간단

 

'Test Automation > APPIUM' 카테고리의 다른 글

APPIUM 스크롤  (0) 2022.01.11
APPIUM logcat 가져오기 : get_log 활용법  (0) 2020.12.08
APPIUM TimeoutException 예외처리  (0) 2020.12.08
APPIUM Desired Capabilities 설정 방법  (0) 2020.12.06
APPIUM Selenium with Python  (0) 2020.12.06
from appium.webdriver.common.touch_action import TouchAction	

actions = TouchAction(self.driver)
actions.press(x=500, y=1809).wait(200) #wait 필수
actions.move_to(x=500, y=567)
actions.release()
actions.perform()
Query 유효성 점검 명령어

직접 작성한 Query문의 유효성 여부를 판별한다. 

POST <인덱스>/_validate/query?explain
{
<점검할 Query 문장>
}

 

SQL 명령문을 Query로 변환

SQL 명령문을 입력하면 자동으로 Query문으로 변환한 결과를 보여준다.

GET /_sql/translate { "query": "<변환할 SQL 명령문>" }

 

멀티 인덱스 검색

Elastic에서 데이터를 검색할 때는 인덱스 내부의 도큐멘트 단위로 검색한다. 한번에 여러 인덱스의 도큐멘트를 조회하려면 다음과 같이 명령어를 입력한다.

GET <인덱스 1>, <인덱스 2>/_search?

 

다중 쿼리 적용

여러개의 쿼리문을 적용한 질의 방식

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "<필드1>": "<반드시 포함할 텍스트1>"
          }
        },
        {
          "match": {
            "<필드2>": "<반드시 포함할 텍스트1>"
          }
        },
        {
          "prefix": { // 접두어 검색
            "<필드3>": "<반드시 포함할 텍스트3>"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "<필드4>": "<포함하지 않을 텍스트>"
          }
        }
      ]
    }
  }
}

 

 

검색 결과 갯수만 출력

검색 결과 데이터는 생략하고, 해당하는 데이터 갯수만 출력하는 명령어

# 기존 Query 질의 (_search)
GET summer/_doc/_search?{ ~ 쿼리문 생략 ~ }

# 질의 결과 갯수만 확인 (_count)
GET summer/_doc/_count?{ ~ 쿼리문 생략 ~ }

# 결과
{
  "count" : 10,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  }
}

 

Shard / Index 상태 조회
# Shard 상태 검색
GET /_cat/shards

# Index 상태 검색
GET /_cat/indices?v&pretty

'곰대생 > ELK Stack' 카테고리의 다른 글

Elasticsearch 검색 질의 방식  (0) 2021.02.15
Elasticsearch 데이터 처리  (0) 2021.01.29
ELK Stack이란? + Kibana 와 Grafana  (0) 2021.01.29

 

검색 기능이 특화된 Elasticsearch의 질의 방식에는 루씬 스타일을 기반으로 하는 URI 방식과 쿼리문을 사용하는 Query DSL 방식이 있다.

 


 

URI 방식 (루씬 스타일)

 

기본 형태

GET <인덱스>/<도큐멘트>/_search?q=<필드>:<값1 값2 값3>

#curl 명령어 사용 시
curl -XGET 'http://<IP 주소>:<포트 번호>/<인덱스>/<도큐멘트>/_search?pretty&q=<필드>:<값1 값2 값3>

예시

더보기

Shakespear 데이터)

GET shakespeare/_doc/_search?q=text_entry:(Henry Prince)
GET shakespeare/_doc/_search?sort=line_id&q=text_entry:Henry

 

여러 필드 검색

GET <인덱스>/<도큐멘트>/_search?q=<필드1>:<값1>&q=<필드2>:<값2>

#curl 명령어 사용 시
curl -XGET 'http://<IP 주소>:<포트 번호>/<인덱스>/<도큐멘트>/_search?pretty&q=<필드1>:<값1>&q=<필드2>:<값2>

 

Wildcard 사용

# UserName이 s로 시작하는 데이터 검색
GET <인덱스>/<도큐멘트>/_search?q=UserName:s*

#curl 명령어 사용 시
curl -XGET 'http://<IP주소>:<포트번호>/<인덱스>/<도큐멘트>/_search?q=UserName:s*'

 

포함(must) / 미포함(must_not) 필수값 지정

# Contents에 ABC가 반드시 포함되어 있고(must), EFG는 포함되지 않은(must_not) 데이터 검색
GET <인덱스>/<도큐멘트>/_search?q=+Contents:ABC&q=-Contents:EFG

#curl 명령어 사용 시
curl -XGET 'http://<IP주소>:<포트번호>/<인덱스>/<도큐멘트>/_search?q=+Contents:ABC&q=-Contents:EFG'

 

검색 결과 정렬 기준 설정

GET <인덱스>/<도큐먼트>/_search?sort=<정렬 기준 필드>:<ASC/DESC>&q=<필드명>:<검색할 텍스트>

#curl 명령어 사용 시
curl -XGET 'http://<IP주소>:<포트번호>/<인덱스>/<도큐멘트>/_search?sort=<정렬 기준 필드>:<ASC/DESC>&q=<필드명>:<검색할 텍스트>'

 

 

Query DSL 방식 (Request Body)

 

- Filter : YES or No 기반의 검색 결과 제공 (Score 미사용), 내부적인 캐싱 기능 ➡️ 빠른 검색 속도

- Query : Score 기반의 검색 결과 제공, 캐싱 기능 X ➡️ 융통적인 검색 결과

- Filtered (Filter + Query) 는 Elasticsearch 5.0 버전부터 권장하지 않음

 

기본 형태

GET <인덱스>/<도큐멘트>/_search?
{
  "query" : {
    "<Filter 또는 Query 함수>" : {
      "<필드>" : <값>
    }
  }
}

#curl 명령어 - json 파일 사용
curl -XGET 'http://<IP주소>:<포트번호>/<인덱스>/<도큐멘트>/_search?pretty' -H 'Content-Type: application/json' -d @query.json

 

Filter 종류

더보기

✔️term : 정확히 일치하는 값만 검색 (prefix 값 제외)

GET <인덱스>/<도큐멘트>/_search?
{
  "query" : {
    "bool" : {
     "filter" : {
       "term" : {
         "<필드>" : "<검색할 텍스트>"
       } 
      } 
    }
  }
}

 

✔️terms : 멀티값 처리 ➡️ [값 1, 값2] 배열 형태로 적어주기

GET <인덱스>/<도큐멘트>/_search?
{
  "query" : {
    "bool" : {
     "filter" : {
       "terms" : {
         "<필드>" : ["<찾을 텍스트1>", "<찾을 텍스트2>"]
       } 
      } 
    }
  }
}

 

✔️range : 숫자나 날짜 데이터 범위 지정

gt (~보다 크다) / gte (크거나 같다) / lt (~보다 작다) / lte (작거나 같다)

GET <인덱스>/<도큐멘트>/_search?
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "<필드>": {
            "gte": <최소 기준값>,
            "lte": <최대 기준값>
          }
        }
      }
  }
}

 

✔️bool : must (매칭 필수, and) / must_not (매칭X 필수, or) / should (유사도 score 계산)

GET <인덱스>/<도큐멘트>/_search?
{
  "query" : {
    "bool" : {
       "must" : {
         "term" : {
           "<필드>" : "<반드시 포함할 값>" 
         }
       } 
    }
  }
}
GET <인덱스>/<도큐멘트>/_search?
{
  "query" : {
    "bool" : {
       "must_not" : {
         "term" : {
           "<필드>" : "<반드시 포함하지 않을 값>" 
         }
       } 
    }
  }
}

 

Query 종류

더보기

✔️exists : 인덱스 내부에 특정 필드가 포함되어 있는지 확인

GET <인덱스>/<도큐멘트>/_search?
{
  "query": {
    "exists": {
      "field": "<조회할 필드명>"
    }
  }
}

 

✔️match : text, number, date 필드 검색 <default> "필드":"값"

GET summer/_doc/_search?
{
  "query" : {
    "match" : {
      "<필드>" : "<찾을 값>"
    }
  }
}

멀티값 처리(세부 질의) - "operator" 추가 <default> or

숫자 데이터는 따옴표(" ") 제거 / 날짜 데이터는 "yyyy-mm-dd" 형식 사용

 

구문으로 처리 - "type":"phrase" 추가 / 구문 사이에 다른 단어 허용 갯수 설정 "slop" 옵션

"minimum_should_match" = "50%"

 

✔️match_all : 모든 도큐먼트에서 검색, 다른 쿼리문이랑 같이 쓰는 경우가 많음

GET <인덱스>/_search?
{
  "query" : {
    "match_all" : {}
  }
}

 

✔️multi_match : 다수의 필드에서 특정 값 검색

GET <인덱스>/_search?
{
  "query" : {
    "multi_match": {
      "query": "<찾을 값>",
      "fields": ["<조회할 필드1>", "<조회할 필드2>"]
    }
  }
}

 

✔️prefix : 접두어로 검색

GET <인덱스>/_search?
{
  "query" : {
    "prefix": {
      "<필드>": {
        "value": "<검색할 텍스트>"
      }
    }
  }
}

# UserName이 Su로 시작하는 데이터 검색
GET <인덱스>/_search?
{
  "query" : {
    "prefix": {
      "UserName": {
        "value": "Su"
      }
    }
  }
}

 

✔️range : 숫자나 날짜 데이터 범위 지정

gt (~보다 크다) / gte (크거나 같다) / lt (~보다 작다) / lte (작거나 같다)

GET <인덱스>/_search?
{
  "query" : {
    "range": {
      "<필드>": {
        "gte": <최소 기준값>,
        "lte": <최대 기준값>
      }
    }
  }
}

# 2020년 자료를 검색할 경우

GET <인덱스>/_search?
{
  "query" : {
    "range": {
      "date": {
        "gte": "2020-01-01",
        "lte": "2020-12-31"
      }
    }
  }
}

 

✔️fuzzy : 유사 단어 검색 지원 (예를 들어, "big" ➡️ "bag"도 검색)

* "fuzziness" : 유사도 Score의 상한/하한 범위 지정

GET <인덱스>/_search?
{
  "query" : {
    "fuzzy": {
      "<필드>": {
        "value": "<찾을 텍스트>",
        "fuzziness": <유사도 범위>
      }
    }
  }
}

 

✔️Query String : 쿼리문을 문자열 형태로

* 구문은 \" ( ) \" 형태로 표시

GET <인덱스>/<도큐멘트>/_search?
{
  "query": {
    "query_string" : {
      "query" : "<필드>: <찾을 값>"
    }
  }
}

# 구문 검색 시
GET <인덱스>/<도큐멘트>/_search?
{
  "query": {
    "query_string" : {
      "query" : "<필드>: \"<검색할 구문>\""
    }
  }
}

'곰대생 > ELK Stack' 카테고리의 다른 글

Elasticsearch Query 사용 시 유용한 명령어  (0) 2021.02.15
Elasticsearch 데이터 처리  (0) 2021.01.29
ELK Stack이란? + Kibana 와 Grafana  (0) 2021.01.29

ELK Stack에서 데이터를 저장하고, 검색하는 (개인적으로) 가장 핵심이라고 생각하는 Elasticsearch

우리에게 친숙한 데이터베이스인 RDMS와 용어부터 차이가 있다.

자세한 이론적인 내용은 아직 너무 걸음마 수준이고, 나도 검색해보고 있는 입장이라 생략하고

지금까지 파악? 시도?해 본 Elasticsearch의 데이터 처리 방법에 대해서 잠깐 정리해본다.

 


 

데이터 저장하기 - Sample Data

Elasticsearch에서 제공하는 Sample Data를 이용한 데이터 저장하기

 

 

Loading sample data | Kibana Guide [6.8] | Elastic

If security is enabled, you must have the all Kibana privilege to run this tutorial. You must also have the create, manage read, write, and delete index privileges. See Security privileges for more information.

www.elastic.co

가이드 그대로 했는데 Mapping Error가 뜬다면 ?

더보기

분명 버전도 똑같은데 자꾸 도큐먼트 부분에서 매핑 에러가 떴다.

Failed to parse mapping [_doc]: Root mapping definition has unsupported parameters

원인을 검색해보니 알 수 없어서 unsupported 라길래 그냥 _doc 중괄호를 날려버렸다.

PUT /shakespeare
{
 "mappings": {
   "properties": {
    "speaker": {"type": "keyword"},
    "play_name": {"type": "keyword"},
    "line_id": {"type": "integer"},
    "speech_number": {"type": "integer"}
   }
  }
}

그랬더니 됐다.

이유는 ... 공부중이다.

 

 

데이터 구조

 

Elasticsearch의 데이터들은 각각 고유한 id를 갖는다.

데이터들이 모여 하나의 Document를 이루고, Document들이 모여 하나의 Index가 된다.

데이터를 저장하기에 앞서 Index를 만들어줘야 하는데, 주로 Index를 생성하면서 데이터 타입을 지정하는 Mapping을 같이 해준다.

 

PUT http://<호스트>:<포트>/<인덱스>
{
 "mappings": {
  "<도큐멘트>": {
   "properties": {
    "<키1>": {"type": "<데이터 타입>"},
    "<키2>": {"type": "<데이터 타입>"},
    "<키3>": {"type": "<데이터 타입>"}
   }
  }
 }
}

 

인덱스가 잘 만들었는지는 다음 명령어로 확인해볼 수 있다.

GET /_cat/indices?v

 

만들어 준 인덱스에 데이터를 저장하려면 http://<호스트>:<포트>/<인덱스>/<도큐멘트>/<도큐멘트 id> 로 curl을 날려주면 된다.

curl -XPUT 'http://<호스트>:<포트>/<인덱스>/_doc/<도큐먼트 id>' \
{
"<키 1>":"<값 1>",
"<키 2>":"<값 2>",
"<키 3>":"<값 3>"
}

로컬에 저장 된 JSON 파일 이용 시

더보기
curl -H 'Content-Type: application/x-ndjson'
\ -XPOST 'http://<호스트>:<포트>/<인덱스>/_doc/<도큐먼트 id>/_bulk?pretty'
\ --data-binary @<JSON 파일경로.json>

 

 

저장한 인덱스 Kibana에서 사용하기 (Yellow 상태 ➡️ Green 상태로 전환)

 

저장한 인덱스의 상태를 조회해보면 Yellow로 되어 있는 것을 확인할 수 있다. 이 상태가 Green일 때 Kibana에서 사용가능해진다.

http://<호스트>:<포트>/_settings?pretty' -d '{"index":{"number_of_replicas": 0}}' -H 'Content-Type: application/json'

 

Bulk_API : 여러 데이터 한번에 넣을 때 사용
POST _bulk
{"index":{"_index":"<인덱스>", "_id":"1"}}
{"<필드>":"<값 1>"}
{"index":{"_index":"<인덱스>", "_id":"2"}}
{"<필드>":"<값 2>"}

 

 

모바일 APP 보안 서비스를 다루다 보면 한번씩 듣는 단어들인데

들을 때마다 검색해보다가 드디어 정리를 해둔다.

 

너무 비슷비슷한 단어들이라 아직도 헷갈린다.

익숙해질 날이 오겠지?


MDM (Mobile Device Management)

모바일기기 원격 제어

 

 

MAM (Mobile Application Management)

모바일기기 내부의 특정 데이터 제어

 

 

UEM (Unified Endpoint Management)

모바일뿐만 아니라 태블릿, 데스크탑, 노트북 등 기타 IT 단말까지 제어

 

 

EMM((Enterprise Mobility Management)

포괄적이고 하드웨어를 가리지 않은 원격 디바이스 관리 방법



DRM (Digital Right Management)

정보보호 기술 중 하나로 암호화 기술을 이용해서 비허가 사용자로부터 디지털 컨텐츠를 보호하게 하는 기술



 

회사에서 새롭게 공부하기 시작한 ELK Stack

정말 처음 들어봐서 아직 걸음마 수준이지만 나중을 위해 조금이라도 정리해보려 한다.


ELK Stack = Elasticsearch + Logstash + Kibana

 

<Elasticsearch> 로그 관리(검색) 시스템
분산 검색 엔진 — 비정형 데이터를 쉽게 저장 및 처리 가능 / 실시간 데이터 검색 가능
RESTful API / JSON 형식 지원

<Logstash> 구문 분석 및 변환
데이터 처리 파이프라인
입력 — *필터 — 출력
*Template, 구조화, Mapping

<Kibana> 데이터 시각화

 

최근에는 데이터 수집 도구인 <Beats>도 함께 사용한다.

 

JSON형식의 DATA ➡️ Beats ➡️ Logstash ➡️ Elasticsearch ➡️ Kibana or Grafana

 

 

Kibana 와 Grafana 의 차이

 

Kibana와 Grafana 모두 데이터를 시각화하는 기능을 제공한다.

데이터를 쿼리해서 다양한 시각화 자료로 구현하고, 이를 Dash보드 형태로 확인할 수 있는 동일한 기능을 가지고 있다.

 

아무래도 ELK Stack으로 한 세트처럼 여겨지는 Kibana가 사용하기에 더 간단하지 않나 생각이 들 수도 있는데,

Grafana에서 Elasticsearch를 연동하는 과정을 찾아보면 어렵지 않아서 사실 복잡도는 비슷한 것 같다.

둘의 차이점을 꼽자면 Kibana는 쿼리 기능에 특화되어 있고, Grafana는 알림 기능이 특화되어 있다.

 

결국 우리 조직에서는 Grafana와 고민하다가 Kibana를 선택했는데,

아직 발 담근지 오래되지 않아서 뭐가 좋은지 정확히 비교는 어렵다🥲

'곰대생 > ELK Stack' 카테고리의 다른 글

Elasticsearch Query 사용 시 유용한 명령어  (0) 2021.02.15
Elasticsearch 검색 질의 방식  (0) 2021.02.15
Elasticsearch 데이터 처리  (0) 2021.01.29

 
UI 테스트를 진행하다 보면 Timeout Exception 오류로 테스트가 중단되는 경우가 많은데,
APPIUM Log만으로는 단순하게 UI 오류인지, 심각한 Crash 오류인지 식별하기가 어렵다.

개발자에게 전송할 로그를 포착하기 위해 로그캣창을 보고 있다가 로그 추출의 자동화가 굉장히 시급하다는 생각이 들었다.
역시 사람은 눈 앞에 고생이 닥쳐오면, 뇌가 활성화되는 것 같다.


get_log 매소드

 

똑똑한 우리의 APPIUM은 로그를 불러오는 get_log 매소드를 지원한다.

 

하지만, 역시 아예 떠먹여주지는 않는 APPIUM은 나를 또 공부시킨다.

 

get_log 매소드는 log type 파라미터를 요구한다.

이 log type은 플랫폼마다 제공되는 로그가 다르기 때문에 아주 다양한 값들이 들어갈 수 있다.

 

그 중에서 내가 필요한 부분은 logcat이기 때문에 다음과 같이 적어준다.

logs = driver.get_log('logcat') # Only Android platform

 

'여기서 print(logs)를 하면 logcat이 출력되겠지?'

친절하게 공부를 요구하는 APPIUM인데, 당연히 안된다.

 

리스트를 문자열로 변환

get_log는 리스트 형식의 output을 내는데,
이를 가독성 좋은 문자열 형태로 변환하기 위해선 다음과 같은 변환이 필요하다.

logs = driver.get_log('logcat')
logs_message = '\n'.join(list(map(lambda log: log['message'], logs)))

# Tip ! 로그 파일로 저장하기
file = open(filepath + filename + '.log', 'w')
file.write(logs_message)

 

그렇다면,

 

Q. 내가 원하던 Crash 로그가 발생 했을 때 로그를 추출하려면 어떻게 해야 될까 ?

A. 리스트 형식인 로그를 차례로 대입하여 Crash 로그가 있는지 확인한다.

logs = driver.get_log('logcat')
logs_message = list(map(lambda log: log['message'], logs))

for message in logs_message:
    if 'Crash' in message:
        break

file = open(filepath + filename + '.log', 'w')
file.write('/n'.join(logs_message))

 

그리하여, 테스트를 열심히 하다가 Crash Log가 나면

똑똑하게 해당 세션의 로그 파일을 저장해주는 자동화 프로그램 구축에 성공했다.

 

'Test Automation > APPIUM' 카테고리의 다른 글

APPIUM 설치부터 실행까지  (0) 2023.05.12
APPIUM 스크롤  (0) 2022.01.11
APPIUM TimeoutException 예외처리  (0) 2020.12.08
APPIUM Desired Capabilities 설정 방법  (0) 2020.12.06
APPIUM Selenium with Python  (0) 2020.12.06

 

UI 기반의 테스트를 진행하다보면 예상치 못한 부분에서 Timeout Exception 에러로 허무하게 테스트가 중단될 때가 있다.
Timeout Exception은 이름에서 유추할 수 있듯이 설정한 대기시간이 넘도록 조건을 만족하지 못해 더이상 테스트를 진행할 수 없을 때 나타나는 오류다.

예를 들면 다음과 같은 소스코드가 있을 때,

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

# Driver의 기본 대기 시간은 20초
wait = WebDriverWait(driver, 20)

# btn_login이 클릭가능할 때까지 대기 (최대 20초)
login = wait.until(EC.element_to_bo_clickable((By.ID, 'com.sample.package:id/btn_login'))
login.click()

 

임의의 오류로 20초가 지나도록 로그인 버튼이 클릭 가능한 상태가 되지 않으면 Timeout Exception 오류가 나고,

이후 스크립트는 실행되지 않고, 테스트는 자동 종료된다.

이를 방지하기 위해 필요한 것이 Timeout Exception 예외 처리다.

즉, Timeout이 생기더라도 스크립트를 중단하지 않고 그대로 다음 스크립트를 진행하기 위한 예외 처리이다.

 

try:
       # Timeout 오류가 날 수 있는 스크립트

except TimeoutException:
       # Timeout 오류가 난 경우 실행 될 스크립트

 

 

디렉토리 유무 확인 후 없으면 디렉토리 생성

 

try:
    if not (os.path.is.dir('directory')):
        os.makedirs(os.path.join('directory'))
except OSError as error:
    if error.errno != errno.EEXIST:
        print('Directory has already created')
        raise

 

파일 쓰기

 

#동일한 이름의 파일이 있을 경우 덮어쓰기
open('directory' +  '파일명.txt', 'w') as file:
    file.write('파일 내용')

#동일한 이름의 파일이 있을 경우 이어쓰기
open('directory' +  '파일명.txt', 'a') as file:
    file.write('파일 내용')

'곰대생 > Python' 카테고리의 다른 글

Python Test framework "Pytest" (설치 및 사용 방법)  (0) 2020.12.07

+ Recent posts