Modern Best Practices for Testing in Java
https://phauer.com/2019/modern-best-practices-testing-java/
ãã¹ãæã«äœæ°ãªãæ°ãã€ããŠããäºãæ ¹æ ãšãšãã«å ·äœçã«ç€ºãããŠããŠã èªåèªèº«ããã¹ãããæã¯ãã¡ãããã³ãŒãã¬ãã¥ãŒæã§ã泚æã»ææãããå 容ããŸãšããããŠããŸãã
ã¿ã€ãã«ã«ã¯ãJavaããšæžãããŠããŸããããã©ã¯ãã£ã¹ãšããŠã¯èšèªã«äŸåããªãå 容ãã»ãšãã©ãªã®ã§ã ç¹å®ã®èšèªã«éããªãæçãªãã©ã¯ãã£ã¹ã ãšæããŸããã
ããã§ã¯ãããããã®äžèº«ããŸãšããŠãããŸãã
äžè¬çãªãã©ã¯ãã£ã¹ã®è©±
ãã¹ãã¡ãœããã§ã¯ã以äžã®åºæ¬ã«ãŒã«ãå®ããšè¯ãã¿ããã§ãã
- (1) 1ã€ã®ç©ºè¡ã§åºåããã3ã€ã®ãããã¯ãå«ãããã«ãã
- Given (Input): ããŒã¿äœæãã¢ãã¯ã®èšå®ãªã©ã®ãã¹ãæºå
- When (Action): ãã¹ããããã¡ãœãããã¢ã¯ã·ã§ã³ãåŒã³åºã
- Then (Output): ã¢ãµãŒããå®è¡ããŠãã¢ã¯ã·ã§ã³ã®æ£ããåºåãåäœãæ€èšŒãã
- (2) çå€ã¢ãµãŒãã§å€æ°ã䜿çšããå Žåã¯ã倿°åã®åã«
actualãšexpectedãä»ãã- å®éã«ååŸãããå€: 倿°åã®åã«
actualãã«ã€ãã - ååŸãããå€ã®æåŸ
å€: 倿°åã®åã«
expectedãã€ãã - ã¢ãããŒã·ã§ã³: æ¯èŒæã®æå³ãæç¢ºã«ãªãééããæžãããå¯èªæ§ãããããã
- å®éã«ååŸãããå€: 倿°åã®åã«
- (3) ã©ã³ãã ãªå€ïŒç¹ã«æå»ãªã©ïŒã¯äœ¿ããªããåºå®å€ã䜿ã£ãŠåçŸæ§ãé«ãã
(1) 1ã€ã®ç©ºè¡ã§åºåããã3ã€ã®ãããã¯ãå«ãããã«ãã
(2) çå€ã¢ãµãŒãã§å€æ°ã䜿çšããå Žåã¯ã倿°åã®åã« actual ãš expected ãä»ãã
æåã«ã(1), (2)ããŸãšãããšã以äžã®åœ¢ãçæ³çã§ãã
@Test
public void getTest() {
// Given
UserGetRequest request = new UserGetRequest(1111L);
UserDao dao = new UserDao();
// When
User actualUser = dao.get(request);
// Then
User expectedUser = new User(1111L, "John");
assertThat(actualUser, is(expectedUser));
}Groovyã®Spock frameworkã䜿ã£ãŠãããšãæ§æãšããŠãã®ããããçšæãããŠããŠèªç¶ãšæèãã圢ã«ãªã£ãŠããŸãããã
Spock Framework Reference Documentation
http://spockframework.org/spock/docs/1.3/all_in_one.html
spock-workshop/02_basics.md at master · yamkazu/spock-workshop
https://github.com/yamkazu/spock-workshop/blob/master/docs/02_basics.md#setup
ãããããã¬ãŒã ã¯ãŒã¯ã«é¢ãããããã¹ãããæãããã®ã¹ãããããšã«ãããã¯ãåããŠã¡ãããšèšè¿°ããããã£ãŠããšã§ãã
(3) ã©ã³ãã ãªå€ïŒç¹ã«æå»ãªã©ïŒã¯äœ¿ããªããåºå®å€ã䜿ã£ãŠåçŸæ§ãé«ãã
çµæãæ¯åå€ãããããªãã©ã³ãã åãããå€ãç»å Žãããã¹ãã¯è¡ããªãããã«ããŸãã
以äžãæªãäŸã§ãã
/**
* ãã¡ãªäŸ
*/
@Test
public void formatBadTest() {
// Given
Instant currentInstant = Instant.now(); // example => 1601739774
EpochSecondFormatter formatter = new EpochSecondFormatter();
// When
String actualDate = formatter.format(currentInstant); // example: => DATE: 20201003
// Then
String expectedDate = createExpectedString(currentInstant);
assertThat(actualDate, is(expectedDate));
}ãããããã¹ãã¯ã¢ãµãŒãã倱æããå Žåã«åºããã°ã®å€ãæ¯åå€ããã®ã§ãããã°ãå°é£ã«ãªããŸãã ãŸããã³ã¡ã³ãã«èšèŒãããŠããå€ã圹ã«ç«ã¡ãŸããã
以äžã®ããã«ãåºå®å€ã䜿ããæ¯ååãçµæã«ãªãããã«ããŸãã
/**
* è¯ãäŸ
*/
@Test
public void formatGoodTest() {
// Given
Instant fixedInstant = Instant.ofEpochSecond(1601739774);
EpochSecondFormatter formatter = new EpochSecondFormatter();
// When
String actualDate = formatter.format(fixedInstant); // DATE: 20201003
// Then
String expectedDate = createExpectedString(fixedInstant);
assertThat(actualDate, is(expectedDate));
}ããããããšã§ãã€å®è¡ããŠãæ¯ååãçµæã«ãªãåçŸæ§ã®é«ããã¹ãã«ãªããŸãã
å°èŠæš¡ã§å ·äœçãªãã¹ããæžãããšãã話
- (1) ç¹°ãè¿ã䜿ãã³ãŒãã¯å°çšã®ã¡ãœããã«åãåºããåãããããèšè¿°çãªååãä»ãã
- (2) è€æ°å䜿çšãããå€ã倿°ã«æœåºããã®ã¯å®ã¯ãããªãæ¹ãè¯ã
- (3) æåŸ
ãããåäœã«ã€ããŠã®ãã¹ãã¡ãœãããããããåå¥ã«çšæãã
- æåŸ ããåäœãäœãªã®ããåããããããã¹ãã¡ãœããåã«ãã
- 1ã€ã®ã§ãããã¹ãã¡ãœããã®äžã§ããããªã³ãŒããŒã±ãŒã¹ã®ãã¹ãã¯ãããªãã»ããè¯ã
- (4) ãã¹ããããéšåã ããã¢ãµãŒããã
(1) ç¹°ãè¿ã䜿ãã³ãŒãã¯å°çšã®ã¡ãœããã«åãåºããåãããããèšè¿°çãªååãä»ãã
ãã¹ãçšã®ãªããžã§ã¯ããäœãã ãã®ã¡ãœãããã¿ãããªãã¿ããªããç¡æèã®ãã¡ã«ãã£ãŠãããšã ãšã¯æããŸãã
ãã ãããã§å€§åãªã®ã¯ãããã¹ãã«é¢ä¿ãããã£ãŒã«ãããã©ã¡ãŒã¿ãšããŠæå®ãããã¡ãœãããã«ãããšããèŠéããè¯ãã¿ããã§ãã
@Test
public void getByIdGreaterThanTest() {
// Given
UserFilter userFilter = new UserFilter(Arrays.asList(
createUserWithId(10L),
createUserWithId(20L),
createUserWithId(30L)
));
// When
List<User> actualUsers = userFilter.getByIdGreaterThan(15L);
// Then
assertThat(actualUsers.size(), is(2));
assertThat(actualUsers, is(containsInAnyOrder(createUserWithId(20L), createUserWithId(30L))));
}ããã§ã¯ãIDã§ User ã¯ã©ã¹ãçµã蟌ãåŠçã®ãã¹ããããŠããã®ã§ã User.name ãã£ãŒã«ãã¯äœã§ãè¯ãã§ãã
ãªã®ã§ã createUserWithId(Long id) ãšããIDãæå®ã㊠User ãè¿ããŠãããïŒ User.name ã®å€ã¯äœã§ãè¯ãïŒãã¹ãçšã®ã¡ãœãããçšæããŸãã
(2) è€æ°å䜿çšãããå€ã倿°ã«æœåºããã®ã¯å®ã¯ãããªãæ¹ãè¯ã
ããã¯æå€ã§ããã
A usual reflex of a developer is to extract values that are used multiple times to variables.
è€æ°å䜿çšãããå€ã倿°ã«æœåºããã®ãéçºè ã®åžžå¥ææ®µã§ãã
ãããããã¡ãšãããããããããããæ¹ãè¯ããšãã人ãããããªãããã®è©±ã§ããã ãã®å èšäºã§ã¯ãå®ã¯å€æ°åã®ããããã¯è¯ããªããšç޹ä»ãããŠããŸããã
ãšããã®ããã¢ãµãŒãã倱æããæã®ãã°ãããã¹ãã³ãŒãã蟿ãéã 倿°åãããŠãããšå®éã«åé¡ãããè¡ãŸã§ãã¬ãŒã¹ããã®ã«æéãããã£ãŠããŸãããããšããããšã®ããã§ãããµãŒãã
ãã®ãè€æ°å䜿çšãããå€ã倿°ã«æœåºãããè¯ããªãäŸã以äžã§ãã
@Test
public void getByIdGreaterThanVariableIdTest() {
// Given
Long id1 = 10L;
Long id2 = 20L;
Long id3 = 30L;
UserFilter userFilter = new UserFilter(Arrays.asList(
createUserWithId(id1),
createUserWithId(id2),
createUserWithId(id3)
));
// When
List<User> actualUsers = userFilter.getByIdGreaterThan(15L);
// Then
assertThat(actualUsers.size(), is(2));
assertThat(actualUsers, is(containsInAnyOrder(createUserWithId(id2), createUserWithId(id3))));
}ããJUnitã ãšåããã¥ãããã§ãããïŒäŒç€Ÿã§ã¯Spock Framework䜿ã£ãŠãŸãããGroovyã«ã¯ Power assert ã£ãŠãã䟿å©ãªæ©èœãã€ããŠããŠã
The Apache Groovy programming language - Testing guide
https://www.groovy-lang.org/testing.html#_power_assertions
äŸãã°ã以äžã®æ§ã«å€±æãããã¹ããå®è¡ãããš…
def "Groovyã®å Žå"() {
setup:
Long id1 = 10L
Long id2 = 20L
Long id3 = 30L
UserFilter userFilter = new UserFilter(Arrays.asList(
createUserWithId(id1),
createUserWithId(id2),
createUserWithId(id3),
));
when:
List<User> actualUsers = userFilter.getByIdGreaterThan(15L)
then:
assert actualUsers.size() == 2
assert actualUsers[0].id == id2
assert actualUsers[1].id == id1
}以äžã®ãããªæãã§ãšã©ãŒããã£ãè¡ãšãã®å€ãããããŠè¡šç€ºããŠãããŸãã
// Console output
actualUsers[1].id == id1
| | | | |
| | 30 | 10
| | false
| io.github.xshoji.samplecode.bestpractice.testingtarget.User@c868802
[io.github.xshoji.samplecode.bestpractice.testingtarget.User@c860008, io.github.xshoji.samplecode.bestpractice.testingtarget.User@c868802]ãªã®ã§ãããã§ææãããŠããæžå¿µã¯æèããããšãããŸããã§ããã Javaã䞻軞ã«ããèšäºãªã®ã§ããã¯ä»æ¹ãªãã§ãããSpockã¯Groovyãªãã§ã
è©±ãæ»ããŠã倿°ã«ãŸãšããªããšäœåºŠãåãå€ãæžãããšã«ãªãã®ã§DRYååã«åãããã§ããããã®å Žåã¯
KISS. Keep It Simple,Stupid > DRY. Don’t Repeat Yourself.
ãšããèãã®ããã§ãããŸããã³ãŒãã¯èªãæéã®ã»ããå§åçã«é·ãã§ããããã ãšã¯ããã倿°åèªäœã¯ãã£ãã»ããè¯ãå Žåããã¡ããããã®ã§ãããã§ã¯è€éåãããèŠå ã«ããªãåŸãããæ°ãã€ããããçšåºŠã§æããŠããã°è¯ãããã
(3) æåŸ ãããåäœã«ã€ããŠã®ãã¹ãã¡ãœãããããããåå¥ã«çšæãã
ããã®éãçµæ§ãã£ãŠããŸããã¡ãGetTest() ãšããããã¹ãã¡ãœããå
ã§éåžžç³»ãšããšã©ãŒãšãè²ã
ãã¹ããã¡ãã£ãŠããšãããã¿ãŒã³ã
ç¹ã«ãæ©èœè¿œå ãªã©ã§æ¢åã®ã¡ãœããã®åœ¹å²ãèšããã§ãã£ãå Žåã«ã
å
ã®ãã¹ãã¡ãœããã«ã©ãã©ããã¿ãŒã³ã远å ããŠãã£ã¡ãããã¿ãããªãã€ã
Yes, itâs more writing effort but you can create a tailored and clear test, that only test the relevant behavior.
ããã§ããããã¯ããå€ãæžãåŽåãå¿ èŠã§ãããé¢é£ããåäœã®ã¿ããã¹ããããããªãã«ã¹ã¿ãã€ãºãããæç¢ºãªãã¹ããäœæããããšãã§ããŸãã
ãã¹ãã¡ãœããå ã§ãã£ãŠãããšãå€ãããŠãä¿®æ£ãããã«ãã©ãçŽãã°è¯ããããããªãïŒãšãªãã®ãé²ãããã 1ãã¹ãã¡ãœããã1ã€ã®èгç¹ã ãã«ããŒããå šäœã®åãã¹ãã¡ãœããã§äœ¿ãããå ±éã®åŠç㯠ã©ãã©ããã«ããŒã¡ãœããã«åãåºããŠãããããšãã話ã§ãã
ãã¹ãã倱æããæã«ãªãã§å€±æãããã£ãŠããã®ãåãããããããã¹ããšããäºã®ããã§ãã
(4) ãã¹ããããéšåã ããã¢ãµãŒããã
So we should only check the relevant field to clearly state and document the scope of the logic under test.
ãã®ãããé¢é£ãããã£ãŒã«ãã ãããã§ãã¯ããŠããã¹ã察象ã®ããžãã¯ã®ç¯å²ãæç¢ºã«ç€ºããææžåããå¿ èŠããããŸãã
é¢ä¿ãªããšãããäžå¿ã¢ãµãŒãããŠããããã¿ãããªã®å²ãšãã£ãŠããŸããã¡ã§ãããæå³ããªãã®ã§ãããŸãããã£ãŠããšã§ããã ç¹ã«ãè²ããªã¡ãœããã§äœ¿ãããŠãå ±éã®ããžãã¹ããžãã¯ã¯ãè€æ°ã®ãã¹ãå ã§äœåºŠãåã芳ç¹ã®ã¢ãµãŒããã¡ãã£ãŠãã±ãŒã¹ã¯å€ãã®ã§ã (3)ã®ãã¹ãã¡ãœãããåé¢ãã話ãšåãããŠã
- å ±éã§ã¢ãµãŒããã¹ããšãã
- ã³ãŒããŒã±ãŒã¹ãšããŠè¿œå ã§ã¢ãµãŒããã¹ããšãã
ãåé¢ããæ§æã«ããããšãæ±ãããããã§ãã
èªå·±å®çµåãã¹ãã®è©±
- (1) ãã¹ãã³ãŒãã®èªè
ã«ã¡ãœããã®å®çŸ©ãžã®ãžã£ã³ãã匷å¶ãããªã
- ãã¹ãã§å¶åŸ¡ããå¿ èŠããããã©ã¡ãŒã¿ããã«ããŒã¡ãœããã®åŒæ°ã«ãã
- äºåããŒã¿ã®æºåæ©èœïŒJUnitã ãš
@BeforeïŒã¯äœ¿ããããã«ããŒã¡ãœãããæç€ºçã«åŒã³åºã圢ã«ãã
- (2) ç¶æ¿ãããåæãåªå ãã
(1) ãã¹ãã³ãŒãã®èªè ã«ã¡ãœããã®å®çŸ©ãžã®ãžã£ã³ãã匷å¶ãããªã
ããã¯ãç¹°ãè¿ã䜿ãã³ãŒãã¯å°çšã®ã¡ãœããã«åãåºãåãããããèšè¿°çãªååãä»ãã ã ã«åºãŠããå 容ãšå°ã被ããŸãããäºåããŒã¿ãçšæããããã®ãã«ããŒã¡ãœããã®åŒæ°ã¯ã å¿ ããã¹ãã«é¢ä¿ããåŒæ°ãæž¡ãããã«ããããšããããšã§ãã
// æªãäŸ
User user = createUser();
// è¯ãäŸ
User user = createdUserWithId(11111L);æªãäŸã®åŒæ°ãªãã®ãã«ããŒã¡ãœããã ãšãäžã§äœãäœãããã®ãå®çŸ©ã«é£ãã§ç¢ºèªããªããšèªãã 人ã¯åãããŸããã
è¯ãäŸã®å Žåãå°ãªããšãIDã 11111 ã®UserãäœãããŠããããšã¯å®çŸ©ã«é£ã°ãªããŠãåãããŸããåžžã«ãã£ã¡ã®åœ¢ã«ãªãããã«ããŸãããã
ãŸããå
±éã®äºååŠçãä»èŸŒãããã®ä»çµã¿ïŒJavaã®JUnitã ãš @Before ã¢ãããŒã·ã§ã³ãã€ããã¡ãœããã¯åãã¹ãã®å®è¡åã«å¿
ãèªåã§åŒã³åºããŠãããïŒã¯ã§ããã ã䜿ããªãããã«ãããããã£ãå
±éåŠçã¯ãã«ããŒã¡ãœããåã«é©åãªååãã€ãã
åãã¹ãã¡ãœããå
ã§åå¥ã«åŒã¶ããã«ããŸãã
以äžãã ããªäŸã§ããïŒå®å šã«ãã¡ã§ã¯ãªããã©ãè¯ããªãäŸïŒ
private static List<User> targetUsers;
/**
* ãã¡ãªäŸ
*/
@Before
public void setup() {
targetUsers = Arrays.asList(
createUserWithId(10L),
createUserWithId(20L),
createUserWithId(30L)
);
}
...
@Test
public void getByIdGreaterThanTestWithBeforeSetup() {
// Given
UserFilter userFilter = new UserFilter(targetUsers);
// When
List<User> actualUsers = userFilter.getByIdGreaterThan(15L);
// Then
assertThat(actualUsers.size(), is(2));
assertThat(actualUsers, is(containsInAnyOrder(createUserWithId(20L), createUserWithId(30L))));
}ããã ãšã getByIdGreaterThanTestWithBeforeSetup ã®ãã¹ããèªãããã«ã¯ã äºååŠçã§ããŒã¿ãã»ããã¢ãããããããšãç¥ããªããšçè§£ã§ããŸããã
ãã®ãããäºååŠçãè¡ãããã¡ãœããã®å®çŸ©ãæ¢ãããšãããã«ããã«é£ãã§äžèº«ãèªãããšã匷å¶ãããŸãã
ãã®å Žåã以äžã®æ¹ãè¯ãã§ãã
@Test
public void getByIdGreaterThanTestIncludesSetup() {
// Given
List<User> targetUsers = createUsersHavingIdFrom10To30();
UserFilter userFilter = new UserFilter(targetUsers);
// When
List<User> actualUsers = userFilter.getByIdGreaterThan(15L);
// Then
assertThat(actualUsers.size(), is(2));
assertThat(actualUsers, is(containsInAnyOrder(createUserWithId(20L), createUserWithId(30L))));
}äºååŠçã§ãã£ãŠè¯ãã®ã¯ãããŒã¿ããŒã¹ã®ã»ããã¢ããããHTTPéä¿¡åšãã®èšå®ãªã©ã åãã¹ãã±ãŒã¹ã«äŸåããªãå ±éèšå®ïŒãã¹ãã±ãŒã¹ã®èªè ã詳现ãæèããªããŠè¯ãããšïŒãæžãã¹ãã§ãã
(2) ç¶æ¿ãããåæãåªå ãã
ç¶æ¿ãããå§è²ã䜿ã£ãã»ããè¯ããšããè°è«
Effective Java Tuesday! Favor Composition Over Inheritance - DEV
https://dev.to/kylec32/effective-java-tuesday-favor-composition-over-inheritance-4ph5
ãšã¯å°ãè¶£æšã¯éããŸããã èšã£ãŠãããšã¯åãã§ãããã¹ãã§ç¶æ¿ã䜿ããªãæ¹ãè¯ãçç±ã¯ä»¥äžã®éãã§ãã
- çŸåšã®ãã¹ããå¿ èŠãšããŠããªããã®ãå€ãå«ãããŒã¹ãã¹ãã¯ã©ã¹ãæ¡åŒµãã矜ç®ã«ãªã
- äžã€ããããã¯è€æ°ã®åºåºã¯ã©ã¹ãé£ã³åããªããã°ãªããªããªã
- ç¶æ¿ã¯æè»æ§ã«æ¬ ãã
The Wrong Abstraction â Sandi Metz
https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction“Prefer duplication over the wrong abstraction.” Sandi Metz.
“ééã£ãæœè±¡åãããéè€ãåªå ãã” ãµã³ãã£ã»ã¡ãã
å ·äœçã«ã¯ã以äžã®ãããªãã¹ãã®æ§æã¯é¿ããŸãããã
public class SelfContainedTestBase {
protected Connection connection;
@Before
public void setup() {
this.connection = this.setupDatabaseConnection();
this.loadFixtures();
}
...
}
public class SelfContainedTest extends SelfContainedTestBase {
@Test
public void extendedTest() {
// Given ( fixture data is loaded in SelfContainedTestBase )
UserDao dao = new UserDao(this.connection);
// When
List<User> actualUsers = dao.getAll();
// Then
User expectedUser = new User(1111L, "John");
assertThat(actualUsers, is(containsInAnyOrder(expectedUser)));
}
}ç¶æ¿å
ã® setup() ã®åŠççžåœã®ã¯ã©ã¹ãåå¥ã«åé¢ãã
以äžã®ããã«ç¶æ¿ãããããã¹ãã¡ãœããå
ã§ããããåå¥ã«åŒã³åºãäœãã®æ¹ãè¯ãã§ãã
public class SelfContainedTest {
@Test
public void compositionTest() {
// Given
Connection connection = new DatabaseConfiguration().setupConnection();
FixtureLoader loader = new FixtureLoader(connection);
loader.load();
CompanyDao dao = new CompanyDao(connection);
// When
List<Company> actualCompanies = dao.getAll();
// Then
Company expectedCompany = new Company(1111L, "Apple");
assertThat(actualCompanies, is(containsInAnyOrder(expectedCompany)));
}
}ãšãŠãè€éãªæç¶ããå¿ èŠã§ãã©ãããŠãç¶æ¿ããŠå ±éåŠçã«ãããïŒã£ãŠå Žåã§ãã ãã®è€éãªæç¶ããè¡ãã ãã®ã¯ã©ã¹ãå¥ã§çšæããåã¯ã©ã¹ã®Givenã§æ¯ååŒã¶ã»ããè¯ããšæããŸãã ãšã«ãããã³ãŒããèªã人ãé¢å¿äºãšãªããã¹ãã¡ãœãã以å€ã®ç®æãæèããªããŠæžãããã«ãããã£ãŠããšã§ããã
åºåã¯ããŒãã³ãŒããããæåŸ å€ãšæ¯èŒããããšããã®è©±
- (1) Production CodeïŒã¢ããªæ¬äœã®ã³ãŒãïŒããã¹ãã§åå©çšããªã
- (2) Production LogicïŒã¢ããªæ¬äœã®ããžãã¯ïŒããã¹ãã«æã¡èŸŒãŸãªã
- (3) ãã¹ãã¡ãœããå ã«ããžãã¯ãæžããããªãããã«ãã
(1) Production CodeïŒã¢ããªæ¬äœã®ã³ãŒãïŒããã¹ãã§åå©çšããªã
ãã¹ã察象ã®åŠç以å€ã®Production CodeïŒã¢ããªæ¬äœã®ã³ãŒãïŒã¯ãã¹ãã§åå©çšãããªããšããããšãã©ãããããšããšãããšã
- ã¢ãµãŒãããããããããã«Production Codeã®ãšããã¡ãœããã䜿ã£ãŠæåŸ å€ãçæããŠæ¯èŒãã
ã®ãããªã±ãŒã¹ã以äžãå èšäºã®ãµã³ãã«ã³ãŒãã
// Don't
boolean isActive = true;
boolean isRejected = true;
insertIntoDatabase(new Product(1, isActive, isRejected));
ProductDTO actualDTO = requestProduct(1);
// production code reuse ahead
List<State> expectedStates = ProductionCode.mapBooleansToEnumList(isActive, isRejected);
assertThat(actualDTO.states).isEqualTo(expectedStates);ããã§ã¯ã requestProduct(1) ã®InãšOutã®ã¿ããã¹ããã¹ãã§ãã£ãŠãæåŸ
å€ã®çæã®ããã« ProductionCode.mapBooleansToEnumList ãšããProduction codeãç»å ŽãããŠã¯ãããªããšããããšã
äœãåé¡ããšãããšã
If you reuse production code in a test, you might miss a bug that is introduced in the reused code because you donât test this code anymore.
ãã¹ãã§Production codeãåå©çšãããšããã®ã³ãŒãããã¹ãããªããªã£ãããã«ãåå©çšããã³ãŒãã«å°å ¥ããããã°ãèŠéããŠããŸãå¯èœæ§ããããŸãã
ã€ãŸãã requestProduct(1) ã®äžã§ ProductionCode.mapBooleansToEnumList ã䜿ãããŠãããšã
ProductionCode.mapBooleansToEnumList ã«ãã°ããã£ãå Žåã«ãééã£ãçµæãšééã£ãæåŸ
å€å士ã§ã®æ€èšŒãšãªã£ãŠããŸããã¢ãµãŒãããã¹ããŠããŸãããã
ãã¹ãã¡ãœããã«ãããŠã¯ã
- ãã¹ã察象ã®å ¥åãåãããããèšå®ãã
- åºåã®æåŸ å€ã«ã¯ããŒãã³ãŒããããå€ã䜿ã
ãè¯ããããã
(2) Production LogicïŒã¢ããªæ¬äœã®ããžãã¯ïŒããã¹ãã«æã¡èŸŒãŸãªã
(1)ã«äŒŒãŠããã©ããã£ã¡ã¯æ¬çªã§äœ¿ã£ãŠãã³ãŒããã³ããŒããŠãã¹ãã¯ã©ã¹ã§äœ¿ããã¯ãããŠããã£ãŠãã€ã§ãã
ç¹ã«ã倿åŠçãšãäœãããå€ãå å·¥ãããããªã·ã³ãã«ãªå®è£ ããã£ãæã ãã¹ãã§ãã®å€æåŠçãæ¬²ãããªã£ãŠã¢ããªæ¬äœã®ã³ãŒããéšåçã«ãã¹ãã¯ã©ã¹ã«æã£ãŠããã¿ãããªããšããããŸãã
ããããã®ã§ã¯ãªããå€æçµæããã¹ãã¡ãœããå ã«ããŒãã³ãŒãããŠãããæåŸ å€ãšããããšããããšã¿ããã§ãã
(3) ãã¹ãã¡ãœããå ã«ããžãã¯ãæžããããªãããã«ãã
ãã¹ãã¡ãœããã¯ãåºæ¬çã«ã¯InãšOutã®æ¯èŒã ãã®ã¯ãã
äŸå€ãå€ãå€ãç°åžžå€ãªã©ãããããªã±ãŒã¹ã1ã€ã®ã¡ãœããå ã§ã¢ãµãŒããå§ãããšã foræãifæã®åµã«ãªãã®ã§ãããªããªãããã«ãã¹ãã
çŸå®ã«è¿ããã¹ãã®è©±
ãããŸã§ã¯ãåäœãã¹ãã«ã€ããŠã®è©±ã§ããããæçµçã«ã¯ãçµ±åãã¹ãããäžçªåšåãé«ããä¿¡é Œã§ãããã¹ãã«ãªãããšãã話ã§ãã
çµå±åäœãã¹ãã¯Mockã䜿ã£ãããã¯ã©ã¹éãã€ãªãã å Žåã®åäœã¯ä¿èšŒã§ããªãã®ã§ã ãªãã¡ã¯ã¿ãªã³ã°ãªã©ã«ãã£ãŠå éšã®æ¯ãèããå€ãã£ãå Žåã§ããã¹ãã§ããã«æ°ã¥ããªãå ŽåããããŸãã
ãçµ±åãã¹ãããããã°ãåãåäœãããŠãããã©ãããåäœãã¹ããããæ£ç¢ºã«ä¿èšŒã§ããŸãã ããã§ãããçµ±åãã¹ãããšã¯ãå®éã«ã¢ããªã±ãŒã·ã§ã³ãåäœãããŠæ¯ãèãã®æ€èšŒãè¡ããã¹ãã®ããšã§ãã
By âintegration testsâ (or âcomponent testâ) I mean putting all classes together (just like in production) and test a complete vertical slide going though all technical layers (HTTP, business logic, database).
ãçµ±åãã¹ããã«ãããæ¬çªãšåãããã«ãã¹ãŠã®ã¯ã©ã¹ããŸãšããŠããã€ãã¹ãŠã®æè¡å±€(HTTPãããžãã¹ããžãã¯ãããŒã¿ããŒã¹)ãå®å šã«äžããäžãŸã§éããã¹ãã宿œã§ããããšãæå³ããŸãã
ãã®çµ±åãã¹ãã«ã€ããŠã¯è©³ããç¥ããããªããå¥ã®èšäºèŠãŠã¡ãã£ãŠæžããŠãã£ã…ç¬
Focus on Integration Tests Instead of Mock-Based Tests
https://phauer.com/2019/focus-integration-tests-mock-based-tests/
ãã¹ã¿ãã«ãªå®è£ ã®è©±
ããã¯ãã¹ãæ¹æ³ãšãããããå®è£ ã®è©±ãªã®ã§ããã£ãšã ãã
- (1) Production codeã§éçã¢ã¯ã»ã¹ã䜿ããªã
- (2) ããžãã¯ãæã€å¶éïŒäžéå€ããããå€ãªã©ïŒã¯ãã©ã¡ã¿ãšããŠå€ããèšå®å¯èœã«ãã
- (3)
Instant.now()ãnew Date()ã䜿ããªãããã«ãã - (4) éåæå®è¡ãšããžãã¹ããžãã¯ã¯åé¢ãã
(1) Production codeã§éçã¢ã¯ã»ã¹ã䜿ããªã
staticã¢ã¯ã»ã¹ãã¡ãããšãMockã«å·®ãæ¿ãããã§ããªããªãã®ã§ããã¹ãããã¥ãããªããŸãã ãªã®ã§ãäŸåããªãstaticçã«å®è£ ã§ããããªåŠçã§ãã£ãŠããéçã¢ã¯ã»ã¹ã«ã¯ãããäŸåãããã¯ã©ã¹ã«DIããŠåŒã³åºã圢ã§å®è£ ããã®ãè¯ãããšãã話ã§ãã
(2) ããžãã¯ãæã€å¶éïŒäžéå€ããããå€ãªã©ïŒã¯ãã©ã¡ã¿ãšããŠå€ããèšå®å¯èœã«ãã
ãããâãšåãã£ã¡ãåãã§ãã¯ã©ã¹å ã«äœããã®å¶éå€ïŒäŸïŒäžéå€ãäžéå€ãªã©ïŒãããŒãã³ãŒãã§æããã¡ãããšã ãã¹ãã®ãšãã«ãæ¬åœã«ãã®å¶éå€ã«åŸã£ãäºåããŒã¿ããããã¯å ¥åãå¿ èŠã«ãªã£ãŠããŸãã®ã§å€§å€ã å ããŠããã®å€ã«å€æŽãå ¥ã£ãå Žåãã¹ããä¿®æ£ããªããšãããªããªãã
æ¬æ¥ãã¹ãã§ã¯ããã®å¶éå€èªäœã¯ã©ãã§ããããŠããã®å¶éå€ã«åŸã£ãå¶åŸ¡ãããŸãåäœããã®ãã確èªãããã¯ãã ãã®ããããã¹ãæã«éœåãè¯ãå¶éå€ãèšå®ã§ããäœãã«ãã¹ãããšãã話ã äžåºŠèšå®ãããšæžãæãã§ããªãããã«ããã®ãçæ³ãªã®ã§ãã³ã³ã¹ãã©ã¯ã¿ã§å€ãèšå®ã§ããããã«ããŠããã°è¯ããããã ãã
(3) Instant.now() ã new Date() ã䜿ããªãããã«ãã
äŸãã°ãDBã«å€ãä¿åããã¡ãœããå
ã§ãæŽæ°æ¥æã« Instant.now() ããå€ãèšå®ããŠä¿åããšããããšã¯ãããããšæããŸãã
ããããã ãšDBã«ä¿åãããéã®æŽæ°æ¥æãã·ã¹ãã æ¥æã«äŸåããŠããŸãã®ã§ãè¯ããªãã§ãã
Javaã«ã¯ java.time.Clock ãšããã¯ã©ã¹ãçšæãããŠããããã®Clockãã€ã³ãžã§ã¯ã·ã§ã³ã§ããäœãã«ããããšã§ã
ãã¹ãæã®ã¿åºå®ã®æå»ãèšå®ãããç¶æ
ãäœããŸãã
ãã¹ããã¥ããå®è£ ãšããã¹ãããããå®è£ äŸã以äžã«ç€ºããŸãã
/**
* ãã¹ããã¥ãã...
*/
public class UnTestableDao {
...
public User updateUser(User user) {
user.setDateTime(LocalDateTime.now());
updateDatabase(user);
return user;
}
...
}/**
* ãã¹ãããããïŒ
*/
public class TestableDao {
private final Clock clock;
public TestableDao(Clock clock) {
this.clock = clock;
}
public User updateUser(User user) {
user.setDateTime(LocalDateTime.now(this.clock));
updateDatabase(user);
return user;
}
...
}äœãå°ãã®ããäœãå¬ããããå®éã®ãã¹ãã³ãŒãã以äžã«ç€ºããŸãã
/**
* ãã¹ããã¥ãã...
*/
@Test
public void updateUserTest() {
// Given
UnTestableDao dao = new UnTestableDao();
User user = createUserWithId(10L);
// When
User actualUser = dao.updateUser(user);
// Then
assertThat(actualUser.getDateTime(), is( ??? )); // æåŸ
å€ãããããªã
} /**
* ãã¹ããããã
*/
@Test
public void updateUserTest() {
// Given
Instant fixedInstant = Instant.parse("2020-10-04T00:00:00.00Z");
Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.systemDefault());
TestableDao dao = new TestableDao(fixedClock);
User user = createUserWithId(10L);
// When
User actualUser = dao.updateUser(user);
// Then
assertThat(actualUser.getDateTime(), is( LocalDateTime.ofInstant(fixedInstant, ZoneId.systemDefault()) )); // æåŸ
å€ãåºå®åããã
}(4) éåæå®è¡ãšããžãã¹ããžãã¯ã¯åé¢ãã
ããã¯åæçãªéšåãšéåæçãªéšåãæ··ãããšããšããã«ãã¹ããã¥ãããªãã®ã§ã åæãéåæãæèããŠããžãã¯ãåé¢ããŠãããã»ããè¯ãããšãã話ãããå ·äœäŸé£ãããªãã
JVMã®èšå®ãJunit5ãªã©ã®è©±
ãã£ãããŸãšãããš
- JVMã®ãªãã·ã§ã³ã«
-noverify -XX:TieredStopAtLevel=1ãªãã·ã§ã³ã€ãããšã¡ãã£ãšãã¹ãæ©ããªã assertTrue()andassertFalse()ã¯é¿ããã»ããè¯ã- ãã¹ãã®ã°ã«ãŒãå
- Mock Remote ServiceïŒHTTPã®ãã¹ãæã«httpã¬ã€ã€ãMockåã§ããæ©èœïŒ
ã«ã€ããŠèª¬æãããŠããŸãã ããã¯JavaäŸåã®è©±ãªã®ã§ãJava奜ãã®äººã¯å ã®èšäºããåç §ãã ãã….!!!