소프트웨어 테스팅이란 소프트웨어의 결함을 찾는 과정이다.
소프트웨어 테스팅에는 다음과 같은 절차가 존재한다.
이후 소프트웨어 릴리즈가 진행된다.
아래의 그림은 Testing 자동화 관점에서의 pyramid이다.
https://martinfowler.com/bliki/TestPyramid.html
피라미드는 테스트 코드를 레벨 별로 작성해야한다는 의미를 내포하고있다.
그렇다면 테스트 자동화 시 레이어 별 테스트 코드를 어떻게 작성해야할까?
테스트 코드의 양은
단위테스트(Unit) > service > UI(end to end) 순으로 작성한다.
이런 UI 테스팅 코드는 코드작성이 매우 어렵고 유지 보수도 어렵다
그러나 단위테스트는 실행 속도가 빠르고 .
많은 양의 코드를 작성해서 code coverage를 넓혀두어야 한다.
code coverage란?
소프트웨어 테스트 시 테스트가 충분한가를 나타내는 지표 중 하나이다.
코드 자체가 얼마나 실행되었는지 숫자로 확인할 수 있다.
참고자료 : 📌 Nesoy 님의 코드 커버리지(Code Coverage)란?
참고자료 : 📌 lxxjn0 님의 코드 분석 도구 적용기 - 1편, 코드 커버리지(Code Coverage)가 뭔가요?
아래의 두 그림은 MSA(마이크로 서비스 아키텍처) 환경 관점에서의 테스팅이다.
최근에는 많은 서비스가 마이크로 서비스 아키텍처로 개발된다.
https://martinfowler.com/articles/microservice-testing/#conclusion-test-pyramid
위 그림은 일반적인 어플리케이션의 구성도이다.
https://martinfowler.com/articles/microservice-testing/#conclusion-summary
이 네 가지는 마이크로 서비스 아키텍처가 아닌 일반적인 어플리케이션에도 적용될 수 있다.
(사실, 두 번째 그림에서 gateway가 없으면 MSA가 아닌 일반적인 서비스라고도 볼 수 있다.)
Integration Tests
JDBC와 관련있는 내용으로, 외부 서비스 간 연동 테스트를 의미한다.
어플리케이션에 연결된 DB가 존재하고 DB와 연동을 담당하는 객체(Repositories)가 있을 때,
그 객체가 실제 사용하는 프레임워크가 있을 수 있다.(data mappers / ORM)
Integration test는 그런 프레임워크와 데이터베이스의 연동을 테스트 하는 것이다.
어플리케이션 외부의 데이터베이스 연동 테스트 혹은 External Service(AWS 의 S3와 같은 파일 업로드 등)을 위한 API 연동.
혹은 서비스의 API를 직접 만들었을 때 이를 노출시키고 호출 시 실제 내부 상태가 어떻게 변화하는지에 대한 테스트를 진행하는 것도 Integration Tests이다.
정리해보면, Integration Tests는 외부 서비스 간 연동 테스트이다.
Unit Tests
소프트웨어에서 테스트할 수 있는 가장 작은 단위.
서비스 별, 도메인 별, 레포지토리 별 단위 테스트 코드 혹은 다른 테스트 코드를 작성할 수가 있다.
Component Tests
서비스 전체를 보게 되는
End-to-end Tests
UI가 포함.
UI가 API를 호출 -> 실제 DB가 변경되는 것을 화면에 보여주고...
이런 것 전체를 보여주는 테스트
일반적으로 백엔드 엔지니어의 경우에는 단위테스트 코드를 가장 많이 작성한다.
그 다음으로 통합 테스트도 어느 정도 작성한다.
통합테스트(Integration), end-to-end테스트와 달리 실행 속도가 매우 빠르며,
단위 테스트는 특정 부분을 고립해서 테스트한다.
https://dancerscode.com/posts/unit-tests/
SUT
System Under Test의 약자, 테스트 대상.
단위 테스트의 대상이 되는 하나의 단위(클래스)를 뜻한다.
전체 큰 시스템을 칭하는 것이 아니다.
SUT는 다른 객체와 협력관계를 맺고 커뮤니케이션을 통해 일한다.
즉, 의존관계에 있는 다른 객체가 존재하고 이를 협력 관계자라고 한다.
이 협력 관계자들과 SUT를 묶어서 테스트 하지 않고
협력 관계의 객체들을 Test Double로 대체한 뒤
SUT만 고립된 상태에서 진행하는 것이 단위 테스트이다.
Test Double이란?
SUT의 의존 구성요소(실제 객체)를 사용할 수 없을 때
(Ex: SUT는 생성자를 통해 다른 객체를 주입받아야 하는데 테스트 시 해당 객체를 사용할 수 없음
Ex2: SUT가 호출해야하는 객체를 테스트 시 사용할 수 없음)
테스트 대상 코드와 상호작용하는 가짜 객체.
Test Double의 종류에는 Stub, Mock Object 등이 있다.
Mocked Dependency
이것이 바로 Test Double이자 Mock Object이다.
Method
Method under test라고 부르기도 한다.
SUT 내부에 존재하는 기능. 즉, 시스템(클래스)가 제공하는 기능이다.
SUT를 테스트하면 메소드를 주로 테스트하게 된다.
GIVEN
단위 테스트 시 주어진 특정한 상황. 테스트 시의 전제 조건들을 만들어둔 것.
이 때 SUT에 Mock 객체를 전달해준다.
WHEN
특정 행위(메서드)를 호출할 때.
THEN
WHEN의 호출 결과를 확인하는 것
1. 기존 기능의 지속성 확인
소프트웨어에서는 지속적으로 변경이 발생한다.
이 때 발생할 수 있는 오류를 테스트 코드에 의해 보고받을 수 있다.
Ex) 계산기 코드의 단위테스트 코드
SUT : 계산기
method : 더하기, 빼기, 곱하기 ...
given : 전제조건
when : 숫자 값을 전달하여 메소드 호출
then : 반환된 결과 확인
그런데 이후 제곱근 기능이 추가되었다.
제곱근 기능에 의해 계산기의 기존 기능이 영향을 받지 않는다는 것을
어떻게 증명할 수 있을까?
이때, 이전 기능들이 이전에 작성한 단위테스트 코드에 의해 정상적으로 작동하는 것을 확인하면
배포 전 테스트 코드 실행 시 새로운 기능에 의해 기존 기능이 망가지지 않았다는 확신을 얻을 수 있다. 그런 확신이 없다면 릴리즈가 어려울 것이다.
2. 기능의 명세서 역할
테스트 코드가 있다면 구현 코드를 일일이 읽어보지 않고 테스트 케이스만 확인하고도
테스트 대상의 기능, 특정 상황에서 메소드 호출 시의 결과를 쉽게 파악할 수 있다.
즉, 단위 테스트 코드 자체가 기능의 명세서가 되는 것이다.
새롭게 합류한 팀원도 단위 테스트 코드를 확인하고 클래스의 기능을 빠르게 파악할 수 있다.
따라서 백엔드 엔지니어는 단위 테스트 코드를 무조건 작성하고, 잘 작성해야한다.
spring에서의 단위 테스트
spring에서도 단위 테스트 코드 작성을 도와주는 기능을 제공한다.
하지만 단위 테스트는 클래스 단위로 작성하기 때문에
spring환경에서 다른 것과 큰 연관이 있는 코드를 단위테스트로 작성하기 보다는
(Ex: applicationContext가 있는 객체와 상호작용하는 복잡한 코드)
상호작용이 있는 것은 Test Double을 이용하고
하나의 클래스 기능에 집중해서 단위 테스트 코드를 작성하는 것이 더 중요하다.
사실, spring은 단위테스트보다는 통합테스트에 더 큰 도움을 준다.
통합테스트는 테스트하고자 하는 코드가 다른 의존관계와 잘 연동이 되었는지를 테스트하는 것이다.
그림에는 전체 통합테스트 (점선) 내부에 단위테스트가 있다.
통합테스트에서는 이런 모듈 간의 협동을 테스트하고
데이터베이스와 같은 외부 시스템과의 연동도 테스트하는 것이다.
Unit Testing Principles, Practices, and Patterns의 그림
end-to-end 테스트 란?
통합테스트를 넘어 더 큰 시스템 전체를 보는 것을 end-to-end테스트라고 한다.
말그대로 처음부터 끝까지를 테스트 하는 것이다.
Ex) UI에서 버튼을 누르면 API가 호출되고 컨트롤러를 타서 서비스 repository -> DB까지 간 뒤
AJax요청을 통해 데이터를 조회해서 화면에 보여지는 과정을 확인.