프론트엔드 테스트 표준 및 스타일 가이드

프론트엔드 코드를 개발하는 동안 GitLab에서는 두 가지 유형의 테스트 스위트를 사용합니다. JavaScript 단위 및 통합 테스트에는 Jest를 사용하고, e2e(End-to-End) 통합 테스트에는 RSpec 기능 테스트와 Capybara를 사용합니다.

새로운 기능에 대해 단위 및 기능 테스트를 작성해야 합니다. 대부분의 경우 기능 테스트에는 RSpec를 사용해야 합니다. 기능 테스트를 시작하는 방법에 대한 자세한 정보는 기능 테스트 시작하기를 참조하세요.

버그 수정을 위해 회귀 테스트를 작성하여 미래에 재발되지 않도록 해야 합니다.

GitLab에서의 일반적인 테스트 관행에 대한 자세한 정보는 테스트 표준 및 스타일 가이드 페이지를 참조하세요.

Vue.js 테스트

Vue 컴포넌트 테스트 가이드를 찾고 계시다면, 이 섹션으로 바로 이동할 수 있습니다.

Vue 3의 테스트에 대한 정보는 이 페이지에서 확인할 수 있습니다.

Jest

우리는 Jest를 사용하여 프론트엔드 단위 및 통합 테스트를 작성합니다. Jest 테스트는 EE에서 /spec/frontend/ee/spec/frontend에서 찾을 수 있습니다.

jsdom의 제한 사항

Jest는 테스트를 실행하기 위해 브라우저 대신 jsdom을 사용합니다. 이는 몇 가지 제한 사항을 동반합니다. 특히 다음과 같습니다:

또한 브라우저에서 Jest 테스트를 실행하는 지원을 위한 이슈도 확인하세요. 브라우저에서 Jest 테스트 실행 지원.

Jest 테스트 디버깅

yarn jest-debug를 실행하면 Jest를 디버그 모드로 실행하여 Jest 문서에 설명된 대로 디버그/검사할 수 있습니다.

시간 초과 오류

Jest의 기본 제한 시간은 /jest.config.base.js에 설정되어 있습니다.

테스트가 해당 시간을 초과하면 실패합니다.

테스트의 성능을 개선할 수 없는 경우, 전체 스위트에 대한 제한 시간을 jest.setTimeout을 사용하여 늘릴 수 있습니다.

jest.setTimeout(500);

describe('Component', () => {
  it('does something amazing', () => {
    // ...
  });
});

또는 it에 세 번째 인수를 제공하여 특정 테스트의 제한 시간을 늘릴 수 있습니다.

describe('Component', () => {
  it('does something amazing', () => {
    // ...
  }, 500)
})

각 테스트의 성능은 환경에 따라 달라짐을 기억하세요.

테스트별 스타일시트

RSpec 통합 테스트를 용이하게 하기 위해 두 가지 테스트별 스타일시트가 있습니다. 이를 사용하여 애니메이션을 비활성화하여 테스트 속도를 향상시키거나 Capybara 클릭 이벤트에서 타겟으로 만들어야 하는 요소를 표시할 수 있습니다.

  • app/assets/stylesheets/disable_animations.scss
  • app/assets/stylesheets/test_environment.scss

테스트 환경은 가능한 한 프로덕션 환경과 일치하도록 해야 하므로, 이러한 기능은 필요할 때에만 추가하고 최소한으로 사용해야 합니다.

무엇을 어떻게 테스트할 것인가

모의 및 감시와 같은 Jest 특정 작업 흐름에 대한 더 자세한 내용에 들어가기 전에, 우리는 무엇을 Jest로 테스트할지 간단히 다루어야 합니다.

라이브러리를 테스트하지 마세요

라이브러리는 JavaScript 개발자의 핵심적인 부분입니다. 일반적인 조언은 라이브러리 내부를 테스트하지 말고, 라이브러리가 자기가 뭘 해야 하는지 알고 있고 자체적으로 테스트 커버리지가 있는 것으로 기대하는 것입니다. 일반적 예시로는 다음과 같습니다.

import { convertToFahrenheit } from 'temperatureLibrary'

function getFahrenheit(celsius) {
  return convertToFahrenheit(celsius)
}

getFahrenheit 함수를 테스트하는 것은 의미가 없습니다. 왜냐하면 내부적으로 라이브러리 함수만 호출하기 때문이며, 우리는 기대대로 작동중인 것으로 예상할 수 있습니다.

Vue 랜드로 한 번 빠르게 들여다봅시다. Vue는 GitLab JavaScript 코드베이스의 중요한 부분입니다. Vue 컴포넌트에 대한 스펙을 작성할 때 흔한 함정은 Vue에서 제공하는 기능을 테스트하게 되는 것입니다. 외부 코드와 위쪽 코드는 예로 하면 다음과 같습니다.

// 컴포넌트 스크립트
{
  computed: {
    hasMetricTypes() {
      return this.metricTypes.length;
    },
}
<!-- 컴포넌트 템플릿 -->
<template>
  <gl-dropdown v-if="hasMetricTypes">
    <!-- 드롭다운 내용 -->
  </gl-dropdown>
</template>

hasMetricTypes 계산된 속성을 테스트하는 것은 딱 보기에는 당연하지만, 실제로는 Vue 라이브러리 자체를 테스트하는 것입니다. 이것은 테스트 스위트에 추가하기만 할 뿐 가치가 없으며, 사용자의 상호 작용 방식에 맞게 컴포넌트를 테스트하는 것이 더 나은 방법입니다. 렌더링된 템플릿을 확인하는 것입니다.

// 안 좋은 예
describe('computed', () => {
  describe('hasMetricTypes', () => {
    it('metricTypes가 존재하는 경우 true를 반환합니다', () => {
      factory({ metricTypes });
      expect(wrapper.vm.hasMetricTypes).toBe(2);
    });

    it('metricTypes가 존재하지 않는 경우 true를 반환합니다', () => {
      factory();
      expect(wrapper.vm.hasMetricTypes).toBe(0);
    });
  });
});

// 좋은 예
it('metricTypes가 존재하는 경우 드롭다운을 표시합니다', () => {
  factory({ metricTypes });
  expect(wrapper.findComponent(GlDropdown).exists()).toBe(true);
});

it('metricTypes가 존재하지 않는 경우 드롭다운을 표시하지 않습니다', () => {
  factory();
  expect(wrapper.findComponent(GlDropdown).exists()).toBe(false);
});

이러한 종류의 테스트에 주의하며, 이는 업데이트된 로직을 더 취약하고 지루하게 만드는 것입니다. 이는 다른 라이브러리에 대해서도 동일합니다. 여기서 제안하는 것은: 만약 wrapper.vm 속성을 확인하는 중이라면, 렌더링된 템플릿을 확인하기 위해 테스트를 중단하고 다시 생각해보아야 합니다.

프론트엔드 단위 테스트 섹션에서 더 많은 예제를 찾을 수 있습니다 (testing_levels.md#frontend-unit-tests).

// Common practices for writing tests

다음은 테스트 스위트의 일부로 포함된  가지 일반적인 관행입니다.  안내서에 명시되지 않은 사항이 있다면, 이상적으로 즉시 수정하십시오. 🎉

### DOM 요소 쿼리 방법

테스트에서 DOM 요소를 쿼리하는 경우, 해당 요소를 고유하고 의미 있게 대상화하는 것이 가장 좋습니다.

보통, [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro/)를 사용하여 사용자가 실제로 볼 수 있는 것을 대상으로 하는 것이 좋습니다. 텍스트로 선택하는 경우, [`byRole`](https://testing-library.com/docs/queries/byrole/) 쿼리를 사용하는 것이 최선입니다. (`name`이 적절하게 설정되었는지 확인하는 데 도움이 됨) 대신 [`shallowMountExtended` 또는 `mountExtended`](#shallowmountextended-and-mountextended)을 사용할 때 [`findByRole`](https://testing-library.com/docs/queries/about/) 및 다른 DOM Testing Library 쿼리가 사용 가능합니다.

Vue 컴포넌트 단위 테스트를 작성할 , 단위 테스트가 아닌 종합적인  커버리지에 초점을 맞추기 위해 자식 컴포넌트를 쿼리하는 것이 현명할  있습니다.

때로는 위의  가지 방법 모두 사용할  없을  있습니다. 이러한 경우, 셀렉터를 간소화하기 위해 테스트 속성을 추가하는 것이 가장 좋을  있습니다. 가능한 셀렉터 목록은 다음과 같습니다:

- `name` 같은 의미 있는 애트리뷰트 (`name` 올바르게 설정되었는지 확인하는 것도 포함)
- `data-testid` 애트리뷰트 ([`@vue/test-utils` 개발자가 권장하는 내용](https://github.com/vuejs/vue-test-utils/issues/1498#issuecomment-610133465) )
  - `shallowMountExtended` 또는 `mountExtended` 사용할 

```javascript
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'

const wrapper = shallowMountExtended(ExampleComponent);

it('존재 여부', () => {
  // 최상 (특히 종합 테스트의 경우)
  wrapper.findByRole('link', { name: /Click Me/i })
  wrapper.findByRole('link', { name: 'Click Me' })
  wrapper.findByText('Click Me')
  wrapper.findByText(/Click Me/i)

  // 양호 (특히 단위 테스트의 경우)
  wrapper.findComponent(FooComponent);
  wrapper.find('input[name=foo]');
  wrapper.find('[data-testid="my-foo-id"]');
  wrapper.findByTestId('my-foo-id'); // with shallowMountExtended or mountExtended – 확인은 아래에

  // 나쁨
  wrapper.find({ ref: 'foo'});
  wrapper.find('.js-foo');
  wrapper.find('.gl-button');
});

data-testid 애트리뷰트에는 kebab-case를 사용해야 합니다.

테스트 목적으로 .js-* 클래스를 추가하는 것은 권장되지 않습니다. 다른 가능한 옵션이 없을 때에만 수행하십시오. 템플릿 ref를 사용하여 DOM 요소를 쿼리하는 것은 권장되지 않습니다. 이것은 컴포넌트의 구현 세부 정보가 아니라 공개 API이기 때문입니다.

자식 컴포넌트에 대한 쿼리

@vue/test-utils를 사용하여 Vue 컴포넌트를 테스트하는 경우, 테스트할 기능의 구현 세부 정보를 커버하는 대신 자식 컴포넌트를 쿼리하는 것이 가능한 접근법 중 하나입니다. 테스트하려는 컴포넌트의 예상 동작을 신뢰할 수 있는 방법으로 커버하면 되므로, DOM 노드를 쿼리하는 것 또는 컴포넌트 쿼리를 작성하는 것 사이의 강력한 선호 사항은 없습니다.

예시:

it('존재 여부', () => {
  wrapper.findComponent(FooComponent);
});

단위/컴포넌트 테스트 이름 짓기

단위/컴포넌트 테스트는 ${componentName}_spec.js로 지어야 합니다.

테스트 이름이 충분히 구체적이지 않다면, 컴포넌트의 이름을 변경하는 것을 고려하십시오.

예시:

diff_stats_dropdown.vuediff_stats_dropdown_spec.js라는 단위/컴포넌트 테스트를 가져야 합니다.

Describe 블록 이름 짓기

특정 함수/메소드를 테스트하는 describe 테스트 블록을 작성할 때, 해당 메소드 이름을 describe 블록 이름으로 사용하십시오.

나쁨:

describe('#methodName', () => {
  it('통과', () => {
    expect(true).toEqual(true);
  });
});

describe('.methodName', () => {
  it('통과', () => {
    expect(true).toEqual(true);
  });
});

좋음:

describe('methodName', () => {
  it('통과', () => {
    expect(true).toEqual(true);
  });
});

Promises 테스트

Promise를 테스트할 때는 테스트가 비동기임을 항상 확인하고 거부 사항이 처리되었는지 확인해야 합니다. 이제 테스트 스위트에서 async/await 구문을 사용할 수 있습니다.

it('프로미스 테스트', async () => {
  const users = await fetchUsers()
  expect(users.length).toBe(42)
});

it('프로미스 거부 테스트', async () => {
  await expect(user.getUserName(1)).rejects.toThrow('User with 1 not found.');
});

테스트 함수에서 프로미스를 반환할 수도 있습니다.

프로미스와 함께 donedone.fail 콜백을 사용하는 것은 피해야 합니다.

나쁨:

// 반환 누락
it('프로미스 테스트', () => {
  promise.then(data => {
    expect(data).toBe(asExpected);
  });
});

// done/done.fail 사용
it('프로미스 테스트', done => {
  promise
    .then(data => {
      expect(data).toBe(asExpected);
    })
    .then(done)
    .catch(done.fail);
});

좋음:

// 해결된 프로미스 확인
it('프로미스 테스트', () => {
  return promise
    .then(data => {
      expect(data).toBe(asExpected);
    });
});

// 해결된 프로미스를 Jest의 `resolves` 매처를 사용하여 확인
it('프로미스 테스트', () => {
  return expect(promise).resolves.toBe(asExpected);
});

// 거부된 프로미스를 Jest의 `rejects` 매처를 사용하여 확인
it('프로미스 거부 테스트', () => {
  return expect(promise).rejects.toThrow(expectedError);
});

### 시간 조작

가끔은 시간에 민감한 코드를 테스트해야 할 때가 있습니다. 예를 들어 X초마다 실행되는 주기적인 이벤트와 같은 것들이 있습니다. 이에 대응하기 위한 몇 가지 전략이 있습니다:

#### 애플리케이션 내부의 `setTimeout()` / `setInterval()`

애플리케이션이 일정 시간을 기다리는 경우, 기다리는 것을 가장하는 것이 좋습니다. Jest의 경우 이것은 이미 [기본 상태로 설정](https://gitlab.com/gitlab-org/gitlab/-/blob/a2128edfee799e49a8732bfa235e2c5e14949c68/jest.config.js#L47)되어 있습니다 (또한 [Jest 타이머 가장하기](https://jestjs.io/docs/timer-mocks)를 참조하세요).

```javascript
const doSomethingLater = () => {
  setTimeout(() => {
    // 어떤 동작 수행
  }, 4000);
};

Jest에서:

it('어떤 동작을 수행함', () => {
  doSomethingLater();
  jest.runAllTimers();

  expect(something).toBe('done');
});

Jest에서 현재 위치 가장하기

주의: window.location.href의 값은 이전 테스트에 영향을 주지 않도록 모든 테스트 전에 재설정됩니다.

테스트가 특정한 값을 가진 window.location.href가 필요할 경우, setWindowLocation 헬퍼를 사용하세요:

import setWindowLocation from 'helpers/set_window_location_helper';

it('통과함', () => {
  setWindowLocation('https://gitlab.test/foo?bar=true');

  expect(window.location).toMatchObject({
    hostname: 'gitlab.test',
    pathname: '/foo',
    search: '?bar=true',
  });
});

해시값만 수정하려면, setWindowLocation 헬퍼를 사용하거나, 예를 들어 직접 window.location.hash에 할당하세요:

it('통과함', () => {
  window.location.hash = '#foo';

  expect(window.location.href).toBe('http://test.host/#foo');
});

만약 테스트가 특정 window.location 메서드가 호출되었는지 확인해야 한다면, useMockLocationHelper 헬퍼를 사용하세요:

import { useMockLocationHelper } from 'helpers/mock_window_location_helper';

useMockLocationHelper();

it('통과함', () => {
  window.location.reload();

  expect(window.location.reload).toHaveBeenCalled();
});

테스트에서의 기다림

어떤 동작이 애플리케이션에서 일어나기를 기다려야 하는 경우가 있습니다.

다음을 회피해야 합니다:

  • setTimeout은 기다림의 이유를 모호하게 만듭니다. 게다가 테스트에서 가짜처리되므로 사용이 까다로울 수 있습니다.
  • setImmediate는 Jest 27 버전 이후로 더 이상 지원되지 않습니다. 자세한 내용은 이 에픽을 참조하세요.

Promises 및 Ajax 호출

Promise가 해결될 때까지 대기하기 위해 핸들러 함수를 등록하세요.

const askTheServer = () => {
  return axios
    .get('/endpoint')
    .then(response => {
      // 어떤 동작 수행
    })
    .catch(error => {
      // 다른 동작 수행
    });
};

Jest에서:

it('Ajax 호출을 기다림', async () => {
  await askTheServer()
  expect(something).toBe('done');
});

Promise에 핸들러를 등록할 수 없는 경우, 예를 들어 동기적으로 Vue 라이프사이클 후크에서 실행되기 때문에, waitFor 헬퍼를 확인하거나 보류 중인 모든 Promise를 강제로 처리하세요:

Jest에서:

it('Ajax 호출을 기다림', async () => {
  synchronousFunction();

  await waitForPromises();

  expect(something).toBe('done');
});

Vue 렌더링

nextTick()을 사용하여 Vue 컴포넌트가 다시 렌더링될 때까지 기다리세요.

Jest에서:

import { nextTick } from 'vue';

// ...

it('어떤 것을 렌더링함', async () => {
  wrapper.setProps({ value: '새로운 값' });

  await nextTick();

  expect(wrapper.text()).toBe('새로운 값');
});

이벤트

애플리케이션이 특정 이벤트를 트리거하고 해당 이벤트를 테스트에서 기다려야 하는 경우, 어설션을 포함하는 이벤트 핸들러를 등록하세요:

it('이벤트를 기다림', () => {
  eventHub.$once('someEvent', eventHandler);

  someFunction();

  return new Promise((resolve) => {
    function expectEventHandler() {
      expect(something).toBe('done');
      resolve();
    }
  });
});

Jest에서는 이를 위해 Promise를 사용할 수도 있습니다:

it('이벤트를 기다림', () => {
  const eventTriggered = new Promise(resolve => eventHub.$once('someEvent', resolve));

  someFunction();

  return eventTriggered.then(() => {
    expect(something).toBe('done');
  });
});

gon 객체 조작

gon (또는 window.gon)은 백엔드로부터 데이터를 전달하는 데 사용되는 전역 객체입니다. 테스트가 해당 값에 의존하는 경우 직접 수정할 수 있습니다:

describe('로그인한 경우', () => {
  beforeEach(() => {
    gon.current_user_id = 1;
  });

  it('메시지가 표시됨', () => {
    expect(wrapper.text()).toBe('로그인됨!');
  });
})

gon은 테스트가 격리되도록 모든 테스트마다 재설정됩니다.

테스트 격리 여부 확인

테스트는 일반적으로 테스트 대상 컴포넌트를 반복 설정하는 패턴으로 설계됩니다. 이는 주로 beforeEach 후크를 사용하여 구현됩니다.

예시

  let wrapper;

  beforeEach(() => {
    wrapper = mount(Component);
  });

enableAutoDestroy를 사용하면 wrapper.destroy()를 수동으로 호출할 필요가 없어집니다. 그러나 일부 목, 스파이 및 fixture는 철거되어야 하며, 이를 위해 afterEach 후크를 활용할 수 있습니다.

예시

  let wrapper;

  afterEach(() => {
    fakeApollo = null;
    store = null;
  });

로컬 전용 Apollo 쿼리 및 뮤테이션 테스트

백엔드에 추가하기 전에 새로운 쿼리나 뮤테이션을 추가하려면 @client 지시어를 사용할 수 있습니다. 예를 들어:

mutation setActiveBoardItemEE($boardItem: LocalBoardItem, $isIssue: Boolean = true) {
  setActiveBoardItem(boardItem: $boardItem) @client {
    ...Issue @include(if: $isIssue)
    ...EpicDetailed @skip(if: $isIssue)
  }
}

이러한 호출에 대한 테스트 케이스를 작성할 때, 리졸버를 사용하여 올바른 매개변수로 호출되는지 확인할 수 있습니다.

예를 들어, 래퍼를 만들 때, 리졸버가 쿼리 또는 뮤테이션에 매핑되어 있는지 확인해야 합니다. 여기서 우리가 가장하는 뮤테이션은 setActiveBoardItem 입니다:

const mockSetActiveBoardItemResolver = jest.fn();
const mockApollo = createMockApollo([], {
    Mutation: {
      setActiveBoardItem: mockSetActiveBoardItemResolver,
    },
});

다음 코드에서는 네 개의 인수를 전달해야 합니다. 두 번째 인수는 가장하는 쿼리나 뮤테이션의 입력 변수의 콜렉션이어야 하며, 올바른 매개변수로 뮤테이션이 호출되는지 테스트해야 합니다:

it('close 시 setActiveBoardItemMutation 호출', async () => {
    wrapper.findComponent(GlDrawer).vm.$emit('close');

    await waitForPromises();

    expect(mockSetActiveBoardItemResolver).toHaveBeenCalledWith(
        {},
        {
            boardItem: null,
        },
        expect.anything(),
        expect.anything(),
    );
});

Jest best practices

기본 값 비교시 toEqual보다는 toBe를 선호하세요

Jest에는 toBetoEqual 매처가 있습니다. toBe는 값 비교에 Object.is를 사용하기 때문에 toEqual보다 기본적으로 빠릅니다. 후자는 결국 복잡한 객체의 비교가 필요할 때에만 사용해야 합니다.

예시:

const foo = 1;

// 나쁨
expect(foo).toEqual(1);

// 좋음
expect(foo).toBe(1);

더 적합한 매처를 선호하세요

Jest는 toHaveLengthtoBeUndefined와 같은 유용한 매처를 제공하여 테스트를 더 읽기 쉽고 이해하기 쉬운 오류 메시지를 생성할 수 있도록 도와줍니다. 전체 매처 리스트는 여기에서 확인하세요.

예시:

const arr = [1, 2];

// 출력:
// 기대값: 1
// 받은 값: 2
expect(arr).toHaveLength(1);

// 출력:
// 기대값: 1
// 받은 값: 2
expect(arr.length).toBe(1);

// 출력:
// expect(received).toBe(expected) // Object.is equality
// 기대값: undefined
// 받은 값: "bar"
const foo = 'bar';
expect(foo).toBe(undefined);

// 출력:
// expect(received).toBeUndefined()
// 받은 값: "bar"
const foo = 'bar';
expect(foo).toBeUndefined();

toBeTruthy 또는 toBeFalsy 사용을 피하세요

Jest는 toBeTruthytoBeFalsy 매처를 제공하지만 사용하지 말아야 합니다. 왜냐하면 이러한 매처는 테스트를 약화시키고 잘못된 긍정 결과를 초래하기 때문입니다.

예를 들어, expect(someBoolean).toBeFalsy()someBoolean === null 또는 someBoolean === false 일 때 통과합니다.

까다로운 toBeDefined 매처

Jest에는 재미있는 toBeDefined 매처가 있습니다. 이 매처는 false positive 테스트를 생성할 수 있습니다. 왜냐하면 이는 주어진 값이 undefined인 경우에만 검증을 수행하기 때문입니다.

// 나쁨: 만약 finder가 null을 반환하면 테스트는 통과합니다
expect(wrapper.find('foo')).toBeDefined();

// 좋음
expect(wrapper.find('foo').exists()).toBe(true);

setImmediate 사용을 피하세요

setImmediate 사용을 피하세요. setImmediate는 I/O가 완료된 후 콜백을 실행하기 위한 임시적인 해결책입니다. 이는 Web API의 일부가 아니므로 우리의 단위 테스트에서는 NodeJS 환경을 대상으로 합니다.

setImmediate 대신 보류 중인 타이머를 실행하기 위해 jest.runAllTimers 또는 jest.runOnlyPendingTimers를 사용하세요. 후자는 코드에 setInterval이 있는 경우 유용합니다. 기억하세요: 우리의 Jest 구성은 가짜 타이머를 사용합니다.

비결정적인 스펙 피하기

비결정적인 것은 허약하고 부서지기 쉬운 스펙의 둥지입니다. 이러한 스펙은 CI 파이프라인을 망가뜨리고 다른 기여자들의 작업 흐름을 방해할 수 있습니다.

  1. 당신의 테스트 대상의 협업자(예: Axios, Apollo, Lodash 도우미)와 테스트 환경(예: Date)이 시스템 및 시간을 통해 일관되게 동작하는지 확인하세요.
  2. 테스트가 “추가 작업”을 수행하지 않도록 집중되어 있는지 확인하세요(예: 단일 테스트에서 테스트 대상을 불필요하게 여러 번 생성하는 것).

결정론적으로 Date 가짜로 만들기

Date는 기본적으로 Jest 환경에서 가짜로 만들어집니다. 즉, Date() 또는 Date.now() 호출마다 고정 결정론적인 값이 반환됩니다.

만약 실제로 기본 가짜 날짜를 변경하고 싶다면, describe 블록 내부에서 useFakeDate를 호출할 수 있으며, 해당 describe 컨텍스트 내의 해당 스펙에서는 해당 날짜가 대체됩니다.

import { useFakeDate } from 'helpers/fake_date';

describe('cool/component', () => {
  // 기본 가짜 `Date`
  const TODAY = new Date();

  // 참고: `useFakeDate`는 테스트 실행 중(즉, `it`, `beforeEach`, `beforeAll` 내부)에서 호출할 수 없습니다.
  describe("on Ada Lovelace's Birthday", () => {
    useFakeDate(1815, 11, 10)

    it('날짜가 기본값이 아님', () => {
      expect(new Date()).not.toEqual(TODAY);
    });
  });

  it('이 스코프에서는 여전히 기본 날짜임', () => {
    expect(new Date()).toEqual(TODAY)
  });
})

마찬가지로, 실제 Date 클래스를 사용해야 하는 경우에는 describe 블록 내부에서 useRealDate를 호출할 수 있습니다.

import { useRealDate } from 'helpers/fake_date';

// 참고: `useRealDate`는 테스트 실행 중(즉, `it`, `beforeEach`, `beforeAll` 내부)에서 호출할 수 없습니다.
describe('with real date', () => {
  useRealDate();
});

결정론적으로 Math.random 가짜로 만들기

테스트 대상이 Math.random에 의존하는 경우, 가짜로 대체하는 것을 고려해보세요.

beforeEach(() => {
  // https://xkcd.com/221/
  jest.spyOn(Math, 'random').mockReturnValue(0.4);
});

테스트에서의 콘솔 경고 및 에러

예기치 않은 콘솔 경고와 에러는 우리의 프로덕션 코드에 문제가 있다는 신호입니다. 우리는 테스트 환경을 엄격하게 유지하고자 합니다. 따라서 예상치 않은 console.error 또는 console.warn 호출 시 테스트가 실패해야 합니다.

감시기에서 콘솔 메시지 무시하기

우리의 제어 영역을 벗어난 많은 코드가 있기 때문에, 기본적으로 무시되어야 하는 콘솔 메시지가 있습니다. 예상 외의 콘솔 메시지는 테스트가 실패하지 않으며 무시됩니다. 이러한 무시할 메시지 목록은 setupConsoleWatcher 호출 지점에서 유지될 수 있습니다. 예시:

setupConsoleWatcher({
  ignores: [
    ...,
    // `console.error('Foo bar')` 또는 `console.warn('Foo bar')` 호출은 우리의 콘솔 감시자에 의해 무시될 것입니다.
    'Foo bar',
    // 유연한 메시지 매칭을 위해 정규식 사용
    /Lorem ipsum/,
  ]
});

특정 테스트가 특정 메시지를 describe 블록에서 무시해야 하는 경우, 해당 describe 상단에서 ignoreConsoleMessages 헬퍼를 사용하세요. 이는 테스트 컨텍스트 내의 이 무시 세트를 설정하고 해제하기 위해 자동으로 beforeAllafterAll을 호출합니다.

이것을 가급적 삼가하고 테스트 유지에 절대적으로 필요한 경우에만 사용하세요. 예시:

import { ignoreConsoleMessages } from 'helpers/console_watcher';

describe('foos/components/foo.vue', () => {
  describe('when blooped', () => {
    // `console.warn('Lorem ipsum')` 호출 시 테스트가 실패하지 않을 것입니다
    ignoreConsoleMessages([
      /^Lorem ipsum/
    ]);
  });

  describe('default', () => {
    // `console.warn('Lorem ipsum')` 호출 시 테스트가 실패할 것입니다
  });
});

팩토리

TBU

Jest를 사용한 모의 전략

스파이 및 모의

일반적으로 스텁 또는 스파이는 동의어로 사용됩니다. Jest에서는 .spyOn 메서드의 덕분에 매우 쉽습니다. 공식 문서 더 어려운 부분은 함수 또는 종속성에 사용할 수있는 모의입니다.

수동 모듈 모의

수동 모의는 전체 Jest 환경에서 모듈을 모의화하는 데 사용됩니다. 이것은 우리의 테스트 환경에서 쉽게 사용할 수없는 모듈을 모의화하여 단위 테스트를 단순화하는 매우 강력한 테스트 도구입니다.

경고: 일부 스펙에서만 필요한 경우 수동 모의를 사용하지 마세요. 대신, 관련된 스펙 파일에서 jest.mock(..) (또는 유사한 모의 기능)을 사용하는 것을 고려하십시오.

수동 모의를 어디에 두어야합니까?

Jest는 소스 모듈 옆에 __mocks__/ 디렉토리에 모의를 배치하여 수동 모듈 모의를 지원합니다 (예: app/assets/javascripts/ide/__mocks__). 이것은 하지 마십시오. 테스트 관련 코드를 모두 하나의 장소( spec/ 폴더)에 유지하려고합니다.

node_modules 패키지에 대해 수동 모의가 필요한 경우 spec/frontend/__mocks__ 폴더를 사용합니다. 다음은 패키지 monaco-editor에 대한 Jest 모의의 예입니다.

CE 모듈에 수동 모의가 필요한 경우, 구현을 spec/frontend/__helpers__/mocks에 배치하고 frontend/test_setup (또는 frontend/shared_test_setup)에 다음과 유사한 항목을 추가합니다.

// "~/lib/utils/axios_utils"은 실제 모듈의 경로입니다
// "helpers/mocks/axios_utils"는 모의 구현의 경로입니다
jest.mock('~/lib/utils/axios_utils', () => jest.requireActual('helpers/mocks/axios_utils'));

수동 모의 예제

  • __helpers__/mocks/axios_utils - 이 모의는 테스트를 통과하지 못하도록 하고, 또한 axios.waitForAll과 같은 일부 테스트 도우미를 주입할 수 있기 때문에 유용합니다.
  • __mocks__/mousetrap/index.js - 이 모의는 모듈 자체가 webpack이 이해하는 AMD 형식을 사용하지만 jest 환경과 호환되지 않습니다. 이 모의는 어떤 동작도 제거하지 않고 깔끔한 es6 호환 래퍼만 제공합니다.
  • __mocks__/monaco-editor/index.js - 이 모의는 Monaco 패키지가 Jest 환경에서 완전히 호환되지 않기 때문에 유용합니다. 실제로 webpack은 동작하려면 특별한 로더가 필요합니다. 이 모의는 Jest에서 이 패키지를 사용할 수 있게 합니다.

모의를 가볍게 유지

전역 모의는 마법을 도입하고 기술적으로 테스트 커버리지를 줄일 수 있습니다. 모의화가 이윤이 있다고 판단될 때:

  • 모의를 짧고 집중시키십시오.
  • 모의에 왜 필요한지에 대한 최상위 주석을 남겨두세요.

추가 모의 기술

사용 가능한 모의화 기능에 대한 전체 개요는 공식 Jest 문서를 참조하십시오.

프론트엔드 테스트 실행

픽스처를 생성하기 전에 GDK 인스턴스가 실행 중임을 확인하십시오.

프론트엔드 테스트를 실행하려면 다음 명령이 필요합니다.

  • rake frontend:fixtures(재) 픽스처를 생성. 필요에 따라서 fixtures가 업데이트되기 전에 테스트를 실행하기 전에 업데이트되었는지 확인하십시오.
  • yarn jest는 Jest 테스트를 실행합니다.

실시간 테스트 및 포커스 테스트 – Jest

테스트 스위트에서 작업하는 동안 이 스펙을 워치 모드에서 실행하여 모든 저장 시 자동으로 다시 실행되도록 할 수 있습니다.

# 아이콘 이름과 일치하는 모든 스펙을 감시하고 다시 실행합니다.
yarn jest --watch icon

# 특정 파일 하나를 감시하고 다시 실행합니다.
yarn jest --watch path/to/spec/file.spec.js

또한 --watch 플래그없이 일부 포커스 테스트를 실행할 수도 있습니다.

# 특정 jest 파일을 실행합니다.
yarn jest ./path/to/local_spec.js
# 특정 jest 폴더를 실행합니다.
yarn jest ./path/to/folder/
# 경로에 용어가 포함된 모든 jest 파일을 실행합니다.
yarn jest term

프론트엔드 테스트 픽스처

프론트엔드 픽스처는 백엔드 컨트롤러에서의 응답을 포함하는 파일입니다. 이러한 응답은 HAML 템플릿에서 생성된 HTML 또는 JSON 페이로드일 수 있습니다. 이러한 응답에 의존하는 프론트엔드 테스트는 백엔드 코드와의 올바른 통합을 확인하기 위해 종종 픽스처를 사용합니다.

픽스처 사용

JSON 또는 HTML 픽스처를 가져오려면 test_fixtures 별칭을 사용하여 import하십시오.

import responseBody from 'test_fixtures/some/fixture.json' // tmp/tests/frontend/fixtures-ee/some/fixture.json을 로드합니다.

it('request를 만듭니다', () => {
  axiosMock.onGet(endpoint).reply(200, responseBody);

  myButton.click();

  // ...
});

픽스처 생성

테스트에서 실행하기 위한 픽스처를 생성하는 코드를 찾을 수 있습니다.

  • CE에서 테스트 실행을위한 spec/frontend/fixtures/,
  • EE에서 테스트 실행을위한 ee/spec/frontend/fixtures/에 테스트 픽스처 생성 코드가 있습니다.

아래의 방법으로 픽스처를 생성할 수 있습니다.

  • bin/rake frontend:fixtures를 실행하여 모든 픽스처를 생성합니다
  • 특정 픽스처(이 경우 merge_request.rb에 대한 픽스처)를 생성하려면 bin/rspec spec/frontend/fixtures/merge_requests.rb를 실행하십시오.

생성된 픽스처는 tmp/tests/frontend/fixtures-ee에 있습니다.

픽스처 다운로드

우리는 GitLab CI에서 픽스처를 생성하고 패키지 레지스트리에 저장합니다.

scripts/frontend/download_fixtures.sh 스크립트는 그러한 픽스처를 로컬에서 다운로드하고 추출하는 데 사용됩니다.

# 로컬 브랜치의 커밋을 확인하여 gitlab-org/gitlab의 frontend 픽스처 패키지가 있는지 확인합니다.
# 패키지가 존재하면 다운로드하고 추출합니다
$ scripts/frontend/download_fixtures.sh

# 위의 것과 동일하지만, 현재 체크아웃된 브랜치의 마지막 10개의 커밋만 확인합니다.
$ scripts/frontend/download_fixtures.sh --max-commits=10

# 현재 체크아웃된 브랜치 대신 로컬 마스터 브랜치의 커밋을 확인합니다.
$ scripts/frontend/download_fixtures.sh --branch master

새로운 픽스처 생성

각 픽스처마다 spec/frontend/fixtures/merge_requests.rb"merge_requests/diff_discussion.json"와 같은 테스트에서 response 변수의 내용을 찾을 수 있습니다. 이 테스트는 type: :request 또는 type: :controller로 표시된 경우에 자동으로 설정됩니다.

새로운 픽스처를 생성할 때, 일반적으로 (ee/)spec/controllers/ 또는 (ee/)spec/requests/에 대한 해당 엔드포인트 테스트를 확인하는 것이 유용합니다.

GraphQL 쿼리 픽스처

get_graphql_query_as_string 도우미 메서드를 사용하여 GraphQL 쿼리의 결과를 나타내는 픽스처를 만들 수 있습니다. 예를 들면:

# spec/frontend/fixtures/releases.rb

describe GraphQL::Query, type: :request do
  include GraphqlHelpers

  all_releases_query_path = 'releases/graphql/queries/all_releases.query.graphql'

  it "graphql/#{all_releases_query_path}.json" do
    query = get_graphql_query_as_string(all_releases_query_path)

    post_graphql(query, current_user: admin, variables: { fullPath: project.full_path })

    expect_graphql_errors_to_be_empty
  end
end

이렇게 하면 tmp/tests/frontend/fixtures-ee/graphql/releases/graphql/queries/all_releases.query.graphql.json에 위치한 새로운 픽스처가 생성됩니다.

test_fixtures 별칭을 사용하여 Jest 테스트에서 JSON 픽스처를 가져올 수 있습니다. 이전에 설명한 내용과 같습니다.

데이터 주도 테스트

RSpec의 매개변수화된 테스트와 유사하게, Jest는 다음을 위한 데이터 주도 테스트를 지원합니다:

  • test.each (별칭: it.each)를 사용하여 각각의 테스트를 수행합니다.
  • describe.each를 사용하여 테스트 그룹을 수행합니다.

이러한 방법은 테스트 내에서 중복을 줄이는 데 유용합니다. 각 옵션은 데이터 값의 배열이나 태그드 템플릿 리터럴을 사용할 수 있습니다.

예를 들어:

// 테스트할 함수
const icon = status => status ? 'pipeline-passed' : 'pipeline-failed'
const message = status => status ? 'pipeline-passed' : 'pipeline-failed'

// 배열 블록으로 테스트
it.each([
    [false, 'pipeline-failed'],
    [true, 'pipeline-passed']
])('아이콘(%s)은 %s를 반환합니다',
 (status, icon) => {
    expect(renderPipeline(status)).toEqual(icon)
 }
);

참고: 템플릿 리터럴 블록은 예쁜 출력이 필요하지 않은 경우에만 사용하십시오. 예를 들어 빈 문자열, 중첩된 객체 등입니다.

템플릿 리터럴 블록을 사용하여 빈 검색 문자열과 비어 있지 않은 검색 문자열 간의 차이를 테스트하는 경우, 예쁜 출력 옵션을 사용하는 것이 좋습니다. 그렇게 하면 빈 문자열('')과 비어 있지 않은 문자열('search string') 간의 차이가 스펙 출력에 표시됩니다. 반면 템플릿 리터럴 블록을 사용하면 빈 문자열이 공백으로 표시되어 혼란스러운 개발자 경험으로 이어질 수 있습니다.

// 나쁜 예
it.each`
    검색어 | 기대값
    ${''} | ${{ issue: { users: { nodes: [] } } }}
    ${'search term'} | ${{ issue: { other: { nested: [] } } }}
`('$searchTerm이(가) 있을 때, $expected를 반환한다', ({ searchTerm, expected }) => {
  expect(search(searchTerm)).toEqual(expected)
});

// 좋은 예
it.each([
    ['', { issue: { users: { nodes: [] } } }],
    ['search term', { issue: { other: { nested: [] } } }],
])('$searchTerm이(가) 있을 때, $expected를 반환한다',
 (searchTerm, expected) => {
    expect(search(searchTerm)).toEqual(expected)
 }
);

// 태그드 템플릿 리터럴 블록을 사용한 테스트 수트
describe.each`
    상태   | 아이콘             | 메시지
    ${false} | ${'pipeline-failed'} | ${'Pipeline failed - boo-urns'}
    ${true}  | ${'pipeline-passed'} | ${'Pipeline succeeded - win!'}
`('파이프라인 컴포넌트', ({ status, icon, message }) => {
    it(`상태 ${status}의 아이콘은 ${icon}를 반환합니다`, () => {
        expect(icon(status)).toEqual(message)
    })

    it(`상태 ${status}의 메시지는 ${message}를 반환합니다`, () => {
        expect(message(status)).toEqual(message)
    })
});

주의 사항

자바스크립트로 인한 RSpec 오류

기본적으로 RSpec 단위 테스트는 브라우저에서 자바스크립트를 실행하지 않으며, rails에서 생성된 HTML을 검사합니다.

통합 테스트가 올바르게 실행되려면 자바스크립트 실행이 필요한 경우, 테스트 실행 시 자바스크립트를 활성화하도록 설정해야 합니다. 그렇지 않으면 테스트 실패에 대한 모호한 오류 메시지가 표시될 수 있습니다.

RSpec 테스트에서 자바스크립트 드라이버를 활성화하려면, 개별 스펙 또는 자바스크립트를 활성화해야 하는 여러 스펙을 포함하는 컨텍스트 블록에 :js를 추가하십시오:

# 하나의 스펙에 대해
it '사용자에 대한 정보 표시', :js do
  # 어설션...
end

describe "Admin::AbuseReports", :js do
  it '사용자에 대한 정보 표시' do
    # 어설션...
  end
  it '신고에 추가하기 버튼 표시' do
    # 어설션...
  end
end

비동기 가져오기로 인한 Jest 테스트 제한 시간 초과

모듈이 런타임에서 비동기적으로 다른 모듈을 가져오는 경우, Jest 로더가 런타임에 해당 모듈을 변환하고 캐시해야 합니다. 이로 인해 Jest가 제한 시간을 초과할 수 있습니다.

이 문제가 발생하는 경우, Jest가 컴파일하고 캐시하도록 강제하여 이 문제를 해결할 수 있습니다.

다음 예를 살펴보십시오:

// the_subject.js

export default {
  components: {
    // 부피가 크고 항상 사용되는 것은 아닌 Thing을 비동기적으로 가져옵니다.
    Thing: () => import(/* webpackChunkName: 'thing' */ './path/to/thing.vue'),
  }
};

Jest는 thing.vue 모듈을 자동적으로 변환하지 않으며, 그 크기에 따라 Jest가 시간을 초과할 수 있습니다. Jest가 이 모듈을 변환하고 캐시하도록 하기 위해 다음처럼 강제로 가져오면 됩니다:

// the_subject_spec.js

import Subject from '~/feature/the_subject.vue';

// Jest에게 변환 및 캐시하도록 강제
// eslint-disable-next-line no-unused-vars
import _Thing from '~/feature/path/to/thing.vue';

참고: 테스트 제한 시간을 무시하지 마십시오. 이는 실제로 프로덕션 문제가 있을 수 있는 신호일 수 있습니다. 여기에 대한 기회를 활용하여 프로덕션 웹팩 번들 및 청크를 분석하고 비동기적 가져오기로 인한 실제 프로덕션 문제가 없는지 확인하십시오.

프론트엔드 테스트 레벨 개요

프론트엔드 테스트 레벨에 대한 주요 정보는 테스트 레벨 페이지에서 찾을 수 있습니다.

프론트엔드 개발에 관련된 테스트는 다음 위치에서 찾을 수 있습니다.

  • Jest 테스트를 위한 spec/frontend/
  • RSpec 테스트를 위한 spec/features/

RSpec는 완전한 기능 테스트를 실행하고, Jest 디렉터리에는 프론트엔드 단위 테스트, 프론트엔드 컴포넌트 테스트프론트엔드 통합 테스트가 포함되어 있습니다.

2018년 5월 이전까지 feature/에는 Spinach에서 실행된 기능 테스트도 포함되어 있었습니다. 이러한 테스트는 2018년 5월에 코드베이스에서 제거되었습니다 (#23036).

또한 Vue 컴포넌트 테스트에 대한 참고 사항을 참조하세요.

테스트 헬퍼

테스트 헬퍼는 spec/frontend/__helpers__에서 찾을 수 있습니다. 새로운 헬퍼를 도입하는 경우 해당 디렉토리에 배치하세요.

Vuex 헬퍼: testAction

우리는 공식 문서에 따라 테스트 액션을 더 쉽게 만들기 위해 사용 가능한 헬퍼를 갖고 있습니다.

// 현재 이 방식을 선호합니다. 매개변수가 테스트를 읽을 때 명확하게 보이도록 단일 객체 인수를 사용합니다.
await testAction({
  action: actions.actionName,
  payload: { deleteListId: 1 },
  state: { lists: [1, 2, 3] },
  expectedMutations: [ { type: types.MUTATION} ],
  expectedActions: [],
});

// 기존 방식, 새로운 테스트에는 이 방식을 사용하지 마세요
testAction(
  actions.actionName, // 액션
  { }, // 액션에 전달할 매개변수
  state, // 상태
  [
    { type: types.MUTATION},
    { type: types.MUTATION_1, payload: {}},
  ], // 커밋된 뮤테이션
  [
    { type: 'actionName', payload: {}},
    { type: 'actionName1', payload: {}},
  ] // 디스패치된 액션
  done,
);

Axios 요청 완료 대기

spec/frontend/__helpers__/mocks/axios_utils.js에 있는 Axios Utils 모의 모듈에는 HTTP 요청을 생성하는 Jest 테스트를 위한 두 가지 헬퍼 메서드가 포함되어 있습니다. 이 메서드들은 Vue 컴포넌트가 생명주기의 일부로 요청을 수행할 때 Promise의 처리를 처리할 수 없는 경우에 매우 유용합니다.

  • waitFor(url, callback): url로의 요청이 완료된 후 callback을 실행합니다 (성공 또는 실패 모두 포함).
  • waitForAll(callback): 보류 중인 모든 요청이 완료되면 callback을 실행합니다. 보류 중인 요청이 없는 경우, callback을 다음 틱에서 실행합니다.

두 함수는 요청이 완료된 다음 틱에서 (setImmediate()를 사용하여) callback을 실행하여 .then() 또는 .catch() 핸들러가 실행될 수 있도록 합니다.

shallowMountExtendedmountExtended

shallowMountExtendedmountExtended 유틸리티를 사용하면 사용 가능한 DOM Testing Library 쿼리 중 하나를 find 또는 findAll과 접두사로 사용하여 수행할 수 있습니다.

import { shallowMountExtended } from 'helpers/vue_test_utils_helper';

describe('FooComponent', () => {
  const wrapper = shallowMountExtended({
    template: `
      <div data-testid="gitlab-frontend-stack">
        <p>GitLab frontend stack</p>
        <div role="tablist">
          <button role="tab" aria-selected="true">Vue.js</button>
          <button role="tab" aria-selected="false">GraphQL</button>
          <button role="tab" aria-selected="false">SCSS</button>
        </div>
      </div>
    `,
  });

  it('`findByTestId`로 요소를 찾음', () => {
    expect(wrapper.findByTestId('gitlab-frontend-stack').exists()).toBe(true);
  });

  it('`findByText`로 요소를 찾음', () => {
    expect(wrapper.findByText('GitLab frontend stack').exists()).toBe(true);
    expect(wrapper.findByText('TypeScript').exists()).toBe(false);
  });

  it('`findAllByRole`로 요소를 찾음', () => {
    expect(wrapper.findAllByRole('tab').length).toBe(3);
  });
});

spec/frontend/alert_management/components/alert_details_spec.js에서 예제를 확인하세요.

오래된 브라우저에서 테스트

일부 회귀 사항은 특정 브라우저 버전에만 영향을 줍니다. 다음 단계를 사용하여 Firefox 또는 BrowserStack을 사용하여 특정 브라우저에 설치 및 테스트할 수 있습니다.

BrowserStack

BrowserStack을 사용하면 1200개 이상의 모바일 장치 및 브라우저를 테스트할 수 있습니다. 라이브 앱을 통해 직접 사용하거나 쉽게 액세스하기 위해 크롬 익스텐션을 설치할 수 있습니다. GitLab의 Engineering 보드의 자격 증명을 사용하여 공유 1Password 계정에 로그인하세요.

Firefox

macOS

릴리스 FTP 서버인 https://ftp.mozilla.org/pub/firefox/releases/에서 Firefox의 이전 버전을 다운로드할 수 있습니다.

  1. 웹 사이트에서 원하는 버전을 선택합니다. 이 경우 50.0.1을 선택합니다.
  2. mac 폴더로 이동합니다.
  3. 원하는 언어를 선택합니다. DMG 패키지가 내부에 있습니다. 다운로드합니다.
  4. 애플리케이션을 Applications 폴더 이외의 다른 폴더로 드래그앤드롭합니다.
  5. 애플리케이션의 이름을 Firefox_Old와 같이 변경합니다.
  6. 애플리케이션을 Applications 폴더로 이동합니다.
  7. 터미널을 열고 /Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager를 실행하여 해당 Firefox 버전에 특정한 새 프로필을 작성합니다.
  8. 프로필이 생성된 후 앱을 종료한 다음 일반적으로 다시 실행합니다. 이제 작동하는 이전 버전의 Firefox를 사용할 수 있습니다.

스냅샷

Jest 스냅샷 테스트는 특정 컴포넌트의 HTML 출력에 예상치 못한 변경을 방지하는 유용한 방법입니다. vue-tests-utils와 같은 다른 테스트 방법이 필요한 경우에만 사용해야 합니다. GitLab에서 사용하려면 몇 가지 지침을 강조해야 합니다.

  • 스냅샷을 코드로 다루기
  • 스냅샷 파일을 블랙 박스로 생각하지 말기
  • 스냅샷의 출력물을 주의 깊게 살피는 것이 중요합니다. 그렇지 않으면 실질적인 가치를 제공하지 못합니다. 일반적으로 생성된 스냅샷 파일을 일반 코드처럼 읽어들이는 것이 일반적입니다.

스냅샷 테스트를 간단히 말하면, 테스트 대상 항목에 입력한 원시적인 String 표현을 저장하는 방법입니다. 이를 통해 컴포넌트, 스토어, 복잡한 생성된 출력물 등의 변경 사항을 평가할 수 있습니다. 몇 가지 권장하는 할 일하지 말아야 할 일을 보다 자세히 알아보기 위해 아래 목록을 참조하세요. 스냅샷 테스트는 매우 강력한 도구일 수 있지만, 단위 테스트를 대체하는 것이 아니라 보완하기 위한 것입니다.

Jest에서는 스냅샷을 생성할 때 염두에 둘 최선의 접근법에 관한 훌륭한 문서를 제공합니다.

스냅샷 작동 방식

스냅샷은 함수 호출의 좌측에 테스트하고자 하는 것의 String 표현을 문자열화한 것뿐입니다. 따라서 문자열의 형식을 변경하는 어떠한 종류의 변경도 결과에 영향을 미칩니다. 이 프로세스는 자동 변환 단계를 위해 시리얼라이저를 활용하여 수행됩니다. Vue의 경우, 이러한 변환을 자동으로 처리하기 위해 vue-jest 패키지를 활용함으로써 이미 처리되었습니다.

스펙의 결과가 생성된 스냅샷 파일의 내용과 다르다면, 테스트 스위트 내에서 실패한 테스트에 대해 알림을 받게 될 것입니다.

Jest의 공식 문서 https://jestjs.io/docs/snapshot-testing에서 모든 세부 정보를 찾아보세요.

장단점

장점

  • 중요한 HTML 구조의 우발적인 변경에 대한 좋은 경고를 제공합니다.
  • 설정이 쉬움

단점

  • vue-tests-utils가 직접 요소를 찾아서 존재를 단언하는 방법과 같이 명확성이나 가드 레일이 부족합니다.
  • 의도적으로 구성 요소를 업데이트할 때 불필요한 잡음을 만듭니다.
  • 결함의 스냅샷을 캡처하여 테스트를 역방향으로 만드는 고위험
  • 스냅샷 안에 의미 있는 단언 또는 기대 설정이 없어서 이를 추론하거나 대체하기 어렵습니다.
  • GitLab UI와 같은 종속성과 함께 사용할 때, 테스트에 취약성을 만들고 있습니다. 기밑 라이브러리가 테스트 중인 컴포넌트의 HTML을 변경할 때

사용 시점

스냅샷을 사용할 때

  • 중요한 HTML 구조를 우발적 변경으로부터 보호하여 변하지 않도록 하는 경우
  • 복잡한 유틸리티 함수의 JS 객체 또는 JSON 출력을 단언할 때

스냅샷을 사용하지 말아야 할 때

  • vue-tests-utils를 사용하여 테스트를 작성할 수 있는 경우
  • 컴포넌트의 로직을 단언하는 경우
  • 데이터 구조의 출력을 예측하는 경우
  • 리파지터리 외부의 UI 요소가 있는 경우 (GitLab UI 버전 업데이트를 생각해보세요)

예시

스냅샷 테스트의 단점이 일반적으로 장점보다 더 큰 경우를 보면, 스냅샷 테스트를 사용하고자 하는 시점과 그 이유에 대한 몇 가지 예시를 설명하겠습니다.

예시 #1 - 요소의 가시성

요소의 가시성을 테스트할 때는 주어진 컴포넌트를 찾고 VTU (vue-tests-utils) 래퍼에서 기본 .exists() 메서드를 호출하는 방식을 사용하는 것이 좋습니다. 아래 예시를 보면, 스냅샷에 대한 단언은 원하는 결과를 알려주지 않습니다. 우리는 it 설명에만 의존하고 스냅샷이 원하는 동작을 캡처했다는 가정에 의존합니다.

<template>
  <my-component v-if="isVisible" />
</template>

나쁨:

it('컴포넌트를 숨김', () => {
  createComponent({ props: { isVisible: false }})

  expect(wrapper.element).toMatchSnapshot()
})

it('컴포넌트를 보여줌', () => {
  createComponent({ props: { isVisible: true }})

  expect(wrapper.element).toMatchSnapshot()
})

좋음:

it('컴포넌트를 숨김', () => {
  createComponent({ props: { isVisible: false }})

  expect(findMyComponent().exists()).toBe(false)
})

it('컴포넌트를 보여줌', () => {
  createComponent({ props: { isVisible: true }})

  expect(findMyComponent().exists()).toBe(true)
})

뿐만 아니라, 컴포넌트에 잘못된 prop을 전달하여 잘못된 가시성이 설정된 상황을 상상해보세요: 스냅샷 테스트는 여전히 통과할 것이고, 테스트가 망가졌다는 것을 스냅샷의 출력물을 두 번 확인하지 않는 이상 알 수 없을 것입니다.

예시 #2 - 텍스트의 존재

컴포넌트 내에서 텍스트를 찾는 것은 vue-test-utilswrapper.text() 메서드를 사용하면 매우 간단합니다. 그러나 형식이나 HTML 중첩으로 인해 반환된 값이 일관성 없는 여백을 가질 수 있는 경우, 스냅샷을 사용하여 공백을 무시하는 것이 유혹적일 수 있습니다.

이 경우 DOM 레이아웃이 변경될 경우 스냅샷 테스트는 실패하게 될 것이며 텍스트가 여전히 완벽하게 서식이 맞춰져 있더라도 실패하게 됩니다. 이것은 DOM 레이아웃에 대한 어떠한 변경이 실패할 수 있다는 뜻입니다. 여러 개의 단언을 수행하는 대신 스냅샷을 사용하여 여러 가지 문자열을 단언하는 것이 더 좋습니다.

<template>
  <gl-sprintf :message="my-message">
    <template #code="{ content }">
      <code>{{ content }}</code>
    </template>
  </gl-sprintf>
  <p> My second message </p>
</template>

나쁨:

it('원하는 대로 텍스트를 렌더링함', () => {
  expect(wrapper.text()).toMatchSnapshot()
})

좋음:

it('코드 스니펫을 렌더링함', () => {
  expect(findCodeTag().text()).toContain("myFunction()")
})

it('문단 텍스트를 렌더링함', () => {
  expect(findOtherText().text()).toBe("My second message")
})

예시 #3 - 복잡한 HTML

매우 복잡한 HTML이 있는 경우, 생각할 때 이를 전체로 캡처하는 대신에 특정한 민감하고 의미 있는 지점을 단언하는 데 중점을 두어야 합니다. 스냅샷 테스트의 가치는 개발자들에게 잘못해서는 안 되는 HTML 구조를 우연히 변경했을 수도 있다는 사실을 알려주는 것입니다. 변경 사항의 출력물이 읽기 어렵다면, 이것이 경고 신호 자체로써 충분한 것일까요? 그렇다면 이것을 스냅샷 없이 달성할 수 있을까요?

복잡한 HTML 출력의 좋은 예로는 GlTable이 있습니다. 스냅샷 테스트는 행과 열 구조를 캡처할 수 있기 때문에 유용해 보일 수 있지만, 우리는 예상한 텍스트를 단언하거나 행과 열의 수를 수동으로 계산하는 방식을 시도해야 합니다.

<template>
  <gl-table ...all-them-props />
</template>

나쁨:

it('원하는 대로 GlTable을 렌더링함', () => {
  expect(findGlTable().element).toMatchSnapshot()
})

좋음:

it('올바른 수의 행을 렌더링함', () => {
  expect(findGlTable().findAllRows()).toHaveLength(expectedLength)
})

it('보름달에만 나타나는 특별한 아이콘을 렌더링함', () => {
  expect(findGlTable().findMoonIcon().exists()).toBe(true)
})

it('올바른 이메일 형식을 렌더링함', () => {
  expect(findGlTable().text()).toContain('my_strange_email@shaddyprovide.com')
})

이제 더욱 상세해졌지만, 이제 우리의 테스트들은 GlTable이 내부적으로 구현을 변경해도 중단되지 않습니다. 우리는 리팩토링하거나 테이블을 추가할 때 보존해야 하는 중요한지 다른 개발자들 (또는 6개월 후의 우리 자신)에게 전달하게 됩니다.

스냅샷 찍는 방법

it('makes the name look pretty', () => {
  expect(prettifyName('Homer Simpson')).toMatchSnapshot()
})

이 테스트를 처음 실행하면 새로운 .snap 파일이 생성됩니다. 다음과 같은 형태일 것입니다:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`makes the name look pretty`] = `
Sir Homer Simpson the Third
`

이제 이 테스트를 호출할 때마다 새로운 스냅샷이 이전에 생성된 버전과 비교됩니다. 이는 스냅샷 파일의 내용을 이해하고 주의 깊게 취급해야 한다는 사실을 강조해야 합니다. 스냅샷은 출력물이 크거나 복잡해서 읽기 어려운 경우 가치를 잃어버립니다. 이는 스냅샷을 인간이 읽을 수 있는 항목으로 유지하거나 머지 요청 리뷰에서 평가할 수 있는 사항으로만 유지하는 것을 의미합니다. 동일한 것은 wrapperselements에도 동일하게 적용될 수 있습니다.

it('renders the component correctly', () => {
  expect(wrapper).toMatchSnapshot()
  expect(wrapper.element).toMatchSnapshot();
})

위의 테스트는 두 개의 스냅샷을 생성합니다. 코드베이스 안전성을 위해 어떤 스냅샷이 더 가치 있는지 결정하는 것이 중요합니다. 즉, 이러한 스냅샷 중 하나가 변경될 때 코드베이스에서 잠재적인 중단을 알릴 수 있는지를 파악할 수 있습니다. 이는 어떤 하위 의존성이 우리의 지식 없이 변경되었을 때 예기치 않은 변화를 감지하는 데 도움이 될 수 있습니다.

기능 테스트 시작하기

기능 테스트란

기능 테스트 또는 화이트박스 테스트라고도 하는 기능 테스트는 브라우저를 생성하고 Capybara 도우미를 가지고 있는 테스트입니다. 이는 다음과 같은 테스트를 할 수 있습니다:

  • 브라우저에서 요소를 찾음
  • 해당 요소를 클릭함
  • API를 호출함

기능 테스트 실행 비용이 많이든다는 것에 유의해야 합니다. 이러한 유형의 테스트를 실행하기 전에 정말로 이 유형의 테스트를 원하는지 확인해야 합니다.

우리의 모든 기능 테스트는 Ruby로 작성되지만, 종종 사용자 인터페이스 기능을 구현하기 때문에 JavaScript 엔지니어들이 작성하게 됩니다. 따라서 다음 섹션에서는 RubyCapybara에 대한 사전 지식이 없다고 가정하고, 언제 어떻게 이러한 테스트를 사용해야 하는지에 대한 명확한 지침을 제시합니다.

기능 테스트를 사용해야 하는 경우

테스트가 다음과 같은 경우 기능 테스트를 사용해야 합니다:

  • 여러 구성 요소를 포함합니다.
  • 사용자가 여러 페이지를 탐색해야 합니다.
  • 양식을 제출하고 다른 곳에서 결과를 관찰해야 합니다.
  • 단위 테스트로 수행된 경우 가짜 데이터 및 구성 요소의 많은 수와 모의 설정이 필요할 것입니다.

기능 테스트는 특히 다음을 테스트하려고 할 때 유용합니다:

  • 여러 구성 요소가 성공적으로 함께 작동하는지 확인
  • 복잡한 API 상호 작용

기능 테스트를 사용하지 말아야 하는 경우

비용이 많이 들기 때문에 동일한 테스트 결과를 얻을 수 있는 경우에는 기능 테스트 대신에 jestvue-test-utils 단위 테스트를 사용해야 합니다.

만약 다음에 해당하는 경우에는 단위 테스트를 사용해야 합니다:

  • 구현하려는 동작이 단일 구성 요소에 모두 속해 있을 때
  • 원하는 효과를 일으키기 위해 다른 구성 요소의 동작을 시뮬레이트할 수 있을 때
  • 가상 DOM에서 UI 요소를 이미 선택해 원하는 효과를 일으킬 수 있을 때

또한, 새 코드에서 여러 구성 요소의 동작이 필요한 경우, 구성 요소 트리가 높은 위치에서 동작을 테스트하는 것이 좋습니다. 예를 들어 ParentComponent라는 구성 요소가 있는 경우:

  <script>
  export default{
    name: ParentComponent,
    data(){
      return {
        internalData: 'oldValue'
      }
    },
     methods:{
      changeSomeInternalData(newVal){
        this.internalData = newVal
      }
     }
  }
  </script>
  <template>
   <div>
    <child-component-1 @child-event="changeSomeInternalData" />
    <child-component-2 :parent-data="internalData" />
   </div>
  </template>

이 예에서:

  • ChildComponent1이 이벤트를 전송함
  • ParentComponentinternalData 값을 변경함
  • ParentComponent가 props를 ChildComponent2에 전달함

다음과 같이 단위 테스트를 사용할 수 있습니다:

  • ParentComponent 단위 테스트 파일에서 childComponent1에서 예상된 이벤트를 발생시킴
  • 속성이 childComponent2로 전달되는지 확인

그런 다음 각 자식 구성 요소는 이벤트가 발생했을 때와 속성이 변경되었을 때 무엇이 발생하는지를 테스트합니다.

이 예는 더 큰 규모와 더 깊은 구성 요소 트리에서도 적용됩니다. 원하는 테스트 동작을 확신할 수 있는 경우, 비용이 많이 드는 기능 테스트보다는 단위 테스트를 사용하고/additional_cost/ 피하는 것이 분명히 가치 있습니다.

테스트를 작성할 위치

기능 테스트는 spec/features 폴더에 저장됩니다. 추가하려는 기능을 테스트 할 수 있는 기존 파일을 찾아야 합니다. 이 폴더 안에서 당신의 섹션을 찾을 수 있을 것입니다. 예를 들어, 만약 파이프라인 페이지에 대한 새로운 기능 테스트를 추가하려면 spec/features/projects/pipelines 에서 당신이 작성하려는 테스트 파일이 있는지 확인해야 합니다.

기능 테스트 실행 방법

  1. GDK 환경이 작동하는지 확인합니다.
  2. gdk start 명령어로 gdk 환경을 시작합니다.
  3. 터미널에서 다음을 실행합니다:

     bundle exec rspec path/to/file:line_of_my_test
    

    이 명령어 앞에 WEBDRIVER_HEADLESS=0을 붙일 수도 있는데, 이렇게 하면 컴퓨터의 실제 브라우저를 열어 볼 수 있기 때문에 디버깅에 매우 유용합니다.

    Firefox를 사용하려면 Chrome 대신 WEBDRIVER=firefox 명령어로 시작하면 됩니다.

테스트 작성 방법

기본 파일 구조

  1. 모든 문자열 리터럴을 변경할 수 없게 만듭니다.

    모든 기능 테스트에서 가장 첫 번째 줄은 다음과 같아야 합니다:

    # frozen_string_literal: true
    

    이는 모든 Ruby 파일에서 모든 문자열 리터럴을 변경할 수 없게 만듭니다. 이외에도 일부 성능상 이점이 있지만, 이것은 이 섹션의 범위를 벗어나는 부분입니다.

  2. 종속성을 가져옵니다.

    필요한 모듈을 가져와야 합니다. 대부분의 경우 spec_helper를 요구할 필요가 있습니다:

    require 'spec_helper'
    

    다른 관련 모듈을 가져옵니다.

  3. RSpec가 정의할 수 있도록 테스트를 위한 전역 범위를 생성합니다. 마치 jest의 초기 설명 블록처럼요.

그런 다음, 가장 처음의 RSpec 범위를 생성해야 합니다.

RSpec.describe 'Pipeline', :js do
  ...
end

다른 점은, Ruby의 모든 것처럼, 이 것이 실제로는 class이라는 것입니다. 즉, 맨 위에서 원하는 모듈을 include할 수 있습니다. 예를 들어, 보다 쉽게 탐색하기 위해 RoutesHelpers를 포함할 수 있습니다.

RSpec.describe 'Pipeline', :js do
  include RoutesHelpers
  ...
end

모든 구현이 완료된 후, 다음과 같이 보일 것입니다:

# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Pipeline', :js do
  include RoutesHelpers
end

데이터 씨딩

각 테스트는 고유의 환경에 있으므로 필요한 데이터를 씨드하기 위해 팩토리를 사용해야 합니다. 파이프라인 예제를 계속 진행하기로 하면, /namespace/project/-/pipelines/:id/ 경로에 있는 메인 파이프라인 페이지로 이동하는 테스트를 원한다고 가정해 봅시다.

대부분의 기능 테스트는 로그인해야 하기 때문에 사용자를 만들어야 합니다. 익명 사용자가 보는 기능을 명시적으로 테스트하는 경우를 제외하고는 이 단계를 건너뛸 수 있습니다. 사용자를 만들지 않아도 되는 경우를 제외하고는 특정 테스트를 수행하는 경우에는 항상 사용자를 만들어야 합니다. 이렇게 하면 테스트에서 필요한 권한 수준을 명시적으로 설정하여 섹션이 변경될 때 권한 수준을 변경하거나 새로운 권한 수준을 테스트할 수 있습니다. 사용자를 만드는 방법은 다음과 같습니다:

  let(:user) { create(:user) }

위 코드는 새로 만든 사용자를 보관하는 변수를 생성하며, spec_helper를 import 했기 때문에 create를 사용할 수 있습니다.

그러나 이 사용자에게 아직 아무 작업도 수행하지 않았기 때문에 단순히 변수일 뿐입니다. 따라서 테스트의 before do 블록에서 사용자로 로그인할 수 있도록 해야 합니다.

  let(:user) { create(:user) }

  before do
    sign_in(user)
  end

이제 사용자가 있으므로 파이프라인 페이지에서 어떤 것을 단언하기 전에 다른 필요한 요소를 살펴보아야 합니다. /namespace/project/-/pipelines/:id/ 경로를 살펴보면 프로젝트와 파이프라인이 필요하다는 것을 알 수 있습니다.

따라서 프로젝트와 파이프라인을 만들고 이들을 연결해야 합니다. 보통 팩토리에서는 자식 요소는 부모 요소를 인수로 필요로 합니다. 이 경우에는 파이프라인이 프로젝트의 자식이기 때문에 프로젝트를 먼저 만들고, 파이프라인을 생성할 때 프로젝트를 인수로 전달하여 파이프라인을 프로젝트에 “연결”할 수 있습니다. 또한 파이프라인은 사용자가 소유하기 때문에 사용자도 필요합니다. 예를 들어, 이렇게 프로젝트와 파이프라인을 생성할 수 있습니다:

  let(:user) { create(:user) }
  let(:project) { create(:project, :repository) }
  let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }

이와 유사하게, 부모 파이프라인을 전달하여 빌드 팩토리를 사용하여 작업(빌드)을 만들 수 있습니다:

  create(:ci_build, pipeline: pipeline, stage_idx: 10, stage: 'publish', name: 'CentOS')

원하는 것이 이미 존재하는 팩토리가 많으므로 필요한 것이 있는지 확인하는 것이 좋습니다.

네비게이션

visit 메서드를 사용하여 페이지로 이동할 수 있으며, 경로를 인수로 전달해야 합니다. Rails는 자동으로 헬퍼 경로(Helper Paths)를 생성하므로 하드코딩된 문자열 대신 이를 사용해야 합니다. 경로 모델을 사용하여 생성되므로 파이프라인으로 이동하려면:

  visit project_pipeline_path(project, pipeline)

UI를 통해 네비게이션 또는 비동기 호출을 수행하기 전에 wait_for_requests를 사용하여 추가 지시사항을 진행하기 전에 대기해야 합니다.

요소 상호작용

요소를 찾고 상호작용하는 다양한 방법이 있습니다. 최상의 관행에 대해서는 UI 테스트 섹션을 참조하십시오.

버튼을 클릭하려면 버튼 내에 있는 텍스트를 사용하여 click_button을 사용하십시오:

  click_button '버튼 요소 내부의 텍스트'

링크를 따라가려면 click_link를 사용하십시오:

  click_link '링크 태그 내부의 텍스트'

fill_in을 사용하여 입력/폼 요소에 값을 채울 수 있습니다. 첫 번째 인수는 셀렉터이고, 두 번째는 with:으로 전달되는 값을 나타냅니다.

  fill_in 'current_password', with: '123devops'

또는 send_keys와 함께 셀렉터를 사용하여 이전 텍스트를 제거하지 않고 필드에 키를 추가하거나, 입력 요소의 값을 완전히 대체하는 데 set을 사용할 수도 있습니다.

기능 테스트 액션 문서에서 더 많은 작업 목록을 찾을 수 있습니다.

단언

페이지의 어떤 것이든 단언하려면 실제로 페이지 문서를 자동으로 정의하고 있는 page 변수를 항상 사용할 수 있습니다. 이겻 은 셀렉터나 콘텐츠와 같은 특정 구성 요소가 있는지를 기대할 수 있음을 의미합니다. 몇 가지 예시는 다음과 같습니다.

  # 버튼 찾기
  expect(page).to have_button('리뷰 제출')
  # 텍스트로 찾기
  expect(page).to have_text('빌드')
  # `href` 값을 통해 찾기
  expect(page).to have_link(pipeline.ref)
  # data-testid로 찾기
  # 특정 매처가 없을 때는 CSS 셀렉터와 유사하게 사용할 수 있습니다.
  expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]')
  # CSS 셀렉터로 찾기. 이는 마지막 수단입니다.
  # 원하는 요소에 속성을 추가할 수 없을 때 사용합니다.
  expect(page).to have_css('.js-icon-retry')
  # 위의 각 셀렉터를 `not_to`로 결합할 수 있습니다.
  expect(page).not_to have_button('리뷰 제출')
  # 테스트 케이스가 연속적인 경우
  # 일련의 실패를 묶기 위해 `:aggregate_failures`를 사용하는 것이 좋습니다
  it '이슈 설명과 디자인 레퍼런스를 보여줍니다', :aggregate_failures do
    expect(page).to have_text('추억했어요')
    expect(page).to have_link(design_tab_ref)
    expect(page).to have_link(design_ref_a)
    expect(page).to have_link(design_ref_b)
  end

다음과 같이 하위 블록을 만들어 내용을 확인할 수도 있습니다.

  • 어디서 단언을 수행하는지 범위를 좁히고 의도치 않은 다른 요소를 찾을 위험을 줄입니다.
  • 올바른 범위 내에서 요소가 찾아지는지 확인합니다.
  page.within('[data-testid="pipeline-multi-actions-dropdown"]') do
    ...
  end

기능 테스트 매처 문서에서 더 많은 매처 목록을 찾을 수 있습니다.

특징 플래그

기본적으로 YAML 정의나 GDK에서 수동으로 설정한 플래그에 관계 없이 모든 특징 플래그가 활성화됩니다. 특징 플래그가 비활성화되었을 때 테스트하려면 일반적으로 before do 블록에서 플래그를 수동으로 스텁해야 합니다.

  stub_feature_flags(my_feature_flag: false)

ee 특징 플래그를 스텁하는 경우 다음을 사용하세요:

  stub_licensed_features(my_feature_flag: false)

브라우저 콘솔 오류 단언

기본적으로 특징 스펙은 브라우저 콘솔 오류를 찾아도 실패하지 않습니다. 때로는 통합 문제를 나타낼 수 있는 예상치 못한 콘솔 오류가 없는지 확인하고 싶기도 합니다.

특징 스펙이 브라우저 콘솔 오류를 만나면 실패하도록 설정하려면 BrowserConsoleHelpers 지원 모듈에서 expect_page_to_have_no_console_errors를 사용하세요:

RSpec.describe 'Pipeline', :js do
  after do
    expect_page_to_have_no_console_errors
  end

  # ...
end

참고: expect_page_to_have_no_console_errorsWEBDRIVER=firefox에서 작동하지 않습니다. Chrome 드라이버를 사용할 때만 로그가 캡처됩니다.

가끔은 무시하고 싶은 알려진 콘솔 오류가 있습니다. 메시지 세트를 무시하여 메시지가 관찰되어도 테스트가 실패하지 않도록 하려면 expect_page_to_have_no_console_errorsallow: 매개변수를 전달할 수 있습니다:

RSpec.describe 'Pipeline', :js do
  after do
    expect_page_to_have_no_console_errors(allow: [
      "Blow up!",
      /Foo.*happens/
    ])
  end

  # ...
end

spec/support/helpers/browser_console_helpers.rb에서 BROWSER_CONSOLE_ERROR_FILTER 상수를 업데이트하여 전역적으로 무시해야 하는 콘솔 오류 목록을 변경할 수 있습니다.

디버깅

WEBDRIVER_HEADLESS=0 접두사를 사용하여 특정 브라우저를 열고 스펙을 실행할 수 있습니다. 그러나 스펙은 명령을 빠르게 실행하고 주변을 둘러볼 시간을 주지 않습니다.

이 문제를 피하려면 Capybara가 실행을 중지하길 원하는 줄에 binding.pry를 작성할 수 있습니다. 그러면 표준적으로 브라우저 내부에 있게 됩니다. 특정 요소를 찾을 수 없는 이유를 이해하려면 다음을 할 수 있습니다:

  • 요소 선택.
  • 콘솔 및 네트워크 탭 사용.
  • 브라우저 콘솔에서 셀렉터 실행.

Capybara가 실행되는 터미널에서 next를 실행할 수도 있으며, 이는 테스트를 한 줄씩 실행합니다. 이렇게 하면 문제를 일으키는 요소를 확인하기 위해 각 상호작용을 하나씩 확인할 수 있습니다.

GDK에서 실행 시간 개선

Jest 테스트 스위트를 실행할 때 사용하는 worker 수는 기계의 사용 가능한 코어의 60%로 설정됩니다. 이로 인해 실행 시간은 빨라지지만 메모리 사용량은 늘어납니다. 이 작동 방식에 대한 자세한 벤치마킹 정보는 이슈를 참조하세요.

ChromeDriver 업데이트

Selenium 4.6부터 ChromeDriver는 selenium-webdriver 젬과 함께 제공되는 Selenium Manager에 의해 자동으로 관리될 수 있습니다. 이제 더 이상 수동으로 chromedriver를 동기화할 필요가 없습니다.


테스팅 문서로 돌아가기