2024.03.27(수)

🏁E2E 테스트

개발물의 안쪽은 침투하지 않고 사용자 관점에서 테스트하는 것

  • 요구사항 명세로부터 테스트 시나리오를 도출해 시나리오에 따른 테스트 코드 작성
  • GUI를 포함하는 시스템에서 UI와 코어 로직의 연결 및 동작 검증
  • 단위 테스트로는 검증이 불가한 사용자 관점의 테스트
  • 테스트 병렬화 중요 (Selenium Grid 적용 가능)

📹Selenium IDE

  • 크롬 확장 프로그램 설치 & pin
  • 확장 프로그램 클릭 > Create a new project > PROJECT NAME 입력 > REC > BASE URL http://localhost:30030/ 입력 > Start Recording
  • play 버튼으로 테스트 재생 가능
  • 시나리오 파일(.side)을 다운로드해서 Command-Line Runner를 이용해 실행할 수 있다!
    • 준비 (설치)
      npm install -g selenium-side-runner
      npm install -g chromedriver # (또는 edgedriver, geckodriver 등)
    • 로컬에서 실행
      selenium-side-runner <시나리오를 담은 .side 파일>

    • Selenium Grid 를 이용한 실행
      selenium-side-runner --server <서버 URL> <시나리오를 담은 .side 파일>
  • 또는 Python pytest로 export 가능! 코드를 알아서 작성해주니 굉장히 편하다. 물론 어느 정도 코드의 수정이 필요(wait, assertion, …)
    • code
      # Generated by Selenium IDE
      import pytest
      import time
      import json
      from selenium import webdriver
      from selenium.webdriver.common.by import By
      from selenium.webdriver.common.action_chains import ActionChains
      from selenium.webdriver.support import expected_conditions
      from selenium.webdriver.support.wait import WebDriverWait
      from selenium.webdriver.common.keys import Keys
      from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
      
      class TestUntitled():
        def setup_method(self, method):
          self.driver = webdriver.Chrome()
          self.vars = {}
        
        def teardown_method(self, method):
          self.driver.quit()
        
        def test_untitled(self):
          self.driver.get("http://localhost:30030/")
          self.driver.set_window_size(1454, 866)
          self.driver.find_element(By.LINK_TEXT, "무료로 시작하기").click()
          self.driver.find_element(By.ID, "email").click()
          self.driver.find_element(By.ID, "email").send_keys("test@gmail.com")
          self.driver.find_element(By.ID, "password").click()
          self.driver.find_element(By.ID, "password").send_keys("1234")
          self.driver.find_element(By.CSS_SELECTOR, "button").click()
          self.driver.find_element(By.CSS_SELECTOR, ".sc-brSamD:nth-child(2) > p").click()
    • test_로 시작하는 메서드가 테스트 케이스가 된다.
    • ⚠️로그아웃 버튼의 경우 react에서 생성되는 hash 값으로 이루어진 class명을 사용하고 있는데 이는 달라질 수 있으므로 다른 방법을 선택해준다.

    • 코드를 살펴보면 로딩 타임을 고려하지 않아 테스트가 실패함 → wait 해주는 코드를 넣기 → 성공
      def test_untitled(self):
      	self.driver.get("http://localhost:30030/")
          self.driver.set_window_size(1454, 866)
          self.driver.find_element(By.LINK_TEXT, "무료로 시작하기").click()
          self.driver.find_element(By.ID, "email").click()
          self.driver.find_element(By.ID, "email").send_keys("test@gmail.com")
          self.driver.find_element(By.ID, "password").click()
          self.driver.find_element(By.ID, "password").send_keys("1234")
          self.driver.find_element(By.CSS_SELECTOR, "button").click()
      	self.driver.implicitly_wait(2)    # 로그인 요청이 성공하여 logout 버튼이 렌더링될 때까지 임의로 2초 대기
      	self.driver.find_element(By.XPATH, "//p[contains(.,\'로그아웃\')]").click()
      	self.driver.close()

🧪E2E 테스트 케이스 개발

  • driver setup/teardown, 로그인-로그아웃과 같은 공통 부분을 베이스 클래스 작성
  • 베이스 클래스를 상속해 테스트 케이스 클래스 작성
    • 로그인 실패
    • 로그인 성공 → 로그아웃
    • 로그인 성공 → 노트 페이지 → user 정보 assertion → 마지막 노트 클릭 → 로그아웃
  • test_e2e.py
    # Generated by Selenium IDE
    # Run test by "python -m pytest -v"
    import pytest
    import time
    import json
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.action_chains import ActionChains
    from selenium.webdriver.support import expected_conditions
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
    
    BASE_URL = "http://localhost:30030"
    CORRECT_USER = {"email": "test@gmail.com", "password": "1234"}
    WRONG_USER = {"email": "wrong@gmail.com", "password": "wrong"}
    
    class BaseTestClass:
        def setup_method(self, method):
            options = webdriver.ChromeOptions()
            options.add_argument("-headless=new")
            self.driver = webdriver.Chrome(options=options)
            self.wait = WebDriverWait(self.driver, timeout=2)
            self.vars = {}
    
        def teardown_method(self, method):
            self.driver.quit()
    
        def login(self, user):
            self.driver.get(BASE_URL + "/")
            self.driver.find_element(By.LINK_TEXT, "무료로 시작하기").click()
            self.driver.find_element(By.ID, "email").click()
            self.driver.find_element(By.ID, "email").send_keys(user["email"])
            self.driver.find_element(By.ID, "password").click()
            self.driver.find_element(By.ID, "password").send_keys(user["password"])
            self.driver.find_element(By.XPATH, "//button[contains(.,'로그인')]").click()
    
        def logout(self):
            self.driver.find_element(By.XPATH, "//p[contains(.,'로그아웃')]").click()
    
    class TestLoginLogout(BaseTestClass):
        def test_login_fail(self):
            self.login(WRONG_USER)
            alert = self.wait.until(expected_conditions.alert_is_present())
            assert alert.text == "로그인에 실패했습니다."
    
        def test_login_logout(self):
            self.login(CORRECT_USER)
            self.driver.implicitly_wait(2)
            self.logout()
    
    class TestNotesView(BaseTestClass):
        def test_notes_view(self):
            self.login(CORRECT_USER)
            self.driver.implicitly_wait(2)
    
            assert (
                self.driver.find_element(
                    By.CSS_SELECTOR, "#root > div > div.side-bar > div.user > p"
                ).text
                == "test@gmail.com"
            )
    
            self.driver.find_element(By.XPATH, "//a[last()]/p").click()
            self.logout()
    

profile
이것저것 관심 많은 개발자👩‍💻

0개의 댓글