私はMockitoとjUnitに非常に慣れていないので、TDDを行う正しい方法を学ぼうとしています。 mockitoを使用して単体テストを記述できるように、いくつかの例が必要です
以下は、ファイルをアップロードし、このファイル入力に対して何らかのアクションを実行するコントローラークラスです。
@Controller
@RequestMapping("/registration")
public class RegistrationController {
@Autowired
private RegistrationService RegistrationService;
@Value("#{Properties['uploadfile.location']}")
private String uploadFileLocation;
public RegistrationController() {
}
@RequestMapping(method = RequestMethod.GET)
public String getUploadForm(Model model) {
model.addAttribute(new Registration());
return "is/Registration";
}
@RequestMapping(method = RequestMethod.POST)
public String create(Registration registration, BindingResult result,ModelMap model)
throws NumberFormatException, Exception {
File uploadedFile = uploadFile(registration);
List<Registration> userDetails = new ArrayList<Registration>();
processUploadedFile(uploadedFile,userDetails);
model.addAttribute("userDetails", userDetails);
return "registration";
}
private File uploadFile(Registration registration) {
Date dt = new Date();
SimpleDateFormat format = new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss");
File uploadedFile = new File(uploadFileLocation
+ registration.getFileData().getOriginalFilename() + "."
+ format.format(dt));
registration.getFileData().transferTo(uploadedFile);
return uploadedFile;
}
private void processUploadedFile(File uploadedFile, List<Registration> userDetails)
throws NumberFormatException, Exception {
registrationService.processFile(uploadedFile, userDetails);
}
}
どのようにして、mockitoを使用して、このためのテストケースをどのように書くことができるか、いくつかの例を提案できますか?
編集次のテストクラスを書き留めましたが、さらに進める方法
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml"})
public class BulkRegistrationControllerTest {
@InjectMocks
private RegistrationService registrationService= new RegistrationServiceImpl();
@Mock
private final ModelMap model=new ModelMap();
@InjectMocks
private ApplicationContext applicationContext;
private static MockHttpServletRequest request;
private static MockHttpServletResponse response;
private static RegistrationController registrationController;
@BeforeClass
public static void init() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
registrationController = new RegistrationController();
}
public void testCreate()
{
final String target = "bulkRegistration";
BulkRegistration bulkRegistration=new BulkRegistration();
final BindingResult result=new BindingResult();
String nextPage=null;
nextPage = bulkRegistrationController.create(bulkRegistration, result, model);
assertEquals("Controller is not requesting the correct form",nextPage,
target);
}
}
テストで交差したように見えることがいくつかあります。統合テストと単体テストがあります。統合テストはすべて(またはほぼすべて)の接続をテストします。そのため、実際のファイルに非常に近いSpring構成ファイルを使用し、オブジェクトの実際の例がテスト中のクラスに注入されます。それは主に私が使うものです@ContextConfiguration
ですが、@ RunWith(SpringJUnit4ClassRunner.class)と組み合わせて使用します
Mockito(または任意のモックフレームワーク)を使用している場合、通常は、テストしているクラスを他のクラスの実際の実装から分離する必要があるためです。したがって、たとえば、RegistrationServiceがNumberFormatExceptionをスローしてそのコードパスをテストする方法を考案する代わりに、模擬RegistrationServiceにそれを実行するように指示するだけです。実際のクラスインスタンスを使用するよりもモックを使用する方が便利な例は他にもたくさんあります。
これでミニレッスンは終了です。ここに私があなたのテストクラスを書き直す方法があります(追加の例と途中でコメントされています)。
@RunWith(MockitoJUnitRunner.class)
public class RegistrationControllerTest {
// Create an instance of what you are going to test.
// When using the @InjectMocks annotation, you must create the instance in
// the constructor or in the field declaration.
@InjectMocks
private RegistrationController controllerUT = new RegistrationController();
// The @Mock annotation creates the mock instance of the class and
// automatically injects into the object annotated with @InjectMocks (if
// possible).
@Mock
private RegistrationService registrationService;
// This @Mock annotation simply creates a mock instance. There is nowhere to
// inject it. Depending on the particular circumstance, it may be better or
// clearer to instantiate the mock explicitly in the test itself, but we're
// doing it here for illustration. Also, I don't know what your real class
// is like, but it may be more appropriate to just instantiate a real one
// than a mock one.
@Mock
private ModelMap model;
// Same as above
@Mock
private BulkRegistration bulkRegistration;
// Same as above
@Mock
private FileData fileData;
@Before
public void setUp() {
// We want to make sure that when we call getFileData(), it returns
// something non-null, so we return the mock of fileData.
when(bulkRegistration.getFileData()).thenReturn(fileData);
}
/**
* This test very narrowly tests the correct next page. That is why there is
* so little expectation setting on the mocks. If you want to test other
* things, such as behavior when you get an exception or having the expected
* filename, you would write other tests.
*/
@Test
public void testCreate() throws Exception {
final String target = "bulkRegistration";
// Here we create a default instance of BindingResult. You don't need to
// mock everything.
BindingResult result = new BindingResult();
String nextPage = null;
// Perform the action
nextPage = controllerUT.create(bulkRegistration, result, model);
// Assert the result. This test fails, but it's for the right reason -
// you expect "bulkRegistration", but you get "registration".
assertEquals("Controller is not requesting the correct form", nextPage,
target);
}
/**
* Here is a simple example to simulate an exception being thrown by one of
* the collaborators.
*
* @throws Exception
*/
@Test(expected = NumberFormatException.class)
public void testCreateWithNumberFormatException() throws Exception {
doThrow(new NumberFormatException()).when(registrationService)
.processFile(any(File.class), anyList());
BindingResult result = new BindingResult();
// Perform the action
controllerUT.create(bulkRegistration, result, model);
}
}
Jherricksが上で示したように、依存関係をMockito(またはJMock)でモックすることにより、Spring MVCコントローラーの純粋な単体テストを書くことは間違いなく可能です。残っている課題は、アノテーション付きPOJOコントローラーを使用すると、テストされていないことがたくさんあるということです。本質的に、アノテーションで表現され、コントローラーが呼び出されたときにフレームワークによって実行されるすべてのことです。
Spring MVCコントローラーのテストのサポートが進行中です( spring-test-mvcプロジェクト を参照)。プロジェクトは変更されますが、現在の形で使用できます。変化に敏感であれば、それに依存すべきではありません。どちらにしても、追跡したいのか、開発に参加したいのかを指摘する価値があると感じました。毎晩のスナップショットがあり、特定のバージョンにロックしたい場合は、今月マイルストーンリリースが行われます。
Mockitoは、オブジェクトのモックに使用されるモックフレームワークです。これは通常、他のオブジェクトのメソッド結果に依存しているメソッドに依存しているメソッドをテストする場合に実行可能です。たとえば、作成メソッドをテストするときは、uploadedFile
変数をモックしたいと思うでしょう。ここでは、uploadFile(Registration registration)
が正しく機能しているかどうかをテストする必要はありません(他のいくつかでテストします) test)、ただし、メソッドがアップロードされたファイルを処理しているかどうか、およびモデルにdetails
を追加しているかどうかをテストすることに興味があります。アップロードファイルを模擬するには、次のようにします:when(RegistrationController.uploadFile(anyObject()).thenReturn(new File());
しかし、これは設計の問題を示しています。あなたのメソッドuploadFile()
はController内ではなく、他のユーティリティクラス内にあるべきです。そして、コントローラの代わりにそのユーティリティクラスを@Mockできます。
コードをテストするのが難しい場合は、コードを単純にするために最善を尽くしていないことを覚えておく必要があります。
私はMockitoに慣れていません(私は JMock を使用しているため)が、モックを使用してテストを作成する一般的なアプローチは同じです。
まず、テスト対象のクラス(CUT)のインスタンス(RegistrationController
)が必要です。これはモックであってはなりません。テストしたいからです。
getUploadForm
をテストする場合、CUTインスタンスは依存関係を必要としないため、_new RegistrationController
_を使用して作成できます。
次に、テストハットはこのように見えるはずです
_RegistrationController controller = new RegistrationController();
Model model = new Model();
String result = controller(model);
assertEquals("is/Registration", result);
assertSomeContstrainsFormodel
_
それは簡単でした。
次にテストするメソッドはcreate
メソッドです。それははるかに困難です。
BindingResult
)は少し複雑かもしれませんregistrationService
とuploadFileLocation
の両方を使用します-これは興味深い部分です。uploadFileLocation
は、テストで設定する必要があるフィールドです。最も簡単な方法は、(getterおよび)setterを追加して、テストで提出されたものを設定することです。 _org.springframework.test.util.ReflectionTestUtils
_を使用してこのフィールドを設定することもできます。 -どちらの方法にも長所と短所があります。
より興味深いのはregistrationService
です。これはモックです!そのクラスのモックを作成し、そのモックをCUTインスタンスに「挿入」する必要があります。 uploadFileLocation
と同様に、少なくとも同じ2つの選択肢があります。
次に、モックの例外を定義する必要があります。そのregistrationService.processFile(uploadedFile, userDetails)
は、正しいファイルとユーザーの詳細で呼び出されます。 (この例外がどの程度正確に定義されているかはMockitoの一部です-私には十分な知識がありません)。
次に、CUTでテストするメソッドを呼び出す必要があります。
ところで、Spring Beanにモックを「注入」する必要がある場合は、独自のユーティリティを作成できます。オブジェクトのインスタンスを取得し、そのオブジェクトをスキャンして_@Inject
_アノテーションが付いたフィールドを探し、そのためのモックを作成し、そのモックを「挿入」します。 (それから、期待値を定義するためにモックにアクセスするためにゲッターが必要になるだけです。)-JMockのためにそのようなツールを構築し、それは私を大いに助けました。
上記のコードサンプルを見ると、いくつか問題があることがわかります。
Mockitoを使用するポイントは、クラスの依存関係をモックすることです。これにより、簡単なJUnitテストケースを使用できるようになります。したがって、@ ContextConfigurationを使用する必要はありません。テストするクラスをnew演算子を使用してインスタンス化し、必要な依存関係を提供できる必要があります。
自動配線を使用して登録サービスを提供しています。このサービスのモックインスタンスを挿入するには、Springテストのプライベートフィールドアクセスユーティリティを使用する必要があります。
RegistrationServiceがインターフェースであるかどうか、コードからはわかりません。そうでない場合は、それをあざけるのに問題があります。
本当の質問は、Springを使用しているアプリケーションのテスト環境をセットアップする方法ですか?この質問への答えは単純ではありません。それは、実際にWebアプリケーションがどのように機能するかに依存します。
最初にJava WebアプリケーションをJUnitする方法に焦点を当て、次にMockitoの使用方法に焦点を当てる必要があります。
代替案:Mockitoを使用しないでください。 Springには、モックに使用できる独自のテストクラスが付属しており、SpringJUnit4ClassRunner
を使用できます。 Spring JUnitテストランナーを使用すると、完全なSpring構成を(@ContextConfiguration
を介して)ロードしたり、オブジェクトをモックしたりできます。あなたの場合、DIを模倣するのではなく、Springを実行するため、インスタンス化コードの多くはなくなります。
これを試して。
@ RunWith(SpringJUnit4ClassRunner.class) @ ContextConfiguration(locations = {"/META-INF/spring/applicationContext.xml"}) public class BulkRegistrationControllerTest { @Mock private RegistrationService registrationService; //テスト中のコントローラー。 @Autowired @InjectMocks private RegistrationController registrationController; @Before public void setUp(){ MockitoAnnotations.initMocks(this); .. 。 } ...