Android 프로젝트를 기반으로 한 단위 테스트 요약 공유

머리말:

회사의 단위 테스트 시스템 구축을 담당하며 초기 프레임 워크 연구부터 전 직원의 중기 교육 및 수십 개의 프로젝트에 단위 테스트를 도입 및 홍보하기까지 약 1 ~ 2 개월 나중에 Android에 대한 큰 공헌으로 간주될 수 있습니다.단위 테스트에는 일부 예비 이득과 일부 새로운 인식이 있으므로 이 기사를 작성하여 기록 및 요약을 작성하십시오.

다음 내용은 모두 순전히 개인적인 의견이므로 토론을 환영합니다.

1. 단위 테스트 표준

1. 테스트 치수

단위 테스트에는 기능 점수 차원 또는 메서드 차원과 같은 많은 차원이 있습니다. 그렇다면 프로젝트에 대해 이 차원을 어떻게 정의해야 할까요?

안드로이드 소스코드 프로젝트에서 단위 테스트는 브로드캐스트를 보내는 기능을 검증하는 등의 기능 포인트 차원에서 작성하고 검증은 브로드캐스트 리시버가 알림을 수신하는지 여부입니다. 이 프로세스에는 시스템 측에 브로드캐스트 전송, 시스템 측에서 수신 및 처리, 시스템 측에서 애플리케이션에 알림, 수신자에게 애플리케이션 배포의 네 단계가 포함됩니다. Android 시스템의 경우 브로드캐스트가 전송된 후 브로드캐스트가 수신되는지 확인할 수 있어야 하지만 시스템 측 코드에 문제가 있는 경우 전체 프로세스가 작동하지 않습니다. Android 프로젝트의 경우 많은 Point에 Native Object를 사용할 수 없고 Mock Object를 사용해야 하지만 프로젝트의 Coupling 정도가 높아지고 Link 수가 증가함에 따라 Mock이 필요한 Object와 Verification Point가 폭발적으로 증가하여 결과적으로 나중에 단위 테스트 방법의 비용은 기하급수적으로 증가할 것입니다.

물론 기능 포인트를 차원으로 삼는 것도 장점이 있습니다.단위 테스트가 실패하면 프로세스의 한 부분에 문제가 있음을 의미하며 문제를 노출하기가 더 쉽습니다.

방법을 차원으로 사용하면 종속성 및 결합 문제는 없지만 적용 범위는 훨씬 작아집니다. 그렇다면 기능점을 차원으로 삼아야 할까요, 방법을 차원으로 삼아야 할까요?

제 생각에는 이것은 궁극적으로 프로젝트의 구조와 형식에 달려 있습니다. UI 수준의 프로젝트라면 프로젝트 복잡도가 상대적으로 가볍거나 다양한 프레임워크를 사용하여 뷰 바인딩을 완성하는데 이러한 종류의 프로젝트는 당연히 기능 점수 차원에 대한 단위 테스트를 작성하는 데 적합합니다. 그러나 프로젝트 결합도가 상대적으로 높고 복잡도가 높은 경우 방법 기반 차원을 선택하고 모의 객체를 사용하여 결합된 부분을 잘라야 합니다.

2. 커버리지

기능 점수: 핵심 기능 점수

첫째, 단위 테스트는 모든 핵심 기능 포인트를 다루어야 합니다. 메서드를 검증할 때 onCreate에 대한 단위 테스트를 작성할 때와 같이 Activity의 onCreate 메서드에 있는 특정 콘텐츠와 같이 메서드의 모든 지점을 확인해야 하는 것은 아닙니다. initView/initListener/init의 세 가지 메서드가 실행되는지 확인합니다. 사실 말도 안 된다.이 세 가지 방법을 개별적으로 검증하기 위해 단위 테스트 코드를 작성해야 한다. 이것이 우리의 비즈니스 로직 포인트이다.

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      initView();
      initListener();
      init();
  }

또한 특정 구현 로직 메서드를 확인해야 합니다. 일부 전송 메서드의 경우 확인하지 않아야 합니다. 예를 들어 ActivityThread의 ApplicationThread 클래스는 확인하지 않아야 합니다. ApplicationThread의 일부 코드 참조:

private class ApplicationThread extends IApplicationThread.Stub {
      public final void scheduleReceiver(Intent intent, ActivityInfo info,
          CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
          boolean sync, int sendingUser, int processState) {
          updateProcessState(processState, false);
          ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                  sync, false, mAppThread.asBinder(), sendingUser);
          r.info = info;
          r.compatInfo = compatInfo;
          sendMessage(H.RECEIVER, r);
      }

      public final void scheduleCreateBackupAgent(ApplicationInfo app,
              CompatibilityInfo compatInfo, int backupMode, int userId, int operationType) {
          CreateBackupAgentData d = new CreateBackupAgentData();
          d.appInfo = app;
          d.compatInfo = compatInfo;
          d.backupMode = backupMode;
          d.userId = userId;
          d.operationType = operationType;

          sendMessage(H.CREATE_BACKUP_AGENT, d);
      }
      ...
  }

3. 보장 요건

단위 테스트는 클래스, 메소드 및 라인에 대한 커버리지 비율을 갖게 됩니다.대부분의 회사는 라인 코드 커버리지에 중점을 두고 목표를 90% 또는 95%의 높은 목표로 설정합니다.

개인적으로 적어도 Android 프로젝트의 경우 이러한 높은 커버리지 비율은 실제로 바람직하지 않습니다. 예를 들어, 위의 예에서 Android 소스 코드의 단위 테스트 클래스 ActivityThreadTest에서 ApplicationThread의 내용에 대한 검증이 없습니다. 실행 가능한 논리가 없습니다. 단위 테스트 기능의 관점에서 볼 때 어떤 기능도 충족하지 않으므로 이러한 종류의 코드에 대해 단위 테스트를 작성하는 것은 의미가 없습니다.

따라서 단위 테스트는 모든 논리 처리 코드를 다루어야 합니다. 그렇게 한다면 ApplicationThread를 별도의 클래스로 추출하고 계산되지 않도록 단위 테스트가 필요하지 않은 것으로 표시할 것입니다.

이 시나리오에서는 라인 커버리지 목표를 85%로 설정해야 한다고 생각합니다. 다루지 못하는 부분은 별도의 클래스로 추출하기 불편한 부분, 상수를 정의하는 부분 등이다.

4. 명명 규칙

단위 테스트도 코드 작성이며 코드 작성에는 특정 사양이 있어야 합니다.

Android 소스 코드의 단위 테스트 클래스를 참조하면 다음 체계에 따라 단위 테스트의 명명 규칙을 공식화하는 것이 더 적절합니다.

1) 단위 테스트 클래스는 테스트할 클래스에 해당하며 테스트할 클래스 뒤에 Test를 추가하면 해당 클래스의 단위 테스트를 나타냅니다.

예를 들어 테스트된 클래스가 ActivityA인 경우 단위 테스트 클래스의 이름은 ActivityATest입니다.

2) 단위 테스트 클래스가 메서드를 기반으로 하는 경우 해당 테스트 메서드의 이름은 테스트+테스트 메서드로 지정됩니다.

예를 들어 테스트할 메서드가 methodA인 경우 테스트 메서드는 testMethodA(카멜 표기법으로 이름 지정)입니다.

테스트 방법은 여러 소스 방법을 다룰 수 있으며 마찬가지로 소스 방법을 검증을 위해 여러 테스트 방법으로 분할할 수도 있습니다.

3) 단위 테스트 클래스가 기능 점수를 기반으로 하는 경우 해당 테스트 방법의 이름은 테스트+해당 기능 점수입니다.

예를 들어 브로드캐스트를 수신자에게 보낼 수 있는지 여부를 확인하려면 메서드 이름이 testResult입니다.

2. 단위 테스트의 역할

1. 단위 테스트는 프로젝트 품질을 효과적으로 향상시킬 수 없습니다.

통합 테스팅이나 심지어 블랙박스 테스팅을 대체하기 위해 단위 테스팅을 사용하는 회사도 있다는 것을 알게 되었지만 개인적으로는 권장하지 않는다고 생각합니다. 아마도 이들 회사의 단위 테스트 범위는 기능 테스트의 일부를 커버했지만 단위 테스트는 검증의 모든 메서드 수준 기능 포인트 이후입니다. 나쁜 영향.

단위 테스트는 메서드의 입력 및 출력 항목을 보장합니다.프로젝트에서 새로운 요구 사항을 개발하거나 리팩터링할 때 단위 테스트를 통해 원래 프로젝트의 영향 지점을 빠르게 식별할 수 있습니다.이것이 단위 테스트가 보장해야 하는 것입니다.

2. 새로운 기능의 영향을 빠르게 파악

새 변경 사항이 메서드의 원래 논리에 영향을 미치면 이전 단위 테스트가 작동하지 않습니다. 이때 요구사항에 맞게 단위 테스트 케이스를 수정할지, 새로 작성한 로직에 문제가 있는지 판단해야 한다.

3. 숨겨진 문제 찾기

이는 단위 테스트를 구현하는 동안 예상치 못한 이점이기도 합니다. 특정 페이지 진입 후 refreshData 메소드를 두 번 호출하는데, 두 호출의 로직이 일관성이 있기 때문에 성능만 보면 이 메소드가 두 번 호출되는지는 알 수 없다.

그러나 단위 테스트 검증을 통해 이때 검증 메소드 실행이 1회인지 여부는 해당 메소드가 호출된 횟수가 1회가 아님을 검증하고 다중 호출 문제는 전혀 발견되지 않는다.

//被检测的类型
  public class MVPPresenter implements IMVPActivityContract.IMainActivityPresenter {
      //这个方法被调用了多次
      @Override
      public void requestInfo() {
          //请求数据,订阅,并显示
          Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;
          Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();
          Disposable disposable = observable
                  .subscribeOn(Schedulers.io())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(consumer);
      }
  }

  //单元测试类
  public class MVPActivityTest {
      @Test
      public void testInit() {
          ...
          MVPPresenter mockPresenter = mock(MVPPresenter.class);
          ...
          //验证requestInfo方法被调用的此时是否是1次
          verify(mockPresenter, times(1)).requestInfo();
      }
  }

4. 프로젝트에서 커플링을 해결하도록 촉구합니다.

단위 테스트는 프로젝트 결합의 좋은 척도가 될 수 있습니다. 회사 프로젝트의 단위 테스트 시스템 구축을 담당할 때 많은 프로젝트 담당자와 소통했는데 많은 사람들이 단위 테스트 케이스 작성이 매우 어렵다고 말했습니다. 조사를 해보니 예외 없이 모두 너무 높은 결합이 원인이었습니다. 개별적으로 실행해야 하는 많은 기능과 링크를 하나의 방법으로 결합합니다.

예를 들어 BroadcastReceiver의 onReceive 메소드에서 매개변수의 수신 로직을 실행할 뿐만 아니라 onReceive 메소드에서 후속 논리 연산 로직을 작성하고 일부는 여기에 새 스레드를 생성하여 관련 로직을 실행하기도 합니다. 커플링, 단위 테스트 메서드는 작성하기 어려워야 합니다. 반대로 프로젝트의 결합도가 매우 낮으면 단위 테스트 작성이 쉬워집니다.

5. 방법의 객관적인 평가

우리는 종종 순환 복잡도라는 개념을 언급하는데, 방법 내에서 순환 복잡도를 측정하기 위해 단위 테스트가 좋은 지표입니다. 순환 복잡도가 높을수록 단위 테스트 코드에 더 많은 경우가 있습니다. 그런 종류의 방법은 매우 짧지만 검증 사례가 많기 때문에 순환 복잡도가 매우 높은 경우가 많습니다.

따라서 단위 테스트 코드를 읽으면 순환 복잡도를 쉽게 측정할 수 있습니다.

6. 보충 설명

일부 기사에서는 단위 테스트를 최고의 주석이라고 부릅니다. 메서드 주석보다 더 자세한 방법의 입력과 출력을 시각적으로 표시할 수 있기 때문입니다. 하지만 제 생각에는 주석과 단위 테스트는 나름의 장점이 있어야 합니다.방법의 경우 단위 테스트의 도입이 주석의 도입보다 명확하지만 읽기 비용도 증가합니다. 그래서 주석에 대한 보충이라고 부르는 것을 선호합니다.

검토용이거나 프로젝트에 대한 초기 이해를 위한 것이라면 주석으로 충분합니다.

그러나 소스 코드를 읽고 이를 기반으로 추가 변환을 수행하려면 단위 테스트가 좋은 도구이기 때문에 원래 방법을 깊이 이해해야 합니다.

예를 들어 Android 소스 코드에는 ContextImpl의 고정 브로드캐스트 sendStickyBroadcast에 대한 소개가 많이 있지만 단위 테스트 방법과 대조됩니다.

public void testSetSticky() throws Exception {
      Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
      intent.putExtra("test", LaunchpadActivity.DATA_1);
      ActivityManager.getService().unbroadcastIntent(null, intent,
              UserHandle.myUserId());

      ActivityManager.broadcastStickyIntent(intent, UserHandle.myUserId());
      addIntermediate("finished-broadcast");

      IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1);
      Intent sticky = getContext().registerReceiver(null, filter);
      assertNotNull("Sticky not found", sticky);
      assertEquals(LaunchpadActivity.DATA_1, sticky.getStringExtra("test"));
  }

단일 테스트 코드를 통해 스티키 브로드캐스트가 먼저 전송하고 등록 후 수신할 수 있는 브로드캐스트임을 명확하게 알 수 있습니다. 

마지막으로 제 글을 읽어주신 모든 분들께 감사드립니다 호혜는 항상 필요합니다 아주 귀한 것은 아니지만 필요하시면 가져가셔도 됩니다:

이 재료는 [소프트웨어 테스팅] 친구를 위한 가장 포괄적이고 완전한 준비 창고가 될 것입니다.이 창고는 또한 수만 명의 테스트 엔지니어와 함께 가장 어려운 여정을 통과했으며 도움이 되기를 바랍니다! 파트너는 아래 작은 카드를 클릭할 수 있습니다. 받다  

추천

출처blog.csdn.net/OKCRoss/article/details/131416751