본문 바로가기
IT/Nodejs

APP 리뷰 수집 서비스

by DOSGamer 2022. 8. 23.
반응형

TDD 로 해보자

언어 : javascript

테스트프레임워크 : jest

IDE : VSCode

JEST Doc

https://jestjs.io/docs/getting-started

STEP 0. 환경구성

# npm 초기화
npm init -y
# jest 설치
npm i -D jest
# appstore scraper 라이브러리 설치
npm i app-store-scraper
# playstore scraper 라이브러리 설치
npm i google-play-scraper

package.json 수정

{
	"scripts": {
    "test": "jest"
  },	
}

Generate a basic jest configuration file

# npx jest --init

The following questions will help Jest to create a suitable configuration for your project

√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing » node
√ Do you want Jest to add coverage reports? ... no
√ Which provider should be used to instrument code for coverage? » v8
√ Automatically clear mock calls and instances between every test? ... no

�  Configuration file created at D:\studyspace\management-app-review\jest.config.js

폴더구조

+---src
|   +---controllers
|   \---models
+---test
|   +---integration
|   \---unit

STEP 1. 테스트 케이스 작성

google playstore 와 apple appstore 두 곳에서 수집할 예정이기에 테스트 케이스가 스토어별로 존재해야 한다

공통 테스트 케이스

function 존재 확인

controller 호출시 model 호출 확인

기능 테스트 케이스

구글 플레이스토어에서 정보를 가져오는 테스트 케이스

app 정보 가져오기

review 정보 가져오기

이전 review의 마지막 번호 기준으로 다음 review 정보 가져오기

app 정보 저장

reviews 저장

STEP 2. 단위테스트 TDD 작성 ( app 정보 가져오기 )

step2 - 1. controller 에 google app 정보 function 이 있는 지 확인하는 테스트 코드 작성

# test/unit/appinfos.test.js
const appInfoController = require("../../src/controllers/appInfos");

describe("Google Playstore", () => {
  describe("Get AppInfo", () => {
    it("should export a function", () => {
      expect(appInfoController.getGoogleAppInfoByAppId).toBeInstanceOf(
        Function
      );
    });
  });
});

npm test

실행하면 function 이 없어서 fail 남

controller 인 appInfos 작성

# src/controllers/appInfos.js
"use strict";
const appinfoModel = require("../models/Appinfo");


exports.getGoogleAppInfoByAppId = async (req) => {
  try {
    const appInfo = await appinfoModel.getGoogleAppInfoByAppId(req);
    return appInfo;
  } catch (error) {
    console.log(error);
  }
};


# src/models/Appinfo.js
"use strict";
const playstore = require("google-play-scraper");

const Appinfo = {
};

module.exports = Appinfo;

실행하면 성공

npm test

step2 - 2. controller 에 google app 정보 function 호출시에 model 의 findGoogleById 가 호출되는 지 테스트 케이스 작성

# test/unit/appinfos.test.js
...
		it("should call Appinfo.getGoogleAppInfoByAppId", async () => {
      //Arrange
      const req = {
        appId: "com.nsmobilehub",
        lang: "ko",
      };
      //Act
      await appInfoController.getGoogleAppInfoByAppId(req);
      //Assert
      expect(appInfoModel.findGoogleById).toHaveBeenCalledWith(req);
    });
...

테스트 케이스를 만들고 npm test 로 실행하면 fail 이 발생하고

테스트 케이스가 성공 할 수 있게 코드를 아래 와 같이 작성하면서

수시로 npm test 를 실행하면서 성공이 되는 지 확인한다

# src/controllers/appInfos.js
"use strict";
const appinfoModel = require("../models/Appinfo");

exports.getGoogleAppInfoByAppId = async (req) => {
  try {
    const appInfo = await appinfoModel.findGoogleById(req);
    return appInfo;
  } catch (error) {
    console.log(error);
  }
};

# src/models/Appinfo.js
"use strict";
const playstore = require("google-play-scraper");

const Appinfo = {
  findGoogleById: async (req) => {
    return playstore.app(req);
  },
};

module.exports = Appinfo;

step2 - 3. nsmall APP 의 정보를 요청하고 return 될 json 을 미리 mockReturnValue 으로 설정하여 정상적인 데이터가 온다는 가정을 만들고 Assert 를 설정한다. (fail 의 경우는 error 가 리턴되도록 한다)

data 폴더에 request 용 , response 용 json 파일을 만들어 둔다

# test/unit/appinfos.test.js
...
const REQ_NSMALL = require("../../data/req_nsmall.json");
const RES_NSMALL_GOOGLE = require("../../data/res_nsmall_google.json");

appInfoModel.findGoogleById = jest.fn();

...
    it("should call google app info", async () => {
      //Arrange
      const req = REQ_NSMALL;
      appInfoModel.findGoogleById.mockReturnValue(RES_NSMALL_GOOGLE);
      //Act
      const res = await appInfoController.getGoogleAppInfoByAppId(req);
      //Assert
      expect(res.title).toBe("NS홈쇼핑");
    });
...

findGoogleById 를 jest.fn() 으로 설정하고 mockReturnValue 로 값을 세팅해서

함수가 실제로 실행되지 않는다

npm test 로 테스트 성공할 때 까지 반복한다

step2 - 4. nsmall APP 의 정보를 요청하고 실패되어 return 될 json 을 미리 mockReturnValue 으로 설정하여 에러 데이터가 온다는 가정을 만들고 Assert 를 설정한다.

# test/unit/appinfos.test.js
...
		it("should handle errors", async () => {
      //Arrange
      const req = REQ_NSMALL;
      const errorMessage = { message: "App not found (404)", status: 404 };
      appInfoModel.findGoogleById.mockReturnValue(errorMessage);
      //Act
      const res = await appInfoController.getGoogleAppInfoByAppId(req);
      //Assert
      expect(res.message).toBe("App not found (404)");
      expect(res.status).toBe(404);
    });
...

실제로 return 되는 error message 를 참조해서 errorMessage 를 만들고

errorMessage 를 mockReturnValue 로 findGoogleById 에 설정해준다

그리고 response 를 받아서 Assert 로 확인한다

STEP 3. 통합테스트 TDD 작성 (app 정보 가져오기)

단위테스트와 다른 점은 mock 같은 격리 프로그램을 사용하지 않고

연관 되어서 서비스가 제대로 되는 지 테스트 한다

단위테스트 : google-play-scraper 가 작동되는 지 확인하지 않는다

통합테스트 : google-play-scraper 가 작동되는 지 확인한다

# test/integration/appinfos.int.test.js

const appInfoController = require("../../src/controllers/appInfos");
const REQ_NSMALL = require("../data/req_nsmall.json");

describe("Appinfo Integration Test", () => {
  it("Get NSmall AppInfo from Google", async () => {
    //Arrange
    const req = REQ_NSMALL;
    //Act
    const res = await appInfoController.getGoogleAppInfoByAppId(req);
    //Assert
    expect(res.title).toBe("NS홈쇼핑");
  });
});

STEP 4. (다시 STEP 2) 단위테스트 TDD 작성 (review 정보 가져오기)

기능 테스트 케이스

구글 플레이스토어에서 정보를 가져오는 테스트 케이스

  • app 정보 가져오기
  • review 정보 가져오기
    • 이전 review의 마지막 번호 기준으로 다음 review 정보 가져오기
  • app 정보 저장
  • reviews 저장

STEP 4-1. review 정보를 가져오는 기능을 테스트 할 수 있는 단위테스트 케이스를 만든다

# test/unit/appinfos.test.js
const appInfoController = require("../../src/controllers/appInfos");
const appInfoModel = require("../../src/models/Appinfo");

const REQ_NSMALL = require("../data/req_nsmall.json");
const RES_NSMALL_GOOGLE = require("../data/res_nsmall_google.json");

appInfoModel.findGoogleById = jest.fn();

describe("Google Playstore", () => {
  describe("Get AppInfo", () => {
		...
  });

	//앱의 리뷰를 가져온다
	describe("Get AppReview", () => {
		//should exist getReviews function
    it("should have a getReviews function", () => {
      //Assert
      expect(appInfoController.getGoogleAppReviewsByAppId).toBeInstanceOf(
        Function
      );
    });
  });
});

테스트를 실행하면 바로 에러가 난다

# src/controllers/appInfos.js
...
exports.getGoogleAppReviewsByAppId = async (req) => {
  try {
    const appReviews = await appinfoModel.findGoogleReviewsById(req);
    return appReviews;
  } catch (error) {
    return error;
  }
};
...

아직 appinfoModel 에 findGoogleReviewsById 함수를 만들지 않아도 테스트는 통과된다

STEP 4-2. controller 의 getGoogleAppReviewsByAppId 함수를 호출하면 model 의 findGoogleReviewsById 가 호출 되는 지 테스트 케이스를 만든다

# test/unit/appinfos.test.js
...
		it("should call Appinfo findGoogleReviewsById", async () => {
      //Arrange
      const req = REQ_NSMALL;
      //Act
      await appInfoController.getGoogleAppReviewsByAppId(req);
      //Assert
      expect(appInfoModel.findGoogleReviewsById).toHaveBeenCalledWith(req);
    });
...

단위 테스트 케이스를 만들어서 실패하고

# src/mocdels/Appinfo.js
...
const Appinfo = {
  findGoogleById: async (req) => {
    return playstore.app(req);
  },
  findGoogleReviewsById: async (req) => {
    return playstore.reviews(req);
  },
};
...
$ npm test
expect(received).toHaveBeenCalledWith(...expected)
Matcher error: received value must be a mock or spy function

mock 처리를 안해서 에러가 난다

# test/unit/appinfos.test.js
...
// findGoogleReviewsById 을 spy function 으로 설정해준다
appInfoModel.findGoogleReviewsById = jest.fn();
...

jest 함수로 만들어 주고 테스트를 실행하면

테스트가 전부 통과되는 것을 확인할 수 있다

STEP 4-3. 구글 앱 리뷰 정보를 가져오는 단위테스트 케이스를 만든다

review 에서 사용하는 request json 구조가 틀리니 request json 파일을 분리해준다

response json 파일 내용도 app 과 review 용으로 분리해준다

# test/data/req_nsmall.json
//googleApp 과 googleReviews request 용 정보를 분리했다
//추후 appleApp 과 appleReviews 도 추가할 예정이다
{
  "googleApp": {
    "appId": "com.nsmobilehub",
    "lang": "ko"
  },
  "googleReviews": {
    "appId": "com.nsmobilehub",
    "sort": 2,
    "num": 1,
    "country": "kr",
    "lang": "ko"
  }
}

# test/data/res_nsmall.json
{
  "googleApp": {
    "title": "NS홈쇼핑",
    "description": "건강한 아이디어, NS홈쇼핑"
  },
  "googleReviews": {
    "data": [
      {
        "id": "gp:AOqpTOE4bkcZFvxpaqA0pSph6gnjVeglkYjsKnqyiFdPpRkO4sNsAkvD5ZdgctdmNGw",
        "userName": "snaL",
        "scoreText": "1",
        "title": null,
        "text": "로그인 절대안됨ㅡㅡ한시간내내 보안문자를 몇번을 찍는지",
        "replyDate": null,
        "replyText": null,
        "version": "3.2.4",
        "thumbsUp": 0
      }
    ],
    "nextPaginationToken": "CoEBCn8q"
  }
}

# test/unit/appinfos.test.js
...
		it("should return json body in response", async () => {
      //Arrange
      const req = REQ_NSMALL.googleReviews;
      appInfoModel.findGoogleReviewsById.mockReturnValue(
        RES_NSMALL.googleReviews
      );
      //Act
      const res = await appInfoController.getGoogleAppReviewsByAppId(req);
      //Assert
      expect(res.data).toBeDefined();
      expect(res.data).toBeInstanceOf(Array);
			expect(res.nextPaginationToken).toBeDefined();
    });
...

STEP 4-4. 구글 앱 리뷰 정보를 가져오는 못하는 단위테스트 케이스를 만든다

# test/unit/appinfos.test.js
...
		//should handle errors or appinfo not found
    it("should handle errors", async () => {
      //Arrange
      const req = REQ_NSMALL.googleReviews;
      const errorMessage = { data: [], nextPaginationToken: null };
      appInfoModel.findGoogleReviewsById.mockReturnValue(errorMessage);
      //Act
      const res = await appInfoController.getGoogleAppReviewsByAppId(req);
      //Assert
			expect(res.data).toBeDefined();
      expect(res.data).toBeInstanceOf(Array);
      expect(res.data.length).toBe(0);
			expect(res.nextPaginationToken).toBeNull();
    });
...

STEP 5. (다시 STEP 3) 통합테스트 TDD 작성 (review 정보 가져오기)

# test/integration/appinfos.int.test.js
...
	//Get AppReviews from Google Playstore
  it("Get NSmall AppReviews from Google", async () => {
    //Arrange
    const req = REQ_NSMALL.googleReviews;
    //Act
    const res = await appInfoController.getGoogleAppReviewsByAppId(req);
    //Assert
    console.log(res);
    expect(res.data).toBeDefined();
    expect(res.data).toBeInstanceOf(Array);
    expect(res.data[0].score).toBeLessThanOrEqual(5);
    expect(res.nextPaginationToken).toBeDefined();
  });
...


Uploaded by N2T

반응형