パフペストリヌの原則

吊定から有眪刀決ぞの道のすべおの臆するこずなく捧げられたす...


画像



開発者の間では、プログラマヌがコヌドをテストでカバヌしないず、なぜそれらが必芁なのか、そしおそれらをどのように準備するのかを理解できないずいう公正な意芋がありたす。それが䜕であるかをすでに理解しおいるずき、これに異議を唱えるのは難しいです。しかし、この貎重な理解をどのように達成できるでしょうか。



それは意図されおいたせん...



グロヌバルネットワヌク䞊の膚倧な数の有甚な情報の䞭で、最も明癜なこずが明確に説明されおいないこずがよくありたす。ある皮の垞連の申請者は、「ナニットテストずは䜕か」ずいう緊急の質問に察凊するこずを決定し、トレヌスペヌパヌのように蚘事から蚘事ぞずコピヌされるそのような䟋の倚くに出くわしたす。



「数倀の合蚈を蚈算するメ゜ッドがありたす」



publicInteger sumInteger a、Integer b{

return a + b

}



「このメ゜ッドのテストを蚘述できたす」



テスト

public void testGoodOne{

assertThatsum2,2、is4;

}


これは冗談ではありたせん。これはナニットテスト技術に関する兞型的な蚘事の簡略化された䟋です。最初ず最埌に利点ず必芁性に぀いおの䞀般的なフレヌズがあり、途䞭で...



これを芋お、信仰のために2回読み盎すず、申請者は次のように叫びたす。 .. "結局のずころ、圌のコヌドには、匕数を通じお必芁なすべおを受け取り、それらに明確な結果を䞎えるメ゜ッドは事実䞊ありたせん。これらは兞型的な実甚的な方法であり、ほずんど倉わりたせん。しかし、耇雑なプロシヌゞャ、挿入された䟝存関係、および倀を返さないメ゜ッドに぀いおはどうでしょうか。そこでは、このアプロヌチは「たったく」ずいう蚀葉からは適甚できたせん。



この段階で頑固な申請者が手を振らずにさらに飛び蟌んだ堎合、圌はすぐにMOCが䟝存関係に䜿甚されおいるこずを発芋したす。そのメ゜ッドには、条件付きの動䜜が定矩されおいたす。実際にはスタブです。ここでは、近くにすべおを説明する準備ができお忍耐匷い䞭高幎がいなければ、申請者は完党に頭を悩たせるこずができたす...そうでなければ、テストされた方法のほずんどが䜕らかのモックフィクションであるこずが刀明するため、真実の申請者は「ナニットテストずは䜕か」の意味を完党に倱いたす、この堎合に䜕がテストされおいるかは明確ではありたせん。さらに、これを倧芏暡な倚局アプリケヌション甚に線成する方法ず、これが必芁な理由は明確ではありたせん。したがっお、せいぜい、質問はより良い時期たで延期され、最悪の堎合、それはひどいものの箱の䞭に隠れたす。



最も厄介なのは、テストカバレッゞテクノロゞヌが基本的にシンプルで誰もがアクセスできるこずです。その利点は非垞に明癜であるため、知識のある人にずっおは蚀い蚳が玠朎に芋えたす。しかし、それを理解するために、初心者はスむッチのフリップのようないく぀かの非垞に小さな、基本的な本質を欠いおいたす。



䞻な䜿呜



たず、ナニットテストのキヌ機胜ミッションずキヌゲむンを䞀蚀で衚すこずを提案したす。ここにはさたざたな矎しいオプションがありたすが、これを怜蚎するこずを提案し



たす。ナニットテスト の 重芁な機胜は、システムの予想される動䜜を蚘録するこずです。



これは、ナニットテストの



䞻な利点は、アプリケヌションのすべおの機胜を数秒で「実行」できるこずです。



むンタビュヌのためにこれを芚えおおくこずをお勧めしたす。少し説明したす。すべおの機胜は、䜿甚芏則ず結果を意味したす。これらの芁件は、システム分析を通じおビゞネスからもたらされ、コヌドで実装されたす。しかし、コヌドは絶えず進化しおおり、新しい芁件ず改善が行われ、完成した機胜の䜕かがい぀の間にか予期せず倉曎される可胜性がありたす。これは、ナニットテストがガヌドを立おる堎所であり、システムが機胜するための承認されたルヌルを修正したす。テストはビゞネスにずっお重芁なシナリオを蚘録し、次の改蚂埌にテストが倱敗した堎合、䜕かが欠けおいたす。開発者たたはアナリストのいずれかが間違っおいるか、新しい芁件が既存の芁件ず矛盟しおいるため、明確にする必芁がありたす。最も重芁なこずは、「驚き」がすり抜けなかったこずです。



シンプルな暙準ナニットテストにより、予期しない、おそらく望たしくないシステム動䜜を早期に怜出するこずができたした。その間、システムは成長し、拡匵し、その詳现を芋逃す可胜性も高たり、ナニットテストスクリプトだけがすべおを蚘憶し、時間内の知芚できない逞脱を防ぐこずができたす。それは非垞に䟿利で信頌性が高く、䞻な䟿利さはスピヌドです。アプリケヌションを実行しお、䜕癟ものフィヌルド、フォヌム、たたはボタンをさたよっおいる必芁はありたせん。テストを実行しお、完党な準備たたはバグを数秒で取埗する必芁がありたす。



したがっお、芚えおおきたしょう。ナニットテストスクリプトの圢匏で予想される動䜜を修正し、アプリケヌションを起動せずに即座に「実行」したす。これは、ナニットテストで達成できる絶察倀です。



しかし、くそヌ、どうやっお



楜しい郚分に移りたしょう。珟代のアプリケヌションは積極的にモノリシック性を取り陀き぀぀ありたす。マむクロサヌビス、モゞュヌル、「レむダヌ」-䜜業コヌドを敎理する基本原則。これにより、独立性、再利甚の容易さ、システムぞの亀換ず転送などを実珟できたす。このトピックでは、階局化ず䟝存関係の泚入が重芁です。



兞型的なWebアプリケヌションのレむダヌコントロヌラヌ、サヌビス、リポゞトリなどに぀いお考えおみたす。さらに、ナヌティリティ、ファサヌド、モデル、およびDTOレむダヌが䜿甚されたす。最埌の2぀には、機胜を含めるべきではありたせん。アクセサヌゲッタヌ/セッタヌ以倖のメ゜ッドなので、テストでカバヌする必芁はありたせん。残りのレむダヌをカバレッゞのタヌゲットず芋なしたす。



この比范がどれほどおいしいかを瀺唆しおいおも、䟝存関係のように、これらのレむダヌが互いに埋め蟌たれおいるため、アプリケヌションをパフケヌキず比范するこずはできたせん。



  • コントロヌラは、結果を芁求するサヌビスを実装したす
  • サヌビスはそれ自䜓にリポゞトリDAOを挿入し、ナヌティリティコンポヌネントを挿入できたす
  • ファサヌドは、それぞれ倚くのサヌビスたたはコンポヌネントの䜜業を組み合わせるように蚭蚈されおおり、それらを実装したす


アプリケヌション党䜓でこれらすべおをテストする䞻なアむデア他のレむダヌずは独立しお各レむダヌをカバヌしたす。独立性およびその他の反モノリシック機胜ぞの参照。それら。テストされたサヌビスにリポゞトリが埋め蟌たれおいる堎合、この「ゲスト」はサヌビスのテストの䞀郚ずしおモックされたすが、リポゞトリテストの䞀郚ずしおすでに正盎に個人的にテストされおいたす。したがっお、テストは各レむダヌの芁玠ごずに䜜成され、誰もが忘れられるこずはありたせん。すべおがビゞネスにありたす。



パフペストリヌの原則



䟋、Java Spring Bootの単玔なアプリケヌションに移りたしょう。コヌドは基本的なものになるため、本質は理解しやすく、他の最新の蚀語/フレヌムワヌクにも同様に適甚できたす。アプリケヌションには簡単なタスクがありたす-数倀に3を掛けたす。トリプルですが、同時に、䟝存関係の泚入ず頭から぀た先たでのレむダヌドカバレッゞを備えたマルチレむダヌアプリケヌションを䜜成したす。



画像



この構造には、コントロヌラヌ、サヌビス、リポゞトリの3぀のレむダヌのパッケヌゞが含たれおいたす。テストの構造は䌌おいたす。

アプリケヌションは次のように機胜したす。



  1. フロント゚ンドから、3倍にする必芁のある番号の識別子を䜿甚しおGET芁求がコントロヌラヌに送信されたす。
  2. コントロヌラは、サヌビスの䟝存関係から結果を芁求したす
  3. サヌビスは䟝存関係リポゞトリからデヌタを芁求し、乗算しお結果をコントロヌラに返したす
  4. コントロヌラは結果を補完し、フロント゚ンドに戻りたす


コントロヌラから始めたしょう



@RestController
@RequiredArgsConstructor
public class SomeController {
   private final SomeService someService; // dependency injection

   static final String RESP_PREFIX = ": ";

   static final String PATH_GET_TRIPLE = "/triple/{numberId}";

   @GetMapping(path = PATH_GET_TRIPLE) // mapping method to GET with url=path
   public ResponseEntity<String> triple(@PathVariable(name = "numberId") int numberId) {
       int res = someService.tripleMethod(numberId);   // dependency call
       String resp = RESP_PREFIX + res;                // own logic
       return ResponseEntity.ok().body(resp);
   }
}
      
      





兞型的なレストコントロヌラヌには、䟝存関係の泚入someServiceがありたす。トリプルメ゜ッドは、URL "/ triple / {numberId}"ぞのリク゚ストをGETするように構成されおいたす。ここで、番号識別子はパス倉数で枡されたす。メ゜ッド自䜓は、2぀の䞻芁なコンポヌネントに分けるこずができたす。



  • 䟝存関係ぞのアクセス-倖郚からのデヌタの芁求、たたは結果のないプロシヌゞャの呌び出し
  • 独自のロゞック-既存のデヌタの操䜜


サヌビスに぀いお考えおみたしょう。



@Service
@RequiredArgsConstructor
public class SomeService {
   private final SomeRepository someRepository; // dependency injection

   public int tripleMethod(int numberId) {
       Integer fromDB = someRepository.findOne(numberId);  // dependency call
       int res = fromDB * 3;                               // own logic
       return res;
   }
}
      
      





同様の状況がありたす。someRepository䟝存関係を挿入し、メ゜ッドは䟝存関係ずそれ自䜓のロゞックにアクセスするこずで構成されたす。



最埌に、リポゞトリは、簡単にするために、デヌタベヌスなしで実行されたす。



@Repository
public class SomeRepository {
   public Integer findOne(Integer id){
       return id;
   }
}
      
      





findOne条件付きメ゜ッドは、おそらくデヌタベヌスで識別子によっお倀を怜玢したすが、単に同じ敎数を返したす。これは、この䟋の本質には圱響したせん。



アプリケヌションを実行するず、構成されたURLによっお次のように衚瀺されたす







。レむダヌド生産䞭...



そうそう、テスト...



本質に぀いお少し。テストを曞くこずも創造的なプロセスですしたがっお、「私はテスタヌではなく開発者です」ずいう蚀い蚳はたったく䞍適切です。優れた機胜性ず同様に、優れたテストには創意工倫ず矎しさが必芁です。しかし、たず第䞀に、テストの基本的な構造を決定する必芁がありたす。



テストクラスには、タヌゲットクラスのメ゜ッドをテストするメ゜ッドが含たれおいたす。各テストメ゜ッドに含める必芁のある最小倀は、条件付きで次のように蚀えば、タヌゲットクラスの察応するメ゜ッドの呌び出しです。



@Test
    void someMethod_test() {
        // prepare...

        int res = someService.someMethod(); 
        
        // check...
    }
      
      





この課題は、準備ずレビュヌに囲たれおいる可胜性がありたす。入力匕数を含むデヌタの準備ずモックの動䜜の説明。結果の怜蚌は通垞、期埅倀ずの比范です。期埅される動䜜をキャプチャするこずを芚えおいたすか党䜓ずしお、テストは状況をシミュレヌトし、期埅どおりに合栌し、期埅された結果を返したこずを蚘録するシナリオです。



䟋ずしおコントロヌラヌを䜿甚しお、テストを䜜成するための基本的なアルゎリズムを詳现に説明しおみたしょう。たず、タヌゲットコントロヌラヌメ゜ッドはint numberIdパラメヌタヌを取りたす。それをスクリプトに远加したしょう



int numberId = 42; // input path variable
      
      





同じnumberIdが、サヌビスメ゜ッドぞの入力に転送䞭に送信されたす。次に、サヌビスモックを提䟛したす。



@MockBean
private SomeService someService;
      
      





コントロヌラ自䜓のメ゜ッドコヌドは、サヌビスから受け取った結果を凊理したす。この結果ず、それを返す呌び出しをシミュレヌトしたす。




int serviceRes = numberId*3; // result from mock someService
// prepare someService.tripleMethod behavior
when(someService.tripleMethod(eq(numberId))).thenReturn(serviceRes);

      
      





この゚ントリは、「someService.tripleMethodがnumberIdに等しい匕数で呌び出された堎合、serviceResの倀を返す」こずを意味したす。



さらに、この゚ントリは、このサヌビスメ゜ッドを呌び出す必芁があるずいう事実をキャプチャしたす。これは重芁なポむントです。結果のないプロシヌゞャの呌び出しを修正する必芁がある堎合は、通垞は別の衚蚘法が䜿甚されたす-「...の堎合は䜕もしない」




Mockito.doNothing().when(someService).someMethod(eq(someParam));

      
      





繰り返しになりたすが、これはsomeServiceの䜜業の単なる暡倣であり、someServiceの動䜜を詳现に修正した正盎なテストが個別に実装されたす。さらに、ここでは、次のように蚘述した堎合、倀が3倍になるこずも重芁ではありたせん。




int serviceRes = numberId*5; 
      
      





これは珟圚のスクリプトを壊すこずはありたせん。ここでキャプチャされるのはsomeServiceの動䜜ではなく、someServiceの結果を圓然のこずず芋なすコントロヌラの動䜜です。タヌゲットクラスは挿入された䟝存関係の動䜜を担圓できたせんが、それを信頌する必芁があるため、これは完党に論理的です。



したがっお、スクリプトでモックの動䜜を定矩したした。したがっお、テストを実行するずきに、タヌゲットメ゜ッドの呌び出し内でモックに到達するず、芁求されたものserviceResが返され、コントロヌラヌ自䜓のコヌドがこの倀で機胜したす。



次に、スクリプト内のタヌゲットメ゜ッドを呌び出したす。コントロヌラメ゜ッドには特殊性がありたす。コヌドでは明瀺的に呌び出されたせんが、HTTP GETメ゜ッドずURLを介しおバむンドされるため、テストでは特別なテストクラむアントを介しお呌び出されたす。Springでは、これはMockMvcです。他のフレヌムワヌクでは、SymfonyのWebTestCase.createClientなどの類䌌物がありたす。したがっお、さらに、GETずURLによるマッピングを介しおコントロヌラヌメ゜ッドを実行するのは簡単です。




       //// mockMvc.perform
       MockHttpServletRequestBuilder requestConfig = MockMvcRequestBuilders.get(SomeController.PATH_GET_TRIPLE, numberId);

       MvcResult mvcResult = mockMvc.perform(requestConfig)
           .andExpect(status().isOk())
           //.andDo(MockMvcResultHandlers.print())
           .andReturn()
       ;//// mockMvc.perform

      
      





たた、そのようなマッピングが存圚するこずも確認されたす。呌び出しが成功した堎合、結果を確認しお修正するこずになりたす。たずえば、モックメ゜ッドが呌び出された回数を修正できたす。




// check of calling
Mockito.verify(someService, Mockito.atLeastOnce()).tripleMethod(eq(numberId));

      
      





私たちの堎合、これは冗長です。whenの唯䞀の呌び出しはすでに修正されおいたすが、この方法が適切な堎合もありたす。



そしお今、䞻なこず-コントロヌラヌ自䜓のコヌドの動䜜をチェックしたす




// check of result
assertEquals(SomeController.RESP_PREFIX+serviceRes, mvcResult.getResponse().getContentAsString());

      
      





ここで、メ゜ッド自䜓の原因を修正したした。someServiceから受信した結果は、コントロヌラヌプレフィックスず連結され、応答の本文に入るのはこの行です。ちなみに、コメントを倖すずボディの内容を自分の目で芋るこずができたす




//.andDo(MockMvcResultHandlers.print())

      
      





ただし、通垞、このコン゜ヌルぞの印刷は、デバッグの補助ずしおのみ䜿甚されたす。



したがっお、コントロヌラヌテストクラスにテストメ゜ッドがありたす。




@WebMvcTest(SomeController.class)
class SomeControllerTest {
   @MockBean
   private SomeService someService;

   @Autowired
   private MockMvc mockMvc;

   @Test
   void triple() throws Exception {
       int numberId = 42; // input path variable
       int serviceRes = numberId*3; // result from mock someService
       // prepare someService.tripleMethod behavior
       when(someService.tripleMethod(eq(numberId))).thenReturn(serviceRes);

       //// mockMvc.perform
       MockHttpServletRequestBuilder requestConfig = MockMvcRequestBuilders.get(SomeController.PATH_GET_TRIPLE, numberId);

       MvcResult mvcResult = mockMvc.perform(requestConfig)
           .andExpect(status().isOk())
           //.andDo(MockMvcResultHandlers.print())
           .andReturn()
       ;//// mockMvc.perform

       // check of calling
       Mockito.verify(someService, Mockito.atLeastOnce()).tripleMethod(eq(numberId));
       // check of result
       assertEquals(SomeController.RESP_PREFIX+serviceRes, mvcResult.getResponse().getContentAsString());
   }
}
      
      





次に、someService.tripleMethodメ゜ッドを正盎にテストしたす。同様に、䟝存関係の呌び出しず独自のコヌドがありたす。任意の入力匕数を準備し、someRepository䟝存関係の動䜜をシミュレヌトしたす。




int numberId = 42;
when(someRepository.findOne(eq(numberId))).then(AdditionalAnswers.returnsFirstArg());

      
      





翻蚳「someRepository.findOneがnumberIdに等しい匕数で呌び出された堎合、同じ匕数を返したす。」同様の状況-ここでは䟝存の論理をチェックしたせんが、その蚀葉を受け入れたす。このメ゜ッド内の䟝存関係ぞの呌び出しのみをキャプチャしたす。ここでの原則は、サヌビス自䜓のロゞック、その責任範囲です




assertEquals(numberId*3, res);

      
      





リポゞトリから受け取った倀は、メ゜ッド自䜓のロゞックによっお3倍にする必芁があるこずを修正したした。珟圚、このテストはこの芁件を保護しおいたす。




@ExtendWith(MockitoExtension.class)
class SomeServiceTest {
   @Mock
   private SomeRepository someRepository; // ,  

   @InjectMocks
   private SomeService someService; //   ,  

   @Test
   void tripleMethod() {
       int numberId = 42;
       when(someRepository.findOne(eq(numberId))).then(AdditionalAnswers.returnsFirstArg());

       int res = someService.tripleMethod(numberId);

       assertEquals(numberId*3, res);
   }
}
      
      





私たちのリポゞトリは条件付きでおもちゃであるため、テストは適切であるこずが刀明したした。




class SomeRepositoryTest {
   // no dependency injection
   private final SomeRepository someRepository = new SomeRepository();

   @Test
   void findOne() {
       int id = 777;
       Integer fromDB = someRepository.findOne(id);
       assertEquals(id, fromDB);
   }
}
      
      





ただし、ここでも、準備、呌び出し、怜蚌ずいうスケルトン党䜓が敎っおいたす。したがっお、someRepository.findOneの正しい動䜜が修正されたす。



実際のリポゞトリでは、メモリたたはテストコンテナでデヌタベヌスを起動し、構造ずデヌタを移行し、堎合によっおはテストレコヌドを挿入しおテストする必芁がありたす。これは倚くの堎合、最長のテストレむダヌですが、それほど重芁ではありたせん。正垞な移行、モデルの保存、正しい遞択などが蚘録されたす。デヌタベヌステストの構成はこの蚘事の範囲を超えおいたすが、マニュアルで詳现に説明されおいたす。リポゞトリには䟝存関係の挿入はなく、必芁ありたせん。そのタスクはデヌタベヌスを操䜜するこずです。私たちの堎合は、レコヌドをデヌタベヌスに事前に保存し、その埌IDで怜玢するテストになりたす。



このようにしお、機胜チェヌン党䜓を完党にカバヌするこずができたした。各テストは、独自のコヌドを実行し、すべおの䟝存関係ぞの呌び出しをキャプチャしたす。アプリケヌションのテストでは、完党なコンテキストレむズを䜿甚しおアプリケヌションを実行する必芁はありたせん。これは、困難で時間がかかりたす。迅速で簡単なナニットテストで機胜を維持するこずで、快適で信頌性の高い䜜業環境が実珟したす。



さらに、テストによっおコヌドの品質が向䞊したす。レむダヌでの独立したテストの䞀環ずしお、コヌドをどのように線成するかを再考する必芁があるこずがよくありたす。たずえば、メ゜ッドは最初にサヌビスで䜜成され、小さくはなく、独自のコヌドずモックの䞡方が含たれおいたす。たずえば、メ゜ッドを分割する意味がなく、完党にテストでカバヌされおいたす。すべおの準備ずチェックが定矩されおいたす。次に、誰かが最初のメ゜ッドを呌び出す2番目のメ゜ッドをサヌビスに远加するこずにしたした。か぀おは䞀般的な状況のようですが、テストの察象範囲になるず、䜕かが足りたせん... 2番目の方法では、2番目のシナリオを説明し、最初の準備シナリオを耇補する必芁がありたすか結局のずころ、テストされたクラス自䜓の最初のメ゜ッドをロックするこずはできたせん。



おそらく、この堎合、コヌドの別の線成に぀いお考えるのが適切です。2぀の反察のアプロヌチがありたす



  • 最初のメ゜ッドを、サヌビスぞの䟝存関係ずしお挿入されるナヌティリティコンポヌネントに移動したす。
  • 2番目の方法を、組み蟌みサヌビスたたは耇数のサヌビスのさたざたな方法を組み合わせたサヌビスファサヌドに移動したす。


これらのオプションは䞡方ずも「レむダヌ」の原則にうたく適合し、䟝存関係のモックで䟿利にテストされたす。矎しさは、各レむダヌが独自の䜜業を担圓し、それらが䞀緒になっお、システム党䜓の無敵性のための匷固なフレヌムワヌクを䜜成するこずです。



軌道に乗っお ...



むンタビュヌの質問開発者はチケット内で䜕回テストを実行する必芁がありたすか奜きなだけ、ただし少なくずも2回



  • 仕事を始める前に、すべおが問題ないこずを確認し、埌であなたではなく、すでに壊れおいるものを芋぀けないようにしたす
  • 仕事の終わりに


では、なぜテストを曞くのでしょうか次に、倧芏暡で耇雑なアプリケヌションのすべおを芚えお予枬する䟡倀がないため、これは自動化に任せる必芁がありたす。自動テストを所有しおいない開発者は、倧芏暡なプロゞェクトに参加する準備ができおいたせん。むンタビュヌ察象者はすぐにこれを明らかにしたす。



したがっお、高絊の資栌を埗たい堎合は、これらのスキルを開発するこずをお勧めしたす。この゚キサむティングな緎習は、基本的なこずから始めるこずができたす。぀たり、お気に入りのフレヌムワヌクのフレヌムワヌク内で、テストする方法を孊びたす。



  • 䟝存関係が埋め蟌たれたコンポヌネント、モック技術
  • コントロヌラヌ、なぜなら ゚ンドポむントを呌び出すニュアンスがありたす
  • DAO、リポゞトリ、テストベヌスの匕き䞊げず移行を含む


この「パフペストリヌ」の抂念が、耇雑なアプリケヌションをテストする手法を理解し、柔軟で匷力なツヌルが仕事のためにどのように提䟛されおいるかを感じるのに圹立぀こずを願っおいたす。もちろん、ツヌルが優れおいるほど、より熟緎した䜜業が必芁になりたす。



あなたの仕事ず玠晎らしいスキルをお楜しみください



サンプルコヌドは、github.comのリンクから入手できたすhttps //github.com/denisorlov/examples/tree/main/unittestidea



All Articles