Java EE6で単体テストや結合テストを自動化する方法について
今週水曜日に、オラクル青山センターで行われたGlassfish Japanユーザーグループの勉強会でJava EE6のお話をさせていただきました。勉強会のスライドとビデオは以下のリンク先にあります。
http://www.ustream.tv/recorded/16552906
今回は基本的に私がこのブログで書いてきたJava EE6関連の情報について紹介させていただきました。欲張って少し内容を詰め込み過ぎたところがあったかもしれませんが、Java EE6を使った単体試験や結合試験の自動化については、説明をスキップしてしまい、ちょっとわかりにくくなってしまいました。ここで、あらためてJava EE6上のアプリケーションのテスト自動化について簡単に補足させていただきたいと思います。
Java EE6アプリケーションの単体試験を行う際のクラスパスについての注意点
基本的にJSFのアクションやEJB3.1などはすべてPOJOなので、普通にJUnitとモックフレームワークを使って単体試験を自動化することが可能です。たとえば、以前に次世代のモックフレームワークであるJMockitの基本的な使い方で紹介したJMockitを利用すると、以下のような感じでアクションクラスの単体試験が書けます。
package jsf2.demo.scrum.web.controller.scrum; import javax.faces.validator.ValidatorException; import mockit.Deencapsulation; import java.util.List; import java.util.Arrays; import jsf2.demo.scrum.domain.project.ProjectRepository; import mockit.Expectations; import jsf2.demo.scrum.application.scrum_management.ScrumManager; import mockit.Mocked; import jsf2.demo.scrum.domain.project.Project; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; public class ProjectActionTest { ProjectAction target = new ProjectAction(); @Mocked ScrumManager scrumManager; @Mocked ProjectRepository projectRepository; Project project = new Project(); @Before public void setUp() { target.scrumManager = scrumManager; target.projectRepository = projectRepository; } @Test public void getCurrentProject() { new Expectations() {{ scrumManager.getCurrentProject(); result = project; }}; Project result = target.getCurrentProject(); assertThat(result, is(project)); } @Test public void setCurrentProject() { new Expectations() {{ scrumManager.setCurrentProject(project); }}; target.setCurrentProject(project); } @Test public void getProjects() { new Expectations() {{ projectRepository.findByNamedQuery("project.getAll"); result = Arrays.asList(project); }}; List<Project> projects = target.getProjects(); assertThat(projects.size(), is(1)); assertThat(projects.contains(project), is(true)); } @Test public void showSprint() { new Expectations() {{ scrumManager.setCurrentProject(project); }}; String view = target.showSprints(project); assertThat(view, is("/sprint/show?faces-redirect=true")); } @Test public void reset() { new Expectations() {{ scrumManager.reset(); }}; target.reset(); } @Test public void checkUniqueProjectName_valid() { new Expectations(target) {{ scrumManager.getCurrentProject(); result = project; projectRepository.countOtherProjectsWithName(project, "test"); result = 0L; }}; target.checkUniqueProjectName(null, null, "test"); } @Test(expected=ValidatorException.class) public void checkUniqueProjectName_invalid() { new Expectations(target) {{ scrumManager.getCurrentProject(); result = project; projectRepository.countOtherProjectsWithName(project, "test"); result = 1L; Deencapsulation.invoke(target, "getMessageForKey", "project.form.label.name.unique"); result = "test value"; }}; target.checkUniqueProjectName(null, null, "test"); } }
ただし、Mavenを使ったプロジェクトにおいて、クラスパスの設定に注意する必要があります。通常、Java EE6のアプリケーションを開発する場合、以下の依存関係のみ追加しておけば、JavaEE 6のAPIを利用して開発が行えます。
<dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency>
しかし、この状態でJUnitを実行すると、以下のような例外となって実行できません。
java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/faces/validator/ValidatorException at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) at java.lang.ClassLoader.defineClass(ClassLoader.java:616) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141) at java.net.URLClassLoader.defineClass(URLClassLoader.java:283) at java.net.URLClassLoader.access$000(URLClassLoader.java:58) at java.net.URLClassLoader$1.run(URLClassLoader.java:197) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:248) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:247) at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:95) at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:107) at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:31) at sun.reflect.annotation.AnnotationParser.parseSig(AnnotationParser.java:370) at sun.reflect.annotation.AnnotationParser.parseClassValue(AnnotationParser.java:351) at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:280) at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:222) at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69) at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52) at java.lang.reflect.Method.declaredAnnotations(Method.java:693) at java.lang.reflect.Method.getDeclaredAnnotations(Method.java:686) at java.lang.reflect.AccessibleObject.getAnnotations(AccessibleObject.java:175) at org.junit.runners.model.FrameworkMethod.getAnnotations(FrameworkMethod.java:135) at org.junit.runners.model.TestClass.addToAnnotationLists(TestClass.java:50) at org.junit.runners.model.TestClass.<init>(TestClass.java:40) at org.junit.runners.ParentRunner.<init>(ParentRunner.java:65) at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:58) at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:13) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.<init>(JUnit4TestReference.java:33) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestClassReference.<init>(JUnit4TestClassReference.java:25) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:48) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
この例外が発生する原因は、javaee-web-apiの中にはAPIのインターフェースの部分のみが格納されていて、実装クラスが入っていないことが原因のようです。これを解消するには、例えばGlassfishであれば、以下のように組み込みGlassfishのjarファイルをクラスパスに追加してやるのが簡単です。
<dependency> <groupId>org.glassfish.extras</groupId> <artifactId>glassfish-embedded-web</artifactId> <version>${glassfish.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency>
これで、単体試験が実行可能になります。
結合試験の自動化について
データベースやEJBコンテナ上で実際に複数のクラスを結合した状態で試験を行う方法については、すでにJava EEサーバーが重くてテスト不能というイメージはもう過去の話かもしれない - 達人プログラマーを目指してでも紹介しています。Glassfishを使って結合試験を自動化するためには、jeeunitというライブラリーを使うと便利です。
jeeunitを使うことで、@Injectを使ってテスト対象のオブジェクトをテストクラスのフィールドにインジェクションできます。なお、バージョン0.8からは@Transactionalというアノテーションがサポートされています。実際、これが非常に便利で、このアノテーションをテストクラスのメソッドかクラスにつけることで、各テストメソッドの実行が自動的にトランザクション内で実行され、最後に自動的にロールバックされます。原則としてテストの実行後には副作用が残らないようにしないと、テストの実行順序に依存したりして、トラブルのもとになるため、ロールバックしてデータベースの状態を復元できるということは重要です。
たとえば、以下のように結合試験用のクラスを記述できます。
package jsf2.demo.scrum.domain.project; import org.junit.Before; import com.googlecode.jeeunit.Transactional; import com.googlecode.jeeunit.JeeunitRunner; import java.util.List; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import jsf2.demo.scrum.infra.util.Dates; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; @RunWith(JeeunitRunner.class) @Transactional public class ProjectRepositoryIT { @PersistenceContext EntityManager em; @Inject ProjectRepository target; Project project1; Project project2; Project project3; @Before public void setUp() { project1 = new Project("test1", Dates.create(2011, 8, 6)); project2 = new Project("test2", Dates.create(2011, 8, 7)); project3 = new Project("test3", Dates.create(2011, 8, 8)); em.persist(project1); em.persist(project2); em.persist(project3); } @Test public void crud() throws Exception { Project project = new Project("new project", Dates.create(2011, 8, 6)); target.persist(project); em.flush(); project.setName("renamed"); project.setEndDate(Dates.create(2012, 8, 6)); em.flush(); target.remove(project); em.flush(); } @Test public void getAll() throws Exception { List<Project> projectList = target.findByNamedQuery("project.getAll"); assertThat(projectList.size(), is(3)); } @Test public void countOtherProjectsWithName() throws Exception { assertThat(target.countOtherProjectsWithName(project1, "test2"), is(1L)); assertThat(target.countOtherProjectsWithName(project1, "test*"), is(0L)); } }
以上のテストクラスを実行すると、以下のようなログが得られて、正しくSQLの実行が行われていることが確認できます。
[#|2011-08-13T00:43:43.806+0900|INFO|glassfish3.1|javax.enterprise.system.core.transaction.com.sun.jts.CosTransactions|_ThreadID=84;_ThreadName=http-thread-pool-8088(1);|JTS5014: Recoverable JTS instance, serverId = [100]|#] [#|2011-08-13T00:43:44.026+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.131+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.156+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.186+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.186+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.286+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.286+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.386+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.391+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|UPDATE projects SET end_date = ?, NAME = ? WHERE (ID = ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.546+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=17;_ThreadName=http-thread-pool-8088(1);ClassName=null;MethodName=null;|DELETE FROM projects WHERE (ID = ?) bind => [1 parameter bound]|#] [#|2011-08-13T00:43:44.984+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.986+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.988+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.990+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.990+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:44.991+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:44.993+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=18;_ThreadName=http-thread-pool-8088(5);ClassName=null;MethodName=null;|SELECT ID, end_date, NAME, start_date FROM projects|#] [#|2011-08-13T00:43:45.119+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:45.122+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:45.193+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:45.196+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:45.197+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|INSERT INTO projects (end_date, NAME, start_date) VALUES (?, ?, ?) bind => [3 parameters bound]|#] [#|2011-08-13T00:43:45.202+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|values IDENTITY_VAL_LOCAL()|#] [#|2011-08-13T00:43:45.204+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|SELECT COUNT(ID) FROM projects WHERE (NAME = ?) bind => [1 parameter bound]|#] [#|2011-08-13T00:43:45.237+0900|FINE|glassfish3.1|org.eclipse.persistence.session.file:/C:/Users/Ryo/AppData/Local/Temp/gfembed7880109940612296692tmp/applications/jeeunit/WEB-INF/classes/_scrumtoysPU.sql|_ThreadID=19;_ThreadName=http-thread-pool-8088(3);ClassName=null;MethodName=null;|SELECT COUNT(ID) FROM projects WHERE ((NAME = ?) AND NOT ((? = ID))) bind => [2 parameters bound]|#]
なお、GitHub - ryoasai/jsf-scrumtoys-refactored: A sample web application using Java EE6 stack.にあるサンプルではMavenのmaven-failsafe-pluginを使うことで普通の単体試験と結合試験の実行を分離する設定となっています。コマンドラインから
mvn verify
とすると結合テストを含めて全テストが実行され、
mvn test
とすると単体試験のみが実行されます。JavaEEに限らず、結合テストの実行はどうしても時間がかかるため、このようにテストの実行を分離しておくと便利です。
Integration tests with Maven (Part 1): Failsafe Plugin
Integration Tests with Maven (Part 2): Test Coverage Reports
Integration Tests with Maven (Part 3): Case study - Flexmojos
(追記)
jsf-scrumtoys-refactoredの実行手順に関してid:megascusさんに説明を書いていただきました。どうもありがとうございます。
jsf-scrumtoys-refactored を動かすまで - 水まんじゅう2
なお、このアプリケーションはmavenのプロジェクトになっており、構成がIDEに依存しないためEclipseでも開くことが可能です。ただし、Eclipseで実行する場合は別途Glassfishのプラグインを追加してください。
Eclipse3.6 GlassFishプラグインの導入 - Diary of absj31
なお、本文中で単体試験、結合試験という用語を用いていますが、このような用語については定義が大切ですね。ここでは、それぞれ一つ一つのクラスに対してロジックを確認するテストを単体試験、複数のクラスを結合してコンテナ上で動かすテストを結合テストと呼んでいます。
もちろん、画面まで結合して各機能を確認するようなテストや他のシステムまで結合して行うようなテストも別途行う必要がありますが、ここではスコープ外としています。