어떻게 방관자를 사용하여 간단한 각도 포인트 테스트를 작성합니까

집적 테스트는 전방 코드를 테스트하는 중요한 도구다.단원 테스트는 모든 구성 요소나 서비스가 우리가 원하는 임무를 독립적으로 완성하도록 확보하고, 통합 테스트는 모든 단독 부분이 정확하게 협동하여 작업을 할 수 있도록 확보한다.This great article 저자 켄트 C.Dodds는 통합 테스트가 가장 높은 투자 수익을 제공하기 때문에 테스트의 대부분을 차지해야 한다고 생각한다.
Spectator는 내장된 각도 테스트 프레임워크를 봉하여 간단하지만 기능이 강한 API를 제공하여 샘플 파일을 줄이고 테스트를 더욱 뚜렷하고 읽을 수 있도록 하는 라이브러리이다.
본고에서, 나는 당신에게 '방관자' 가 어떻게 당신이 통합 테스트를 쉽게 작성하는 데 도움을 줄 수 있는지 보여 드리겠습니다.
코드로 직접 이동하려면 myGitHub와 onStackBlitz에서 찾을 수 있습니다.

접근


통합 테스트를 작성할 때, 나는 최종 사용자가 응용 프로그램을 사용하는 방식과 비슷한 테스트를 작성하는 것을 더욱 좋아한다.이것은 실천에서 다음과 같은 의미를 가진다.
  • 세부 사항을 테스트할 필요가 없다. 사용자의 상호작용과 출력만 필요하다
  • 아날로그 감소(얕은 레이어 렌더링 어셈블리 제외)
  • 여러 단언이 있는 더 긴 테스트는 수동 테스트 작업 흐름에 가깝다
  • 예제 응용 프로그램


    이 방법을 보여주기 위해 다음과 같은 작업을 수행하는 간단한 애플리케이션을 테스트했습니다.
  • 페이지가 로드될 때 JSONPlaceholder API에서 게시물 목록을 가져와 화면에 응답
  • 데이터가 로드되면 진행률 표시줄
  • 이 표시됩니다.
  • 데이터를 로드한 후 드롭다운 목록에서 사용자를 선택할 수 있습니다. 드롭다운 목록은 해당 사용자에 대해서만 게시물을 필터합니다
  • .
  • 우리는 300밀리초
  • 의 온스 제거 시간의 검색 조건에 따라 검색 상자를 입력할 수 있다.
    전체 코드에 대한 자세한 내용은 StackBlitz를 참조하십시오. 그러나 다음은 DellPostsService입니다.
    export class PostsService {
      constructor(private dataService: DataService) {}
    
      private posts = new BehaviorSubject<Post[]>(null);
      private loading = new BehaviorSubject<boolean>(true);
    
      posts$ = this.posts.asObservable();
      loading$ = this.loading.asObservable();
    
      // call the DataService which is responsible for making HTTP requests and then update the posts and loading states
      load() {
        this.loading.next(true);
    
        return this.dataService.fetch().pipe(
          tap(response => {
            this.posts.next(response);
            this.loading.next(false);
          })
        );
      }
    
      // return an observable with the filtered posts
      getPosts(searchTerm: string, userId: number) {
        const filterFunction = (post: Post) =>
          (!searchTerm ||
            post.title.toLowerCase().includes(searchTerm.toLowerCase())) &&
          (!userId || post.userId === userId);
    
        return this.posts$.pipe(map(posts => posts.filter(filterFunction)));
      }
    }
    
    저는 여기서 상세하게 소개할 생각은 없습니다. (세부 사항을 실현하는 것은 사실상 저희 테스트에 중요하지 않습니다.) 그러나 PostsService 저희 상태를 저장하고 POST 데이터를 불러오고 필터하는 것을 책임집니다.
    그러나 이 프로그램은 HTTP 요청을 보내기 위해 DataService를 사용했다는 점을 지적해야 한다.어쨌든, HTTP 요청을 보내는 단독 서비스를 만드는 것은 최선의 실천이지만, 시뮬레이션 서비스가 시뮬레이션 AngularHTTPClient보다 간단하기 때문에 테스트를 더욱 쉽게 할 수 있다는 것을 알았다.
    다음은 DellPostsComponent입니다.
    export class PostsComponent implements OnInit {
      searchTermControl = new FormControl('');
      userFilterControl = new FormControl(null);
      posts$: Observable<Post[]>;
      loading$: Observable<boolean>;
    
      constructor(private service: PostsService) {}
    
      ngOnInit(): void {
        // request the posts data
        this.service.load().subscribe();
    
        // when the searchterm or the user filter changes get the filtered posts based on the filter
        this.posts$ = combineLatest(
          this.searchTermControl.valueChanges.pipe(
            debounceTime(300),
            startWith('')
          ),
          this.userFilterControl.valueChanges.pipe(startWith(null))
        ).pipe(
          switchMap(([searchTerm, userId]) => {
            return this.service.getPosts(searchTerm, userId);
          })
        );
    
        // loading state observable
        this.loading$ = this.service.loading$;
      }
    }
    

    테스트 설정


    우선, 우리는 우리의 프로젝트에 관중을 설치해야 한다.
    npm install @ngneat/spectator --save-dev
    
    네가 Jasmine을 사용하든 Jest를 사용하든'방관자'는 모두 상자를 열면 바로 사용할 수 있고 별도의 설정이 없다. 그러나 이 예에 대해 나는 Jasmine를 계속 사용할 것이다.posts.component.spec.ts에서 우리는 먼저 방관자createComponentFactory 기능 설정 테스트를 사용했다.여기서, 우리는 시청자들에게 우리가 어떤 구성 요소를 테스트하고 있는지 알려주고, 구성 요소에 필요한 모든 모듈을 가져옵니다.우리의 예에서 구성 요소는 FormsModuleReactiveFormsModule에 의존하기 때문에 imports 그룹에 추가합니다.
    여기에는 사용자가 어떻게 설정할 것인지 ngModule 와 같이 우리가 제공해야 할 모든 하위 구성 요소나 서비스도 설명하지만, 이 간단한 예에는 하위 구성 요소나 서비스가 없습니다.
    // posts.component.spec.ts
    describe('PostsComponent', () => {
      const createComponent = createComponentFactory({
        component: PostsComponent,
        imports: [FormsModule, ReactiveFormsModule],
        mocks: [DataService],
        detectChanges: false
      });
    
      ...
    });
    
    통합 테스트를 작성할 때, 우리는 어떤 구성 요소와 서비스가 테스트에 포함되고, 어떤 것이 시뮬레이션될지 결정해야 한다.이 예에서 나는 나의 테스트가 PostsComponentPostsService를 포괄하기를 바란다.테스트 DataService 를 하고 싶지 않기 때문에, 모크스 그룹에 추가합니다.이렇게 하면 방관자는 자동으로 시뮬레이션DataService을 하고 각 기능을 Jasmine spy(jasmine.createSpy()로 변환합니다.
    나는 ngOnInit 직접 도망가고 싶지 않다. 왜냐하면 나는 먼저 DataService에 어떻게 fetch 응답을 모의하는지 알려줘야 하기 때문에 detectChangesfalse로 설정했다.

    첫 번째 테스트 쓰기


    이 설정이 있으면 우리는 첫 번째 테스트를 작성할 수 있다!다음 사항을 테스트합니다.
  • 처음에 우리는 진도표를 보였는데 댓글이 없었다
  • 처음에 사용자 드롭다운 목록이 전체
  • 로 설정됨
  • API로부터 게시물 데이터를 받으면 페이지에 게시물이 표시되고 진행률 표시줄이 표시되지 않습니다
  • 다음은 테스트입니다.
    it('should load a list of posts for all users by default', fakeAsync(() => {
      // create the test component
      const spectator = createComponent();
    
      // get the mocked instance of the DataService
      const dataService = spectator.get(DataService);
    
      // mock the fetch function to wait 100ms and return 2 posts
      dataService.fetch.and.returnValue(
        timer(100).pipe(
          mapTo([
            {
              userId: 1,
              id: 1,
              title: 'First Post'
            },
            {
              userId: 2,
              id: 2,
              title: 'Another Post'
            }
          ])
        )
      );
    
      // run ngOnInit
      spectator.detectChanges();
    
      // assert that the progress bar is showing
      expect(spectator.query(MatProgressBar)).toExist();
      expect(spectator.query(byText('First Post'))).not.toExist();
    
      // get the user select element
      const select = spectator.query(
        byLabel('Filter by user')
      ) as HTMLSelectElement;
    
      // assert that it is showing 'All' by default
      expect(select).toHaveSelectedOptions(
        spectator.query(byText('All')) as HTMLOptionElement
      );
    
      // advance the time 100ms to simulate the HTTP request being made
      spectator.tick(100);
    
      // assert that the progress bar is not showing and that both our posts are showing
      expect(spectator.query(MatProgressBar)).not.toExist();
      expect(spectator.queryAll(MatListItem).length).toEqual(2);
      expect(spectator.query(byText('First Post'))).toExist();
    
      expect(dataService.fetch).toHaveBeenCalledTimes(1);
    }));
    
    좋아, 여기 많은 일이 생겼어.
    먼저 AngularfakeAsync 영역에서 테스트를 수행합니다.이것은 우리가 시간의 흐름을 제어함으로써 동기화 방식으로 비동기 코드를 쉽게 테스트할 수 있도록 한다.

    If you want to understand more about testing asynchronous code in Angular, I thoroughly recommend checking out this post by Netanel Basel.


    이전에 만들어진 공장에서 테스트 구성 요소를 만든 후에 나는 fetch에서 DataService 방법을 시뮬레이션했다.나는 HTTP 요청을 시뮬레이션하기 위해 100밀리 초를 기다린 다음 일련의 댓글을 보낼 observable로 되돌아오라고 말했다.
    이 시뮬레이션이 있어서 나는 spectator.detectChanges()을 호출했는데 이것은 구성 요소에서 실행하는 것과 같다ngOnInit().
    이 점에서 진도표가 존재한다고 단언하고 싶습니다. 제 게시물이 나타나지 않았고 사용자 밑에 있는 목록에'모두'가 표시되었습니다. 저는 방관자의 사용자 정의 매칭기toHaveSelectedOptions를 사용하여 이 점을 쉽게 실현할 수 있습니다.
    그리고 저는 spectator.tick(100)를 사용하여 타이머를 100밀리초 앞당겼습니다. 이때fetch 방법은 게시물 목록을 되돌려줍니다.
    마지막으로 나는 진도표가 이미 존재하지 않는다고 단언할 수 있다. 이 두 게시물도 존재한다.나는 또 fetch 방법이 단지 한 번 호출된 적이 있다고 단언했다.
    이 테스트에 대한 참고 사항:
  • 우선, 나는 어떤 실현 세부 사항도 테스트하지 않았다.이 예에서 나는 하나PostsComponent와 하나PostsService로 나의 응용 프로그램을 설정했지만, 나의 응용 프로그램이 증가함에 따라, 나는 그것을 재구성하여 여러 개의 구성 요소나 다른 상태 관리 방법을 사용하기를 원할 수도 있다. 예를 들어 하나의 상태 관리 라이브러리를 포함한다.이런 방법의 장점은 내가 코드를 재구성할 수 있다는 데 있다. 나의 해결 방안이 여전히 호출DataService된다면 나의 테스트는 변경할 필요가 없다!😄
  • 그 다음으로 나는 여러 가지 주장을 제기했다.이렇게 함으로써 나는 테스트 사이에서 상태를 공유하는 것을 피했다. 이것은 위험할 수도 있다.더 중요한 것은 모든 단언을 위한 단독 테스트를 작성할 필요가 없다는 것이다. 왜냐하면 현대 테스트 운영자들은 우리에게 테스트에 실패한 부분을 정확하게 알려줄 수 있기 때문이다.
  • 전체적으로 말하자면 나의 테스트는 실제 사용자가 응용 프로그램을 어떻게 사용하는지, 또는 수동 테스트 인원이 이 기능을 어떻게 테스트하는지와 매우 유사하기 때문에 나는 나의 코드에 대해 자신감을 가질 수 있다.방관자의 DOM 선택기(예를 들어 spectator.query(byLabel('Filter by user'))를 사용하면 이 점을 실현할 수 있다. 이것은 실제 사용자가 요소를 찾는 방식이지 요소id를 통해 찾는 방식이 아니기 때문이다.그것은 또한 접근성을 시험하는 방향으로 은밀하게 발전하고 있다.
  • 게시물 필터링


    우리는 여전히 댓글을 필터링하는 테스트를 해야 하기 때문에 마지막으로 몇 가지 예를 보고'방관자'가 얼마나 간단한지 봅시다.
    사용자별로 필터링할 때, 내 프로그램은 선택 입력을 사용합니다.이를 위해, 나는 관중 selectOption 매칭기를 사용하여 나의 옵션을 선택할 수 있다.
    const select = spectator.query(
      byLabel('Filter by user')
    ) as HTMLSelectElement;
    
    spectator.selectOption(
      select,
      spectator.query(byText('User 2')) as HTMLOptionElement
    );
    
    selectOption도 나중에 detectChanges()하여 샘플 파일을 더 줄일 수 있습니다.👍
    검색어 필터를 테스트할 때 사용자 입력을 시뮬레이션해야 합니다.이를 위해 나는 typeInElement 조수를 사용할 수 있다.
    const input = spectator.query(byLabel('Filter by title'));
    
    spectator.typeInElement('first', input);
    
    spectator.tick(300);
    
    검색 입력 변경에 300밀리초의 온스 제거 시간이 있기 때문에, 나는 spectator.tick(300) 타이머를 300밀리초 앞당겨 실행해야 한다. (테스트는 반드시 fakeAsync 구역에서 실행해야 앞에서 논의한 것처럼 작동할 수 있다.)

    결론


    방관자를 사용하여 간단하고 읽기 쉬운 통합 테스트를 작성하는 방법을 보여 드리겠습니다. 이것은 사용자의 상호작용과 결과를 주목하는 것이지, 세부 사항을 실현하는 것이 아니라, 코드가 최종 사용자가 예상한 대로 작동할 것이라고 믿게 하는 것입니다.
    전체 코드는 GitHub 또는 StackBlitz를 참조하십시오.

    리소스


    켄트 C 도드 - Write Fewer Longer Tests
    켄트 C 도드 - Write tests. Not too many. Mostly integration
    Netanel Basel-Testing Asynchronous Code in Angular Using FakeAsync

    좋은 웹페이지 즐겨찾기