๊ฒฌ๊ณ ํ๊ณ ์ ๋ขฐํ ์ ์๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๋จ์ ํ ์คํธ ๋ฐ ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ๊ธฐ์ ์ ์ด์ ์ ๋ง์ถ ์น ์ปดํฌ๋ํธ ํ ์คํธ ์ ๋ต ์ข ํฉ ๊ฐ์ด๋์ ๋๋ค.
์น ์ปดํฌ๋ํธ ํ ์คํ : ๋จ์ ํ ์คํธ vs. ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ
์น ์ปดํฌ๋ํธ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ ์บก์ํ๋ UI ์์๋ฅผ ๋ง๋๋ ํ์คํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ์ฌ ํ๋ฐํธ์๋ ๊ฐ๋ฐ์ ํ๋ช ์ ์ผ์ผ์ผฐ์ต๋๋ค. ์ต์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์น ์ปดํฌ๋ํธ๊ฐ ์ ์ ๋ ๋ณดํธํ๋จ์ ๋ฐ๋ผ, ์๊ฒฉํ ํ ์คํธ๋ฅผ ํตํด ํ์ง์ ๋ณด์ฅํ๋ ๊ฒ์ด ๋ฌด์๋ณด๋ค ์ค์ํฉ๋๋ค. ์ด ๊ธ์์๋ ์น ์ปดํฌ๋ํธ๋ฅผ ์ํ ๋ ๊ฐ์ง ํต์ฌ ํ ์คํธ ์ ๋ต์ธ ๋จ์ ํ ์คํธ์ ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ๋ฅผ ์ดํด๋ณด๊ณ , ๊ฐ๊ฐ์ ์ฅ๋จ์ ๊ณผ ์ด๋ฅผ ๊ฐ๋ฐ ์ํฌํ๋ก์ ํจ๊ณผ์ ์ผ๋ก ํตํฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ๋๋ค.
์น ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํด์ผ ํ๋ ์ด์
ํน์ ํ ์คํธ ๊ธฐ๋ฒ์ ์ดํด๋ณด๊ธฐ ์ ์ ์น ์ปดํฌ๋ํธ ํ ์คํธ๊ฐ ์ ํ์์ ์ธ์ง ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค:
- ์ ๋ขฐ์ฑ: ํ ์คํธ๋ ์น ์ปดํฌ๋ํธ๊ฐ ๋ค์ํ ๋ธ๋ผ์ฐ์ ์ ํ๊ฒฝ์์ ์์๋๋ก ์๋ํ๋๋ก ๋ณด์ฅํ์ฌ ์๊ธฐ์น ์์ ๋์๊ณผ ๋ฒ๊ทธ๋ฅผ ์ต์ํํฉ๋๋ค.
- ์ ์ง๋ณด์์ฑ: ์ ํ ์คํธ๋ ์ปดํฌ๋ํธ๋ ์ ์ง๋ณด์ ๋ฐ ๋ฆฌํฉํ ๋ง์ด ๋ ์ฌ์์ ธ ๋ณ๊ฒฝ ์ ํ๊ท(regression) ๋ฐ์ ์ํ์ ์ค์ ๋๋ค.
- ์ฌ์ฌ์ฉ์ฑ: ์ฒ ์ ํ ํ ์คํธ๋ ์ปดํฌ๋ํธ๊ฐ ์ง์ ์ผ๋ก ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๋ถ๋ถ์ด๋ ์ฌ๋ฌ ํ๋ก์ ํธ์ ๊ฑธ์ณ ์์ ์๊ฒ ํตํฉ๋ ์ ์์์ ๊ฒ์ฆํฉ๋๋ค.
- ๊ฐ๋ฐ ๋น์ฉ ์ ๊ฐ: ํ ์คํธ๋ฅผ ํตํด ๊ฐ๋ฐ ๊ณผ์ ์ด๊ธฐ์ ๋ฒ๊ทธ๋ฅผ ๋ฐ๊ฒฌํ๋ ๊ฒ์ด ๋์ค์ ํ๋ก๋์ ํ๊ฒฝ์์ ์์ ํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๋น์ฉ์ด ์ ๋ ดํฉ๋๋ค.
- ์ฌ์ฉ์ ๊ฒฝํ ํฅ์: ์น ์ปดํฌ๋ํธ์ ์์ ์ฑ๊ณผ ๊ธฐ๋ฅ์ฑ์ ๋ณด์ฅํจ์ผ๋ก์จ ๋ ๋ถ๋๋ฝ๊ณ ์ฆ๊ฑฐ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ๊ธฐ์ฌํฉ๋๋ค.
์น ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ
๋จ์ ํ ์คํธ๋ ์ฝ๋์ ๊ฐ๋ณ ๋จ์๋ฅผ ๊ฒฉ๋ฆฌํ์ฌ ํ ์คํธํ๋ ๋ฐ ์ค์ ์ ๋ก๋๋ค. ์น ์ปดํฌ๋ํธ์ ๋งฅ๋ฝ์์ '๋จ์'๋ ์ผ๋ฐ์ ์ผ๋ก ์ปดํฌ๋ํธ ํด๋์ค ๋ด์ ํน์ ๋ฉ์๋๋ ํจ์๋ฅผ ์๋ฏธํฉ๋๋ค. ๋จ์ ํ ์คํธ์ ๋ชฉํ๋ ๊ฐ ๋จ์๊ฐ ์ปดํฌ๋ํธ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๋ถ๋ถ๊ณผ ๋ ๋ฆฝ์ ์ผ๋ก ์๋๋ ์์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ํํ๋์ง ํ์ธํ๋ ๊ฒ์ ๋๋ค.
์น ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ์ ์ฅ์
- ์ธ๋ถํ๋ ํ ์คํธ: ๋จ์ ํ ์คํธ๋ ํ ์คํธ ํ๋ก์ธ์ค์ ๋ํ ์ธ๋ฐํ ์ ์ด๋ฅผ ์ ๊ณตํ์ฌ ์ปดํฌ๋ํธ ๊ธฐ๋ฅ์ ํน์ ์ธก๋ฉด์ ๊ฒฉ๋ฆฌํ๊ณ ํ ์คํธํ ์ ์๊ฒ ํด์ค๋๋ค.
- ๋น ๋ฅธ ์คํ ์๋: ๋จ์ ํ ์คํธ๋ ์ผ๋ฐ์ ์ผ๋ก ์คํ ์๋๊ฐ ๋งค์ฐ ๋นจ๋ผ ๊ฐ๋ฐ ์ค์ ์ ์ํ ํผ๋๋ฐฑ์ ๋ฐ์ ์ ์์ต๋๋ค.
- ์ฌ์ด ๋๋ฒ๊น : ๋จ์ ํ ์คํธ๊ฐ ์คํจํ๋ฉด ์๊ณ ๊ฒฉ๋ฆฌ๋ ์ฝ๋ ์กฐ๊ฐ๋ง ํ ์คํธํ๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ์ ์์ธ์ ํ์ ํ๊ธฐ๊ฐ ๋ณดํต ๊ฐ๋จํฉ๋๋ค.
- ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง: ๋จ์ ํ ์คํธ๋ ๋์ ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ๋ ๋ฐ ๋์์ด ๋์ด ์ปดํฌ๋ํธ ์ฝ๋์ ์๋น ๋ถ๋ถ์ด ํ ์คํธ๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
์น ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ์ ๊ณผ์
- ์๋ DOM์ ๋ณต์ก์ฑ: ์๋ DOM์ ์ปดํฌ๋ํธ์ ๋ด๋ถ ๊ตฌ์กฐ์ ์คํ์ผ์ ์บก์ํํ๊ธฐ ๋๋ฌธ์ ๋จ์ ํ ์คํธ์์ ์ํธ์์ฉํ๊ธฐ๊ฐ ๊น๋ค๋ก์ธ ์ ์์ต๋๋ค.
- ์์กด์ฑ ๋ชจ์(Mocking): ํ ์คํธ ๋์ ๋จ์๋ฅผ ๊ฒฉ๋ฆฌํ๊ธฐ ์ํด ์์กด์ฑ์ ๋ชจ์ ์ฒ๋ฆฌํด์ผ ํ ์ ์์ผ๋ฉฐ, ์ด๋ ํ ์คํธ์ ๋ณต์ก์ฑ์ ๋ํ ์ ์์ต๋๋ค.
- ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ์ง์ค: ์ง๋์น๊ฒ ๊ตฌ์ฒด์ ์ธ ๋จ์ ํ ์คํธ๋ ์ปดํฌ๋ํธ์ ๋ด๋ถ ๊ตฌํ์ ๋ฆฌํฉํ ๋งํ ๋ ๊นจ์ง๊ธฐ ์ฝ๊ณ ์ทจ์ฝํ ์ ์์ต๋๋ค.
์น ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ๋ฅผ ์ํ ๋๊ตฌ ๋ฐ ํ๋ ์์ํฌ
๋ช ๊ฐ์ง ์ธ๊ธฐ ์๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ ์คํธ ํ๋ ์์ํฌ๋ฅผ ์น ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
- Jest: Facebook์์ ๊ฐ๋ฐํ ๋๋ฆฌ ์ฌ์ฉ๋๋ ํ ์คํธ ํ๋ ์์ํฌ๋ก, ๋จ์์ฑ, ์๋, ๋ด์ฅ๋ ๋ชจ์ ๊ธฐ๋ฅ์ผ๋ก ์ ๋ช ํฉ๋๋ค.
- Mocha: ๋จ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: Chai, Assert)์ ๋ชจ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: Sinon)๋ฅผ ์ง์ ์ ํํ ์ ์๋ ์ ์ฐํ ํ ์คํธ ํ๋ ์์ํฌ์ ๋๋ค.
- Jasmine: ๊นจ๋ํ๊ณ ๋ฐฐ์ฐ๊ธฐ ์ฌ์ด ๋ฌธ๋ฒ์ ๊ฐ์ง ๋ ๋ค๋ฅธ ์ธ๊ธฐ ์๋ ํ ์คํธ ํ๋ ์์ํฌ์ ๋๋ค.
Jest๋ฅผ ์ฌ์ฉํ ์น ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ ์์
์นด์ดํฐ๋ฅผ ํ์ํ๊ณ ์ฌ์ฉ์๊ฐ ์ฆ๊ฐ์ํฌ ์ ์๋๋ก ํ๋ <my-counter>
๋ผ๋ ๊ฐ๋จํ ์น ์ปดํฌ๋ํธ๋ฅผ ์๋ก ๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
my-counter.js
class MyCounter extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this._count = 0;
this.render();
}
increment() {
this._count++;
this.render();
}
render() {
this.shadow.innerHTML = `
<p>Count: ${this._count}</p>
<button id="incrementBtn">Increment</button>
`;
this.shadow.getElementById('incrementBtn').addEventListener('click', () => this.increment());
}
}
customElements.define('my-counter', MyCounter);
my-counter.test.js (Jest)
import './my-counter.js';
describe('MyCounter', () => {
let element;
beforeEach(() => {
element = document.createElement('my-counter');
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
it('should increment the count when the button is clicked', () => {
const incrementBtn = element.shadowRoot.getElementById('incrementBtn');
incrementBtn.click();
expect(element.shadowRoot.querySelector('p').textContent).toBe('Count: 1');
});
it('should initialize the count to 0', () => {
expect(element.shadowRoot.querySelector('p').textContent).toBe('Count: 0');
});
});
์ด ์์ ๋ Jest๋ฅผ ์ฌ์ฉํ์ฌ <my-counter>
์ปดํฌ๋ํธ์ increment
๋ฉ์๋์ ์ด๊ธฐ ์นด์ดํธ ๊ฐ์ ํ
์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. `shadowRoot`๋ฅผ ์ฌ์ฉํ์ฌ ์๋ DOM ๋ด์ ์์์ ์ ๊ทผํ๋ ๊ฒ์ ๊ฐ์กฐํฉ๋๋ค.
์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ
์ปดํฌ๋ํธ ํ ์คํธ ๋๋ ์๊ฐ์ ํ ์คํธ๋ผ๊ณ ๋ ์๋ ค์ง ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ ์ผ๋ฐ์ ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋จธ์ง ๋ถ๋ถ๊ณผ ๊ฒฉ๋ฆฌ๋, ๋ณด๋ค ํ์ค์ ์ธ ํ๊ฒฝ์์ ์น ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ๋ ๋ฐ ์ค์ ์ ๋ก๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ์ฃผ๋ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณต์ก์ฑ์ ์ํฅ์ ๋ฐ์ง ์๊ณ ์ปดํฌ๋ํธ์ ๋์, ๋ชจ์, ์ฌ์ฉ์ ์ํธ ์์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.
์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ์ ์ฅ์
- ํ์ค์ ์ธ ํ ์คํธ ํ๊ฒฝ: ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ ๋จ์ ํ ์คํธ์ ๋นํด ๋ ํ์ค์ ์ธ ํ ์คํธ ํ๊ฒฝ์ ์ ๊ณตํ์ฌ, ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ๋ ๋ฐฉ์๊ณผ ๋ ์ ์ฌํ ๋งฅ๋ฝ์์ ์ปดํฌ๋ํธ์ ๋์์ ํ ์คํธํ ์ ์์ต๋๋ค.
- ์๊ฐ์ ํ๊ท ํ ์คํธ: ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ ์๊ฐ์ ํ๊ท ํ ์คํธ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฌ๋ฌ ๋น๋ ๊ฐ์ ์ปดํฌ๋ํธ์ ์คํฌ๋ฆฐ์ท์ ๋น๊ตํ์ฌ ์๋ํ์ง ์์ ์๊ฐ์ ๋ณํ๋ฅผ ๊ฐ์งํ ์ ์์ต๋๋ค.
- ํ์ ๊ฐ์ : ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ๋๊ตฌ๋ ์ข ์ข ๊ฐ๋ฐ์, ๋์์ด๋, ์ดํด๊ด๊ณ์๊ฐ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ฒ ๊ฒํ ํ๊ณ ํผ๋๋ฐฑ์ ์ ๊ณตํ ์ ์๋ ์๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ ๊ทผ์ฑ ํ ์คํธ: ๊ฒฉ๋ฆฌ๋ ์ปดํฌ๋ํธ์์ ์ ๊ทผ์ฑ ํ ์คํธ๋ฅผ ์ํํ๊ธฐ๊ฐ ๋ ์ฌ์ ์ ๊ทผ์ฑ ํ์ค์ ์ถฉ์กฑํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ์ ๊ณผ์
- ๋๋ฆฐ ์คํ ์๋: ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์ ๋ ๋๋งํด์ผ ํ๋ฏ๋ก ๋จ์ ํ ์คํธ๋ณด๋ค ์คํ ์๋๊ฐ ๋๋ฆด ์ ์์ต๋๋ค.
- ๋ ๋ณต์กํ ์ค์ : ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ ํ๊ฒฝ์ ์ค์ ํ๋ ๊ฒ์ด ๋จ์ ํ ์คํธ ํ๊ฒฝ์ ์ค์ ํ๋ ๊ฒ๋ณด๋ค ๋ ๋ณต์กํ ์ ์์ต๋๋ค.
- ํ๋ ์ดํค(Flaky) ํ ์คํธ ๊ฐ๋ฅ์ฑ: ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ ๋คํธ์ํฌ ์ง์ฐ ๋ฐ ๋ธ๋ผ์ฐ์ ๋ถ์ผ์น์ ๊ฐ์ ์์ธ์ผ๋ก ์ธํด ๋ถ์์ ํด์ง(flaky) ๊ฐ๋ฅ์ฑ์ด ๋ ๋์ต๋๋ค.
์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ฅผ ์ํ ๋๊ตฌ ๋ฐ ํ๋ ์์ํฌ
์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ฅผ ์ํด ์ฌ์ฉํ ์ ์๋ ์ฌ๋ฌ ๋๊ตฌ์ ํ๋ ์์ํฌ๊ฐ ์์ต๋๋ค:
- Storybook: ๊ฒฉ๋ฆฌ๋ ํ๊ฒฝ์์ UI ์ปดํฌ๋ํธ๋ฅผ ๊ฐ๋ฐํ๊ณ ํ ์คํธํ๊ธฐ ์ํ ์ธ๊ธฐ ์๋ ์คํ ์์ค ๋๊ตฌ์ ๋๋ค. Storybook์ ์ปดํฌ๋ํธ๋ฅผ ํ์ํ๊ณ , ์ํธ์์ฉํ๋ฉฐ, ๋ฌธ์๋ฅผ ๋ณผ ์ ์๋ ์๊ฐ์ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
- Cypress: ์ปดํฌ๋ํธ ํ ์คํธ์๋ ์ฌ์ฉํ ์ ์๋ ์๋ํฌ์๋(end-to-end) ํ ์คํธ ํ๋ ์์ํฌ์ ๋๋ค. Cypress๋ ์ปดํฌ๋ํธ์ ์ํธ์์ฉํ๊ณ ๋์์ ๋จ์ธํ๊ธฐ ์ํ ๊ฐ๋ ฅํ API๋ฅผ ์ ๊ณตํฉ๋๋ค.
- Chromatic: Storybook๊ณผ ํตํฉ๋์ด ์๊ฐ์ ํ๊ท ํ ์คํธ ๋ฐ ํ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์๊ฐ์ ํ ์คํธ ํ๋ซํผ์ ๋๋ค.
- Bit: ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌ์ถ, ๋ฌธ์ํ ๋ฐ ๊ตฌ์ฑํ๊ธฐ ์ํ ์ปดํฌ๋ํธ ํ๋ซํผ์ ๋๋ค.
Storybook์ ์ฌ์ฉํ ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ ์์
๋จ์ ํ
์คํธ ์์ ์์ ์ฌ์ฉํ ๋์ผํ <my-counter>
์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ Storybook์ผ๋ก ํ
์คํธํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
.storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions'
],
framework: '@storybook/web-components',
core: {
builder: '@storybook/builder-webpack5'
},
};
src/my-counter.stories.js
import './my-counter.js';
export default {
title: 'MyCounter',
component: 'my-counter',
};
const Template = () => '<my-counter></my-counter>';
export const Default = Template.bind({});
์ด ์์ ๋ <my-counter>
์ปดํฌ๋ํธ์ ๋ํ Storybook ์คํ ๋ฆฌ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ๊ทธ๋ฐ ๋ค์ Storybook์ ๋ํํ ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ๋ฅผ ์๋์ผ๋ก ํ
์คํธํ๊ฑฐ๋ Chromatic๊ณผ ๊ฐ์ ์๊ฐ์ ํ
์คํธ ๋๊ตฌ์ ํตํฉํ ์ ์์ต๋๋ค.
์ฌ๋ฐ๋ฅธ ํ ์คํธ ์ ๋ต ์ ํํ๊ธฐ
๋จ์ ํ ์คํธ์ ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ ์ํธ ๋ฐฐํ์ ์ธ ๊ฒ์ด ์๋๋ผ, ์คํ๋ ค ์๋ก๋ฅผ ๋ณด์ํ๋ฉฐ ์น ์ปดํฌ๋ํธ์ ๋ํ ํฌ๊ด์ ์ธ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ํจ๊ป ์ฌ์ฉ๋์ด์ผ ํฉ๋๋ค.
๋จ์ ํ ์คํธ๋ฅผ ์ฌ์ฉํด์ผ ํ ๋:
- ์ปดํฌ๋ํธ ํด๋์ค ๋ด์ ๊ฐ๋ณ ๋ฉ์๋๋ ํจ์๋ฅผ ํ ์คํธํ ๋.
- ์ปดํฌ๋ํธ์ ๋ด๋ถ ๋ก์ง๊ณผ ๊ณ์ฐ์ ๊ฒ์ฆํ ๋.
- ๊ฐ๋ฐ ์ค ๋น ๋ฅธ ํผ๋๋ฐฑ์ด ํ์ํ ๋.
- ๋์ ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ๊ณ ์ถ์ ๋.
์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ๋ฅผ ์ฌ์ฉํด์ผ ํ ๋:
- ํ์ค์ ์ธ ํ๊ฒฝ์์ ์ปดํฌ๋ํธ์ ๋์๊ณผ ์ธ๊ด์ ํ ์คํธํ ๋.
- ์๊ฐ์ ํ๊ท ํ ์คํธ๋ฅผ ์ํํ ๋.
- ๊ฐ๋ฐ์, ๋์์ด๋, ์ดํด๊ด๊ณ์ ๊ฐ์ ํ์ ์ ๊ฐ์ ํ ๋.
- ์ ๊ทผ์ฑ ํ ์คํธ๋ฅผ ์ํํ ๋.
์น ์ปดํฌ๋ํธ ํ ์คํธ๋ฅผ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
๋ค์์ ์น ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ ๋ ๋ฐ๋ผ์ผ ํ ๋ช ๊ฐ์ง ๋ชจ๋ฒ ์ฌ๋ก์ ๋๋ค:
- ํ ์คํธ๋ฅผ ์ผ์ฐ, ๊ทธ๋ฆฌ๊ณ ์์ฃผ ์์ฑํ์ธ์: ํ๋ก์ ํธ ์์๋ถํฐ ๊ฐ๋ฐ ์ํฌํ๋ก์ ํ ์คํธ๋ฅผ ํตํฉํ์ธ์. ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD) ๋๋ ํ๋ ์ฃผ๋ ๊ฐ๋ฐ(BDD) ์ ๊ทผ ๋ฐฉ์์ ๊ณ ๋ คํ์ธ์.
- ์ปดํฌ๋ํธ์ ๋ชจ๋ ์ธก๋ฉด์ ํ ์คํธํ์ธ์: ์ปดํฌ๋ํธ์ ๊ธฐ๋ฅ, ์ธ๊ด, ์ ๊ทผ์ฑ ๋ฐ ์ฌ์ฉ์ ์ํธ ์์ฉ์ ํ ์คํธํ์ธ์.
- ๋ช ํํ๊ณ ๊ฐ๊ฒฐํ ํ ์คํธ ์ด๋ฆ์ ์ฌ์ฉํ์ธ์: ๊ฐ ํ ์คํธ๊ฐ ๋ฌด์์ ๊ฒ์ฆํ๋์ง ๋ช ํํ๊ฒ ๋ํ๋ด๋ ์์ ์ ์ธ ํ ์คํธ ์ด๋ฆ์ ์ฌ์ฉํ์ธ์.
- ํ ์คํธ๋ฅผ ๊ฒฉ๋ฆฌ๋ ์ํ๋ก ์ ์งํ์ธ์: ๊ฐ ํ ์คํธ๊ฐ ๋ค๋ฅธ ํ ์คํธ์ ๋ ๋ฆฝ์ ์ด๋ฉฐ ์ธ๋ถ ์ํ์ ์์กดํ์ง ์๋๋ก ํ์ธ์.
- ๋ชจ์(Mocking)๋ฅผ ์ ์คํ๊ฒ ์ฌ์ฉํ์ธ์: ํ ์คํธ ๋์ ๋จ์๋ฅผ ๊ฒฉ๋ฆฌํ๊ธฐ ์ํด ํ์ํ ๊ฒฝ์ฐ์๋ง ์์กด์ฑ์ ๋ชจ์ ์ฒ๋ฆฌํ์ธ์.
- ํ ์คํธ๋ฅผ ์๋ํํ์ธ์: ํ ์คํธ๋ฅผ ์ง์์ ํตํฉ(CI) ํ์ดํ๋ผ์ธ์ ํตํฉํ์ฌ ๋ชจ๋ ์ปค๋ฐ๋ง๋ค ์๋์ผ๋ก ์คํ๋๋๋ก ํ์ธ์.
- ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ๊ฒํ ํ์ธ์: ์ ๊ธฐ์ ์ผ๋ก ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๊ฒํ ํ์ฌ ์คํจํ ํ ์คํธ๋ฅผ ์๋ณํ๊ณ ์์ ํ์ธ์.
- ํ ์คํธ๋ฅผ ๋ฌธ์ํํ์ธ์: ํ ์คํธ์ ๋ชฉ์ ๊ณผ ์๋ ๋ฐฉ์์ ์ค๋ช ํ๊ธฐ ์ํด ํ ์คํธ๋ฅผ ๋ฌธ์ํํ์ธ์.
- ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํ ์คํธ๋ฅผ ๊ณ ๋ คํ์ธ์: ํธํ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ๋ค์ํ ๋ธ๋ผ์ฐ์ (Chrome, Firefox, Safari, Edge)์์ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ์ธ์. BrowserStack ๋ฐ Sauce Labs์ ๊ฐ์ ์๋น์ค๊ฐ ์ด๋ฅผ ์ง์ํ ์ ์์ต๋๋ค.
- ์ ๊ทผ์ฑ ํ ์คํธ: axe-core์ ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ ํ ์คํธ ์ ๋ต์ ์ผ๋ถ๋ก ์๋ํ๋ ์ ๊ทผ์ฑ ํ ์คํธ๋ฅผ ๊ตฌํํ์ธ์.
์์ : ๊ตญ์ ํ(i18n) ์น ์ปดํฌ๋ํธ ๊ตฌํ ๋ฐ ํ ์คํธ
๊ตญ์ ํ๋ฅผ ์ฒ๋ฆฌํ๋ ์น ์ปดํฌ๋ํธ๋ฅผ ๊ณ ๋ คํด ๋ณด๊ฒ ์ต๋๋ค. ์ด๋ ๊ธ๋ก๋ฒ ์ฌ์ฉ์๋ฅผ ๋์์ผ๋ก ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋งค์ฐ ์ค์ํฉ๋๋ค.
i18n-component.js
class I18nComponent extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.language = 'en'; // Default language
this.translations = {
en: {
greeting: 'Hello, world!',
buttonText: 'Click me',
},
fr: {
greeting: 'Bonjour le monde !',
buttonText: 'Cliquez ici',
},
es: {
greeting: 'ยกHola Mundo!',
buttonText: 'Haz clic aquรญ',
},
};
this.render();
}
setLanguage(lang) {
this.language = lang;
this.render();
}
render() {
const translation = this.translations[this.language] || this.translations['en']; // Fallback to English
this.shadow.innerHTML = `
<p>${translation.greeting}</p>
<button>${translation.buttonText}</button>
`;
}
}
customElements.define('i18n-component', I18nComponent);
i18n-component.test.js (Jest)
import './i18n-component.js';
describe('I18nComponent', () => {
let element;
beforeEach(() => {
element = document.createElement('i18n-component');
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
it('should display the English greeting by default', () => {
expect(element.shadowRoot.querySelector('p').textContent).toBe('Hello, world!');
});
it('should display the French greeting when the language is set to fr', () => {
element.setLanguage('fr');
expect(element.shadowRoot.querySelector('p').textContent).toBe('Bonjour le monde !');
});
it('should display the Spanish greeting when the language is set to es', () => {
element.setLanguage('es');
expect(element.shadowRoot.querySelector('p').textContent).toBe('ยกHola Mundo!');
});
it('should fallback to English if the language is not supported', () => {
element.setLanguage('de'); // German is not supported
expect(element.shadowRoot.querySelector('p').textContent).toBe('Hello, world!');
});
});
์ด ์์ ๋ ๊ตญ์ ํ ์ปดํฌ๋ํธ๋ฅผ ๋จ์ ํ ์คํธํ์ฌ ์ ํ๋ ์ธ์ด์ ๋ฐ๋ผ ์ฌ๋ฐ๋ฅธ ํ ์คํธ๋ฅผ ํ์ํ๊ณ , ํ์ํ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ์ธ์ด๋ก ๋์ฒด๋๋์ง ํ์ธํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ์ด ์ปดํฌ๋ํธ๋ ์น ๊ฐ๋ฐ์์ ๊ธ๋ก๋ฒ ์ฌ์ฉ์๋ฅผ ๊ณ ๋ คํ๋ ๊ฒ์ ์ค์์ฑ์ ๋ณด์ฌ์ค๋๋ค.
์น ์ปดํฌ๋ํธ์ ์ ๊ทผ์ฑ ํ ์คํธ
์น ์ปดํฌ๋ํธ๊ฐ ์ฅ์ ๋ฅผ ๊ฐ์ง ์ฌ์ฉ์์๊ฒ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ๋ณด์ฅํ๋ ๊ฒ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ ๊ทผ์ฑ ํ ์คํธ๋ ํ ์คํธ ์ํฌํ๋ก์ ํตํฉ๋์ด์ผ ํฉ๋๋ค.
์ ๊ทผ์ฑ ํ ์คํธ ๋๊ตฌ:
- axe-core: ์คํ ์์ค ์ ๊ทผ์ฑ ํ ์คํธ ์์ง์ ๋๋ค.
- Lighthouse: ์ ๊ทผ์ฑ์ ํฌํจํ์ฌ ์น ํ์ด์ง๋ฅผ ๊ฐ์ฌํ๊ธฐ ์ํ Google Chrome ํ์ฅ ํ๋ก๊ทธ๋จ ๋ฐ Node.js ๋ชจ๋์ ๋๋ค.
์์ : axe-core์ Jest๋ฅผ ์ฌ์ฉํ ์ ๊ทผ์ฑ ํ ์คํธ
import { axe, toHaveNoViolations } from 'jest-axe';
import './my-component.js';
expect.extend(toHaveNoViolations);
describe('MyComponent Accessibility', () => {
let element;
beforeEach(async () => {
element = document.createElement('my-component');
document.body.appendChild(element);
await element.updateComplete; // Wait for the component to render
});
afterEach(() => {
document.body.removeChild(element);
});
it('should pass accessibility checks', async () => {
const results = await axe(element.shadowRoot);
expect(results).toHaveNoViolations();
});
});
์ด ์์ ๋ axe-core๋ฅผ Jest์ ํจ๊ป ์ฌ์ฉํ์ฌ ์น ์ปดํฌ๋ํธ์ ๋ํ ์๋ํ๋ ์ ๊ทผ์ฑ ํ ์คํธ๋ฅผ ์ํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. `toHaveNoViolations`๋ ์ปดํฌ๋ํธ์ ์ ๊ทผ์ฑ ์๋ฐ ์ฌํญ์ด ์์์ ๋จ์ธํ๋ ์ฌ์ฉ์ ์ ์ Jest ๋งค์ฒ์ ๋๋ค. ์ด๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํฌ์ฉ์ฑ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค.
๊ฒฐ๋ก
์น ์ปดํฌ๋ํธ ํ ์คํธ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ๋ฉฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ UI ์์๋ฅผ ๊ตฌ์ถํ๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋จ์ ํ ์คํธ์ ์ปดํฌ๋ํธ ๊ฒฉ๋ฆฌ ํ ์คํธ ๋ชจ๋ ์ปดํฌ๋ํธ์ ํ์ง์ ๋ณด์ฅํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ์ด๋ฌํ ์ ๋ต์ ๊ฒฐํฉํ๊ณ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด, ์ ๋ขฐํ ์ ์๊ณ ์ ๊ทผ ๊ฐ๋ฅํ๋ฉฐ ๊ธ๋ก๋ฒ ์ฌ์ฉ์์๊ฒ ํ๋ฅญํ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ ์น ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์ปดํฌ๋ํธ๊ฐ ํฌ์ฉ์ ์ด๊ณ ๋ ๋์ ์ฌ์ฉ์์ธต์ ๋๋ฌํ ์ ์๋๋ก ํ ์คํธ ๊ณผ์ ์์ ๊ตญ์ ํ ๋ฐ ์ ๊ทผ์ฑ ์ธก๋ฉด์ ๊ณ ๋ คํ๋ ๊ฒ์ ์์ง ๋ง์ธ์.