TDD 방법론
TDD(Test-driven Development)는 코드를 작성하기 전에 테스트를 쓰는 소프트웨어 개발 방법론이다. 다시 말해, 개발자 자신이 바람직하다고 생각하는 코드의 결과를 미리 정의하고, 이것을 바탕으로 코드를 작성하는 법이다. TDD를 통해 소프트웨어를 개발한다는 것은 작은 단위의 테스트 케이스를 작성하고, 이를 통과하는 코드를 작성하는 과정을 반복하는 것을 의미한다.
TDD의 개발 주기를 그림으로 나타내면 아래와 같이 총 3단계로 이루어진다.
- Write Failing Test: 실패하는 테스트 코드를 먼저 작성한다.
- Make Test Pass: 테스트 코드를 성공시키기 위한 실제 코드를 작성한다.
- Refactor: 중복 코드 제거, 일반화 등의 리팩토링을 수행한다.
이 과정에서 1의 과정을 마치기 전에 2의 작업을 시작하지 않도록 주의해야 한다. 또한 2를 진행할 때에는, 1의 테스트를 통과할 정도의 최소 코드만 작성해야 한다. 테스트를 먼저 작성하는 것은 필연적으로 코드를 어떻게 구성할지 고민하게 된다는 것을 의미하고, 결과적으로 버그가 더 적은 코드를 짤 수 있게 된다. 또, 불필요한 설계를 피할 수 있고, 테스트 코드의 요구 사항에 집중할 수 있게 된다. 일반적으로 TDD를 따라 소프트웨어를 개발할 경우 그렇지 않은 경우보다 결함을 50 ~ 90% 까지 감소시킬 수 있다.
이처럼 버그가 적은, 보다 효과적인 코드를 짤 수 있는 방법임에도 불구하고, 실제로 완전한 TDD를 따르는 개발자는 의외로 많지 않다. 그 이유는 대부분의 개발자들이 생각하고 일하는 방식과 일치하지 않기 때문이다. 대부분의 개발자는 테스트를 작성하는 것보다, 만들어야 하는 것을 바로 코드로 작성하는 방식이 훨씬 자연스럽고 빠르다고 느낄 것이다. 많은 개발자들에게 왜 TDD를 따르지 않는지 물어보면, 대부분 ‘속도' 때문이라고 대답할 것이다.
TDD를 사용하는 이유
그럼에도 불구하고 TDD를 사용하는 이유는 무엇일까? 코드를 작성하기에 앞서 테스트 코드를 먼저 작성해야 하기 때문에 시간이 오래 걸리는 것처럼 느껴지지만, 오히려 예상하지 못했던 버그를 줄여 소요 시간을 줄일 수 있기 때문이다. 개발 과정에서 코드는 다양한 조건에 의해 계속해서 삽입, 수정, 삭제된다. 이 과정에서 코드가 중복되거나 불필요한 코드가 남게 된다. 그리고 그로 인해 여러 가지 버그가 발생하거나, 디버깅 또한 어려워지는 현상이 발생하기도 한다. 결국 그런 코드를 유지보수하기 위해서는 처음 개발할 때 아꼈던 리소스보다 더 많은 리소스를 투입해야 하는 경우가 발생한다.
테스트 코드를 작성하는 방법
console.log를 통해 확인하는 것도 일종의 테스트이다.
또, JavaScript Koans 과제를 진행하면서 테스트를 작성해 보기도 했다. 그 과정에서 describe, it, assert, expect 등의 다양한 키워드들을 마주쳤다. 이 키워드들은 JavaScript 내장 기능이 아니라 테스트 프레임워크에서 제공하는 테스트 작성을 위한 도구이다.
여러 개발자들이 더 나은 테스트를 작성하기 위해 많은 테스트 오픈소스 프레임워크를 제작했다. 이후 진행할 Test Builder 과제에서는 'mocha'라는 테스트 프레임워크와 'chai'라는 라이브러리를 사용한다.
과제 : Test builder
detectNetwork.js
function detectNetwork(cardNumber) {
/**
* 주의사항: 'cardNumber'는 항상 문자열입니다.
* 'Diner's Club' => prefix 38 or 39 length = 14 자리
* 'American Express' => prefix 34 or 37 length = 15 자리
* 이 글을 읽으셨다면, detectNetwork함수가 'Diner's Club', 'American Express'를
* 정확히 검사할 수 있도록 만들고 브라우저 console 화면으로 돌아가세요.
*/
const prefix = cardNumber.substr(0, 2);
const fourPrefix = cardNumber.substr(0, 4);
const threePrefix = cardNumber.substr(0, 3);
if (
(prefix === "38" && cardNumber.length === 14) ||
(prefix === "39" && cardNumber.length === 14)
)
return "Diner's Club";
if (
(prefix === "34" && cardNumber.length === 15) ||
(prefix === "37" && cardNumber.length === 15)
)
return "American Express";
if (
(prefix[0] === "4" && cardNumber.length === 13) ||
(prefix[0] === "4" && cardNumber.length === 16) ||
(prefix[0] === "4" && cardNumber.length === 19)
)
return "Visa";
if (
(prefix === "51" && cardNumber.length === 16) ||
(prefix === "52" && cardNumber.length === 16) ||
(prefix === "53" && cardNumber.length === 16) ||
(prefix === "54" && cardNumber.length === 16) ||
(prefix === "55" && cardNumber.length === 16)
)
return "MasterCard";
if (
(fourPrefix === "6011" && cardNumber.length === 16) ||
(fourPrefix === "6011" && cardNumber.length === 19)
)
return "Discover";
if (
(prefix === "65" && cardNumber.length === 16) ||
(prefix === "65" && cardNumber.length === 19)
)
return "Discover";
if (
("644" <= threePrefix <= "649" && cardNumber.length === 16) ||
("644" <= threePrefix <= "649" && cardNumber.length === 19)
)
return "Discover";
}
detectNetwork.test.js
describe("Introduction to Mocha Tests - READ ME FIRST", function() {
// Mocha 테스트는 그저 다음 기능을 하는 도구입니다!
// - 함수를 실행할 때 오류가 발생하면, 실패합니다.
// - 오류가 발생하지 않으면, 실패하지 않습니다.
// mocha에 대해 더 알고 싶다면 다음을 참고하세요. mochajs.org
// 먼저 아래의 테스트를 수정해 테스트가 정상적으로 작동하도록 해주세요.
// 그리고 Diner's club과 American Express 테스트로 넘어가주세요
it("오류가 발생하면 테스트가 실패합니다.", function() {
// throw new Error("저를 지워주세요!");
});
it("오류가 발생하지 않으므로, 실패하지 않습니다.", function() {
// 이 테스트는 실제로 아무것도 테스트하지 않습니다. 그러므로 그냥 여기는 통과하게 됩니다.
let even = function(num) {
return num % 2 === 0;
};
return even(10) === true;
});
// 우리는 테스트에서 예상 동작과 실제 동작을 비교하기를 원할 것입니다.
// 예상 동작이 실제 동작과 다르다면, 테스트는 실패해야 합니다.
it("예상 동작이 실제 동작과 일치하지 않을 때 오류가 발생합니다.", function() {
let even = function(num) {
return num % 2 === 0; // 체크하려는 함수에 뭔가 문제가 있군요!
};
if (even(10) !== true) {
throw new Error("10은 짝수여야 합니다!");
}
});
});
/**
* 아래의 테스트들은 detectNetwork 함수를 detectNetwork.js 파일로부터 불러와
* 함수가 정상적으로 작동하는지 테스트합니다.
* detectNetwork.js파일과 현재 파일을 수정해 모든 테스트가 통과하도록 만들어보세요.
*/
describe("Diner's Club", function() {
// 주의하세요, 테스트에도 버그가 존재할 수 있습니다...
it("has a prefix of 38 and a length of 14", function() {
// throw new Error("Delete me!");
if (detectNetwork("38345678901234") !== "Diner's Club") {
throw new Error("Test failed");
}
});
it("has a prefix of 39 and a length of 14", function() {
if (detectNetwork("39345678901234") !== "Diner's Club") {
throw new Error("Test failed");
}
});
});
describe("American Express", function() {
// 항상 if/throw 구문으로 오류를 체크하는 것은 귀찮은 일이기 때문에,
// 여기에 도움을 줄 수 있는 함수를 하나 제공했습니다. 입력값이 true가 아닐 경우 에러를 발생시킵니다.
let assert = function(isTrue) {
if (!isTrue) {
throw new Error("Test failed");
}
};
it("has a prefix of 34 and a length of 15", function() {
assert(detectNetwork("343456789012345") === "American Express");
});
it("has a prefix of 37 and a length of 15", function() {
assert(detectNetwork("373456789012345") === "American Express");
});
});
describe("Visa", function() {
// Chai는 테스트에 필요한 헬퍼 함수들이 담긴 라이브러리입니다!
// Chai는 이전에 만들었던 assert 함수와 동일한 기능을 하는 assert 함수를 제공합니다.
// Chai가 제공하는 assert 함수를 어떻게 사용하는지 웹사이트의 공식 문서를 참고해보세요.
// http://chaijs.com/
let assert = chai.assert;
it("has a prefix of 4 and a length of 13", function() {
assert(detectNetwork("4123456789012") === "Visa");
});
it("has a prefix of 4 and a length of 16", function() {
assert(detectNetwork("4123456789012345") === "Visa");
});
it("has a prefix of 4 and a length of 19", function() {
assert(detectNetwork("4123456789012345678") === "Visa");
});
});
describe("MasterCard", function() {
// Chai는 좀 더 영어 문법에 가까운 코드로 테스트를 작성할 수 있게 도와줍니다.
// Expect 문법은 그 중 한가지이며, 다른 문법도 있습니다.
// 이와 관련해 더 알고 싶다면, 공식 문서를 참고하세요.
// http://chaijs.com/api/bdd/
let expect = chai.expect;
it("has a prefix of 51 and a length of 16", function() {
expect(detectNetwork("5112345678901234")).to.equal("MasterCard");
});
it("has a prefix of 52 and a length of 16", function() {
expect(detectNetwork("5212345678901234")).to.equal("MasterCard");
});
it("has a prefix of 53 and a length of 16", function() {
expect(detectNetwork("5312345678901234")).to.equal("MasterCard");
});
// expect 대신에 should라는 문법을 사용해서 스타일을 조금 변경할 수도 있습니다.
// 사실 둘 중 어떤 것을 사용하는지는 중요하지 않습니다.
// 스타일에 관련해서는 다음 사이트를 참조하세요. http://chaijs.com/guide/styles/
// 다만 중요한 것은 스타일의 일관성을 유지하는 것입니다.
// (우리는 공부를 하는 중이기 때문에 두가지 방법 모두를 사용해 보았습니다.)
// 테스트를 작성하는 중에, 두가지 방법을 동시에 사용하려고 하면 진행되지 않을 것입니다.
// expect나 should 둘 중에 한가지 방법을 선택해서 사용하세요.
let should = chai.should();
it("has a prefix of 54 and a length of 16", function() {
should.equal(detectNetwork("5412345678901234"), ("MasterCard"));
});
it("has a prefix of 55 and a length of 16", function() {
should.equal(detectNetwork("5512345678901234"), ("MasterCard"));
});
});
describe("Discover", function() {
// 함수가 없는 테스트는 "pending"이라는 표시가 뜨며 실행되지 않습니다.
// 아래 테스트를 작성하고 테스트가 통과하도록 만드십시오.
let expect = chai.expect;
it("has a prefix of 6011 and a length of 16", function(){
expect(detectNetwork('6011123412341234')).to.equal("Discover")
});
it("has a prefix of 6011 and a length of 19", function(){
expect(detectNetwork('6011123412341234123')).to.equal("Discover")
});
it("has a prefix of 65 and a length of 16", function(){
expect(detectNetwork('6512341234123412')).to.equal("Discover")
});
it("has a prefix of 65 and a length of 19", function(){
expect(detectNetwork('6511123412341234123')).to.equal("Discover")
});
it("has a prefix of 644 and a length of 16", function(){
expect(detectNetwork('6442341234123412')).to.equal("Discover")
});
it("has a prefix of 644 and a length of 19", function(){
expect(detectNetwork('6441123412341234123')).to.equal("Discover")
});
it("has a prefix of 645 and a length of 16", function(){
expect(detectNetwork('6452341234123412')).to.equal("Discover")
});
it("has a prefix of 645 and a length of 19", function(){
expect(detectNetwork('6451123412341234123')).to.equal("Discover")
});
it("has a prefix of 646 and a length of 16", function(){
expect(detectNetwork('6462341234123412')).to.equal("Discover")
});
it("has a prefix of 646 and a length of 19", function(){
expect(detectNetwork('6461123412341234123')).to.equal("Discover")
});
it("has a prefix of 647 and a length of 16", function(){
expect(detectNetwork('6472341234123412')).to.equal("Discover")
});
it("has a prefix of 647 and a length of 19", function(){
expect(detectNetwork('6471123412341234123')).to.equal("Discover")
});
it("has a prefix of 648 and a length of 16", function(){
expect(detectNetwork('6482341234123412')).to.equal("Discover")
});
it("has a prefix of 648 and a length of 19", function(){
expect(detectNetwork('6481123412341234123')).to.equal("Discover")
});
it("has a prefix of 649 and a length of 16", function(){
expect(detectNetwork('6492341234123412')).to.equal("Discover")
});
it("has a prefix of 649 and a length of 19", function(){
expect(detectNetwork('6491123412341234123')).to.equal("Discover")
});
});
- Mocha(테스트 프레임워크)
Mocha 테스트는 그저 다음 기능을 하는 도구이다.
- 함수를 실행할 때 오류가 발생하면, 실패합니다.
- 오류가 발생하지 않으면, 실패하지 않습니다.
- chai(라이브러리)
Chai는 테스트에 필요한 헬퍼 함수들이 담긴 라이브러리이다.
ex) assert, expect, sould
React와 TDD
React 환경에서 테스트하기
React에서 테스트는 Testing Library, Jest를 이용해서 할 수 있다. (Testing Library, Jest 둘다 React에서만 쓸 수 있는 라이브러리는 아니다. Vue, Svelte 등 다른 프레임워크에서도 이용할 수 있다.)
Testing Library에서 React용 React Testing Library을 제공하고 있기 때문에, create-react-app을 이용하여 React 프로젝트를 생성하면 자동으로 Testing Library를 이용할 수 있다. 이렇게 설치한 Testing Library는 테스트를 실행하고 싶은 컴포넌트나 클릭 이벤트등 다양한 곳에 쓸 수 있다.
Jest는 JavaScript의 Testing Framework / Test Runner로써, 테스트 파일을 자동으로 찾아 테스트를 실행하고, 테스트를 실행한 결과 기대만큼 올바른 값을 가지고 있는지 함수를 이용하여 체크하여 테스트가 성공인지 실패인지를 판단해준다.
Testing Libarary와 Jest는 역할이 각각 다르기 때문에 React에 대한 테스트를 진행할 때는 어느 한쪽 라이브러리를 이용하는 것만으로는 테스트를 할 수 없다.
1. React 기본 테스트 환경 확인하기
1-1. 새로운 React 프로젝트 생성하기
npx create-react-app 명령을 실행하여 React 프로젝트를 만들어 보자. create-react-app에서 React 프로젝트를 생성하면 테스트 환경이 설정되어 있기 때문에 바로 테스트가 가능하다.
프로젝트 생성 후 프로젝트 폴더로 이동하여 설치된 JavaScript의 라이브러리를 확인하기 위해 package.json 파일을 확인하면 dependencies 안에 @testing이라는 접두어가 붙은 3개의 라이브러리를 확인할 수 있다. 이것들이 바로 테스트를 수행할 때 이용하는 라이브러리이다. scripts안에 test가 있는 것으로 미루어 볼 때, 테스트를 실행할 때는 npm run test를 이용하면 된다는 것을 알 수 있다.
- @testing-library/jest-dom : Jest-dom 제공하는 custom matcher를 사용할 수 있게 해준다.
- @testing-library/react : 컴포넌트의 요소를 찾기 위한 query가 포함되어 있다.
- @testing-library/user-event : click 등 사용자 이벤트에 이용된다.
1-2. 테스트 파일 확인하기
다시 src 폴더 안을 확인해 보면, setupTests.js 와 App.test.js 라는 이름의 파일을 확인할 수 있다. App.test.js 파일에는 간단한 테스트가 이미 만들어져 있다.
작성한 프로젝트의 package.json 파일과 파일의 구성을 보면 npx create-react-app 명령을 실행하여 프로젝트를 생성함으로써 테스트를 하기 위한 환경이 사전에 설치 되어 있는 것을 알 수 있다.
npm run test 명령어로 test를 실행시킬 수 있다.
테스트를 실행하면 위와 같은 대화식 메시지가 나타나는데, 메시지를 보면 키보드 키에 따라 다양한 처리를 할 수 있다는 것을 알 수 있다. 지금은 Press a to run all tests. 라고 설명되어 있는 부분을 실행하기 위해 a키를 눌러 보자.
a키를 누르면 테스트가 실행되고, 실행한 테스트에 성공했음을 의미하는 PASS 메시지를 확인할 수 있다. 이외에도 테스트 건수나 테스트에 걸린 시간 등도 확인이 가능하다.
이제 다시 App.test.js로 돌아가서 내부에 어떤 코드가 들어있는지 확인해 보자. test 함수는 Jest 함수로 테스트를 실행할 때 반드시 이용하는 함수이다. test 함수의 첫 번째 인자는 테스트가 어떤 내용인지 나중에 다시 읽어도 테스트 내용을 알 수 있는 설명을 작성한다. 두 번째 인자는 하고자 하는 테스트를 함수의 형태로 넣는다.
두 번째 인자로 들어간 함수를 자세히 살펴보자.
- 첫번째 줄을 보면, 테스트하고자 하는 컴포넌트를 render()함수로 전달하고 있다. react-testing-library에서는 테스트를 진행할 컴포넌트를 render()함수의 인자로 전달한다.
- 두번째 줄에 있는 screen의 다양한 메소드 중 getByText() 메서드를 이용하여 render()에서 가져온 App 컴포넌트 중 "learn react"라는 문자열이 있는지 확인하여 linkElement에 할당하고 있다. (“i”는 Regular Expression으로 "i"를 붙임으로써 대소문자를 구분하지 않게 만들어 준다.)
- 세번째 줄에서는 expect 함수의 인자로 지정한 요소가 document.body에 존재하는지 toBeInTheDocument 함수를 사용하여 체크하고 있다. 여기서 toBeInTheDocument 함수는 matchers 함수라고 부른다.
App.test.js 파일 중 이용되고 있는 test 함수, expect 함수는 Jest의 함수고, toBeInTheDocument는 jest-dom 라이브러리에 포함된 Custom matchers이다. 하지만 아직 jest-dom을 import 한 적이 없다.
jest-dom은 src 폴더의 setupTests.js 파일 내에서 import 되고 있다. setupTests.js 파일에서 import를 삭제하면 toBeInTheDocument 함수를 이용할 수 없다.
npm run test 를 실행시켰을 때의 결과는 PASS였다. 테스트로 확인한 부분 App.js 파일을 보면 <a>태그의 콘텐츠로 "Learn React"를 찾을 수 있다. App.test.js의 내부에서 screen의 getByText 메서드로 찾아낸 부분이 바로 이 부분이다.
Learn의 첫 글자인 L을 지운 다음 테스트를 실행하면 learn react를 가진 요소를 찾지 못해 위와 같이 FAIL이 뜨고 테스트가 실패하게 된다.
1-3. 간단한 테스트 직접 만들기
앞에서 간단하게 다뤄본 테스트는 React를 이용하고 있었기 때문에 테스트를 처음 실행한 사람에게는 어렵게 느껴질 수 있다. 그래서 테스트를 좀 더 이해하기 위해 Jest만을 이용하여 테스트를 실행하고 테스트의 기본적인 규칙이나 기술방법을 확인해 보는 것이 필요하다.
src폴더에 Example.test.js 파일을 작성해 보자. 테스트를 실행하기 위해서는 파일명을 <파일명>.test.js와 같이 하면된다. (<파일명>.spec.js에서도 가능합니다.) 파일명을 이렇게 짓고 테스트를 작성하면, Jest가 테스트 파일로 판단하여 작동한다.
Jest만을 이용하므로 React의 컴포넌트를 이용하지 않고 덧셈의 간단한 테스트를 만들어보자. 테스트는 test 함수 안에 쓰고 test 함수의 첫 번째 인자에는 테스트에 대한 설명을, 두 번째 인자에는 테스트의 내용을 함수의 형태로 작성한다.
expect 함수의 인자로 2+2를 넣고 toBe함수에 4라는 결과를 지정하여 테스트를 해보자. toBe 함수는 matchers 함수 중 하나로 expect 함수에 지정한 값이 toBe 함수에 지정한 값과 일치하는지 체크한다. npm run test를 실행하여 "a"를 선택하면 Example.test.js와 App.test.js가 모두 실행이 된다. 2+2의 결과로 4를 지정하였으니 PASS가 나온다.
그렇다면 만약 toBe 함수의 인자를 5로 바꾸면 어떻게 될까?
이번에는 Example.test.js에서 FAIL이 발생했습니다. 메시지를 잘 읽어보면, 왜 테스트가 실패했는지 확인할 수 있다.
test 함수 대신 it 함수를 사용해도 같은 결과가 나온다. 또 describe 함수를 사용하면 it함수나 test함수를 하나의 파일에 여러 개 포함할 수 있다.
describe 함수 블록은 Test Suites라고 불리며 test/it 함수 블록은 Test(Test Case)라고 한다.
2. 직접 만든 컴포넌트 테스트하기
2-1. 컴포넌트 만들기
Jest의 간단한 기능을 알아봤으니 이제 본격적으로 React의 컴포넌트를 이용하여 테스트 연습을 해보자. src 폴더에 components 폴더를 생성하고 Light.jsx 파일을 작성한 후, App.test.js 파일은 삭제한다.
import {useState} from 'react';
function Light({ name }) {
const [light, setLight] = useState(false);
return (
<div>
<h1>
{name} {light ? 'ON' : 'OFF'}{' '}
</h1>
<button
onClick={() => setLight(true)}
disabled={light ? true : false}
>
ON
</button>
<button
onClick={() => setLight(false)}
disabled={!light ? true : false}
>
OFF
</button>
</div>
);
}
export default Light;
Light.js는 전원의 상태를 OFF에서 ON으로 전환하는 심플한 컴포넌트이다. name은 props로 전달받아 <h1>태그를 사용하여 표시한다.
App.js 파일에서 Light.jsx 파일을 import하고 있다. 여기까지 되었다면 npm start 명령을 입력하여 실행해 보자.
2-2. 컴포넌트 테스트하기
이렇게 만든 컴포넌트를 테스트 해보자. 먼저 compoents 폴더 아래에 Light.test.js 파일을 만들고 아래와 같이 작성한다.
import { render, screen } from '@testing-library/react';
import Light from './Light';
it('renders Light Component', () => {
render(<Light name="전원" />);
const nameElement = screen.getByText(/전원 off/i);
expect(nameElement).toBeInTheDocument();
})
getByText를 이용하여 props로 전달된 전원이 올바르게 표시되어 있는지 확인해 보자. 올바르게 코드를 작성했다면 PASS가 결과로 나오게 된다.
이번에는 OFF 버튼이 disabled로 되어 있는지 matchers 함수의 toBeDisabled 함수를 이용해 테스트를 작성해보자. getByRole을 이용하여 button을 지정했고, 버튼이 2개이므로 옵션의 name을 이용하여 OFF 버튼을 찾는다. 현재 OFF버튼이 disabled 상태이기 때문에, 테스트의 결과도 PASS로 나오는 것을 확인할 수 있다.
it('off button disabled', () => {
render(<Light name="전원" />);
const offButtonElement = screen.getByRole('button', { name: 'OFF' });
expect(offButtonElement).toBeDisabled();
})
toBedisabled 함수 앞에 not을 붙이면 반대로 ON 버튼이 disabled가 아니라는 것을 구현할 수 있다. 현재 ON버튼이 disabled 상태가 아니기 때문에, 테스트의 결과도 PASS로 나오는 것을 확인할 수 있다.
it('on button enable', () => {
render(<Light name="전원" />);
const onButtonElement = screen.getByRole('button', { name: 'ON' });
expect(onButtonElement).not.toBeDisabled();
});
버튼 클릭 이벤트의 유무도 테스트로 구현할 수 있다. 그럴 때는 fireEvent를 사용한다. fireEvent를 import하고, fireEvent의 click메서드에 전달인자로 테스트하고자 하는 요소를 전달한다. 해당 테스트의 결과가 PASS로 나오는 것을 확인할 수 있다.
import { fireEvent, render, screen } from '@testing-library/react';
import Light from './Light';
it('change from off to on', () => {
render(<Light name="전원" />);
const onButtonElement = screen.getByRole('button', { name: 'ON' });
fireEvent.click(onButtonElement);
expect(onButtonElement).toBeDisabled();
})
'코드스테이츠 SEB FE 41기 > Section 별 내용 정리' 카테고리의 다른 글
Lighthouse - Opportunities를 통해 최적화하기 (0) | 2022.12.05 |
---|---|
section4/Unit8/[최적화] Optimization (0) | 2022.12.05 |
section4/Unit6/[API] GraphQL (0) | 2022.12.01 |
section4/Unit5/[컴퓨터 공학] 기초(11/30) (0) | 2022.11.30 |
section4/Unit4/[React] 심화(11/29) (0) | 2022.11.29 |