TDD (Test Driven Development)

Judy·2023년 1월 24일
0

Dev

목록 보기
6/7
post-custom-banner

Software Test

  • Unit Test
    • Test each function and class unit (modular test)
  • Integration Test
    • Test by combining modules
  • Acceptance Test
    • Test by assuming the production environment

TDD (Test Driven Development)

  • Coding test code first
  • And hard-coding to develop faster & refactoring
  • 👉 Increase performance and reliability

Unit Test

Jest

  • Javascript test tool 🛠️
  • Install : npm i -D jest

Project Tree

.
├── node_modules
├── src
│   ├── controllers
│   ├── models
│   │   └── data-source.js (Connect to DB using TypeORM)
│   ├── routes
│   ├── services
│   └── utils
├── tests
│   └── **user.test.js**
├── .env
├── **.env.test**
├── .gitignore
├── **app.js**
├── package-lock.json
├── package.json
└── **server.js**

app.js

Create App

  • Only router, middleware code of application
  • Create createApp function that returns app
const express = require("express");
const cors = require("cors");
const morgan = require("morgan");

const dotenv = require("dotenv");
dotenv.config();

const routes = require("./src/routes");
const { errorHandler } = require("./src/utils/errorHandling.js");

const createApp = () => {
  const app = express();

  app.use(express.json());
  app.use(cors());
  app.use(morgan("combined"));

  app.use(routes);
  app.use(errorHandler);

  return app;
};

module.exports = { createApp };

server.js

Connect to DB (Initialize) & Start server

  • Use createApp function to use app that created in app.js
  • Run startServer function that includes AppDataSource.initialize() and app.listen().
  • So run server.js when start server, and use app (= return value of
    createApp) in test code.
require("dotenv").config();

const { createApp } = require("./app");
const { AppDataSource } = require("./src/models/data-source");

const startServer = async () => {
  const app = createApp();
  	// Connect to DB & initialize
	await AppDataSource.initialize();

  const PORT = process.env.PORT;

  app.listen(PORT, () => {
    console.log(`Listening on Port ${PORT}`);
  });
};

startServer();

user.test.js

Test code = describe + test

  • Not only describe but also test are consists of (description, callback)
  • description : Category or description about the logic
  • describe and test can be declaired in callback
  • Functional units(function, class..) can be called in test callback and we can compare the values with expect to see if they were performed well.
  • describe can helps adding caption about test
  • Test using jest instruction
    • jest runs test code files automatically such as .test.js, .spec.js
// npm i --save-dev supertest

// Send a test request to the app using the request of the *supertest*.
const request = require("supertest");

// Call CreateApp function to use app in request of supertest.
const { createApp } = require("../app");
// Call DataSource object for connecting to DB
const { AppDataSource } = require("../src/models/data-source");

// Category
describe("Sign Up", () => {
  let app;

  // Before start the test, create app object
  beforeAll(async () => {
    app = createApp();
    
    // We can initialize DB here, but I moved the code to server.js to avoid some log :
    // Jest has detected the following 1 open handle potentially keeping Jest from exiting:
  	//●  TCPWRAP
    // ...
    // await AppDataSource.initialize();
  });

  afterAll(async () => {
    // Remove all unnecessary data in test DB
    // If TRUNCATE instruction doesn`t work because of foreign key,
    // temporarily release the foreign key and restore.
    await AppDataSource.query(`SET foreign_key_checks = 0;`);
    await AppDataSource.query(`TRUNCATE users`);
    await AppDataSource.query(`SET foreign_key_checks = 1;`);

    // After all tests are finished, disconnect DB
    await AppDataSource.destroy();
  });

  // Test all different cases.
  // It helps to check if the code is well develpted for your intentions from the test stage!

  // 1. Check the emaill address rule
  test("FAILED: invalid email", async () => {
    // Send test request to app using supertest의 request
    await request(app)
      .post("/users/signup") // HTTP Method, Endpoint address
    	// body
      .send({
        email: "wrongEmail",
        password: "password001@",
        username: "testName",
        mobile: "testMobile",
      })
      .expect(400) // Test appropriate statusCode and response using expect()
      .expect({ message: "invalid email!" });
  });

  // 2. Check creating user
  test("SUCCESS: created user", async () => {
    await request(app)
      .post("/users/signup")
      .send({
        email: "wecode002@gmail.com",
        password: "password001@",
        username: "testName",
        mobile: "testMobile",
      })
      .expect(201);
  });

  // 3. Check duplicated email
  test("FAILED: duplicated email", async () => {
    await request(app)
      .post("/users/signup")
      .send({
        email: "wecode002@gmail.com",
        password: "password001@",
        username: "testName",
        mobile: "testMobile",
      })
      .expect(409)
      .expect({ message: "ALREADY_SIGNED_UP" });
  });
});

.env.test

Create test DB or copy DB

  • Change TYPEORM_DATABASE = testDB

package.json

Setting dotenv config path (=.env.test)

  • Setting jest option
  • Force exit when test finished
"test": "DOTENV_CONFIG_PATH=.env.test jest --setupFiles=dotenv/config --runInBand --detectOpenHandles --forceExit"

Result


(Additional) CI/CD

Q. How can we test and release program with less time and effort? 🤔

A. Continuous Integration / Continuous Deployment

  • Automate the development environment

CI (Continuous Integration)

  • Automate test and merge after commit

CD (Continuous Deployment)

  • Automate test and release production after commit & push

CICD System

  • Jenkins
  • Travis CI
  • Circle CI
  • Github Action
profile
NLP Researcher
post-custom-banner

0개의 댓글