Spring Boot-1 Spring Boot RepositoryとService Layerテスト
76626 ワード
現在、多くの会社がREST方式のAPIを使用する傾向にある.
これは,我々がマイクロサービスアーキテクチャを採用し,Front SideとBackEnd Sideを完全に分離したためである.
まずサービス・レイヤを作成し、テストを行います.
1.依存性の追加
ビジネスロジック(このセクションでは、ドメインがビジネスロジックの責任者であると考えている本が多く、トビーのspringでは、ビジネスロジックはサービス層が担当することが望ましい.アプリケーションは十分に処理できるが、データベースをデータベースに渡すとデータベースの負荷が増加する.) 事務管理主体 まだいくつかあるかもしれませんが、上の2つだと思います.
まず、ビジネスロジックについてユニットテストを行います.
UserServiceImplは、UserRepositoryに依存します.
しかし、ユニットテストは対応する方法に集中しなければならない.他の依存性を直接注入してテストすることを避ける.
したがって、これらの依存性がある場合は、これらのライブラリを使用できます.
テスト時にターゲットオブジェクトを使用してテストを支援するライブラリ.
スプリングのデフォルトで提供されるライブラリバージョンにはホットスポットが含まれています.これは依存性を変える.
コードの理解を助けるために、いくつかの説明を追加します.
ターゲットオブジェクトを作成します.
依存性を注入するターゲットオブジェクトを作成します.
メソッドの条件.
given(userRepository.findByEmail(user.getEmail())) : userRepository.findByEmail(user.getEmail()を呼び出すと
willReturn(Optional.empty()) : Optional.空に戻る()
verify(userRepository).findByEmail(anyString()); : userRepositoryがfindByEmailを呼び出すかどうかを確認します
既存のユーザー・レポートを考慮しない場合は、サービス・レイヤのテストのみが作成されます.
ユーザーが正常に稼働していることを報告し、サービス・レベルのテストを実施すると仮定します.
このように孤立したユニットテストを行ってこそ、完全なビジネスロジックテストに集中することができます.
次のヒントを続けましょう
これは,我々がマイクロサービスアーキテクチャを採用し,Front SideとBackEnd Sideを完全に分離したためである.
まずサービス・レイヤを作成し、テストを行います.
1.依存性の追加 implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'com.h2database:h2'
'org.springframework.boot:spring-boot-start-web":Web関連モジュール
'org.springframework.boot:spring-boot-start-data-jpa":jpa関連モジュール
'com.h 2 database:h 2':組み込みデータベースを使用し、テスト環境でも使用
依存項目を追加して実験を行います.
2.h 2接続を確認する
データベースに簡単なデータを格納し、再度クエリーを行い、入力値と一致する値があるかどうかをテストします.
2.1ユーザーエンティティの作成
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String password;
}
2.1.1 @Entity
spring-boot-starter-data-jpa
依存項目を追加し、@Entity
を追加すると、テーブルとJavaクラスがマッピングされます.
2.1.2 @Id
テーブルの識別子を設定します.メインキーになるでしょう
2.1.3 @GeneratedValue
ポリシーはGenerationTypeです.AUTOで作るデータベースの読み込み時に自動的に管理されます.
2.1.4 @AllArgsConstructor , @NoArgsConstructor
すべてのパラメータのタイプの作成者を作成します.
パラメータのないジェネレータを作成します.
2.2ユーザーレポートの作成
import com.plee.auth.domain.User;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Springが提供する抽象インタフェースは、データベースの一貫した方法でアクセスできます.
2.3 RepositoryTestの作成
import com.plee.auth.domain.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Optional;
@ExtendWith(SpringExtension.class)
@DataJpaTest
@ActiveProfiles(value = "dev")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void saveUserTest() {
User user = new User(null,"name", "password");
User savedUser = userRepository.save(user);
Assertions.assertEquals(user.getEmail(), savedUser.getEmail(),
"saveUserTest");
Assertions.assertEquals(user.getPassword(), savedUser.getPassword(),
"saveUserTest");
}
@Test
public void findByEmailSuccessTest() {
User user = new User(null, "name1", "password");
User savedUser = userRepository.save(user);
Optional<User> userFindByEmail = userRepository.findByEmail(user.getEmail());
userFindByEmail.ifPresent(value -> Assertions.assertEquals(savedUser.getEmail(), value.getEmail()));
}
@Test
public void findByEmailFailureTest() {
Optional<User> userFindByEmail = userRepository.findByEmail("not exist email");
Assertions.assertEquals(Optional.empty(), userFindByEmail);
}
@Test
public void idStrategyTest() {
User user1 = new User(null, "name1", "password");
User user2 = new User(null,"name2", "password");
User savedUser1 = userRepository.save(user1);
User savedUser2 = userRepository.save(user2);
Assertions.assertEquals(1, Math.abs(savedUser1.getId() - savedUser2.getId()));
}
}
ユーザーの作成と保存をテストしてみましょう.
2.3.1 @DataJpaTest
初めて登場したマネです
次に、JPAに関するテスト設定のみをロードできます.
DataSourceの設定やJPAに関するテストを実行できます.
2.3.2 saveUserTest
新しいユーザーを作成し、保存したユーザーと同じであることを確認します.
2.3.3 findByNameTest
ユーザー・レポートの作成時にメソッドを追加し、保存した名前でメソッドが見つかったかどうかをテストします.
2.3.4 idStrategyTest
Databaseにidの生成を管理させます.従って、連続して格納される2つのデータのid値は1差である.
もちろん、本番環境で適切なIsolationが指定されていない場合は、テストが正常に動作しない可能性があります.
user 1を保存するときに別のuserを保存する人がいる場合、差は2になる可能性があります.
3. UserService
まずサービスレイヤーを作成します.
3.1 UserService Interface
import com.plee.auth.domain.User;
public interface UserService {
User add(User user);
User get(Long id);
User update(User user);
boolean delete(Long id);
User findByEmail(String email);
}
その後、コントローラは、複数のユーザサービスを使用するためのインタフェースを作成します.実際,これらのサービスはあまり変化していないが,インタフェースを作成する習慣を身につけることで依存性を低減することはメンテナンスにおいて非常に重要な役割を果たす.
3.2 UserServiceImpl
import com.plee.auth.domain.User;
import com.plee.auth.exception.UserExistedException;
import com.plee.auth.exception.UserNotFoundException;
import com.plee.auth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
final boolean DELETE_SUCCESS = true;
final boolean DELETE_FAILED = false;
private UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User add(User user) {
if (userRepository.findByEmail(user.getEmail()).isPresent())
throw new UserExistedException("User is already joined : "+user.getEmail());
else
return userRepository.save(user);
}
@Override
public User get(Long id) {
return userRepository.findById(id).orElseThrow(
() -> new UserNotFoundException("delete: User not found by : " + id));
}
@Override
public User update(User user) {
if (userRepository.findById(user.getId()).isPresent())
return userRepository.save(user);
else
throw new UserNotFoundException("update: User not found by : " + user.getId());
}
@Override
public boolean delete(Long id) {
userRepository.deleteById(id);
if (!userRepository.findById(id).isPresent())
return DELETE_SUCCESS;
else
return DELETE_FAILED;
}
@Override
public User findByEmail(String email) {
return userRepository.findByEmail(email).orElseThrow(
() -> new UserNotFoundException("findbyEmail: User not found by : " + email));
}
}
ユーザーレポートを入力して、各メソッドを実装します.
3.2.1 CustomException
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.CONFLICT)
public class UserExistedException extends RuntimeException{
public UserExistedException(String message) {
super(message);
}
}
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException{
public UserNotFoundException(String message) {
super(message);
}
}
サービス層に使用するExceptionを作成します.
@ResponseStatus定義後、コントローラテストにエラーが発生した場合、どのHttpStatusが返されますか.
3.3 UserServiceImplTest
通常、各レイヤをテストするときは、クラスが持つ役割を考慮する必要があります.
通常、サービス・レベルには次の役割があります.
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'com.h2database:h2'
データベースに簡単なデータを格納し、再度クエリーを行い、入力値と一致する値があるかどうかをテストします.
2.1ユーザーエンティティの作成
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String password;
}
2.1.1 @Entity
spring-boot-starter-data-jpa
依存項目を追加し、@Entity
を追加すると、テーブルとJavaクラスがマッピングされます.2.1.2 @Id
テーブルの識別子を設定します.メインキーになるでしょう
2.1.3 @GeneratedValue
ポリシーはGenerationTypeです.AUTOで作るデータベースの読み込み時に自動的に管理されます.
2.1.4 @AllArgsConstructor , @NoArgsConstructor
すべてのパラメータのタイプの作成者を作成します.
パラメータのないジェネレータを作成します.
2.2ユーザーレポートの作成
import com.plee.auth.domain.User;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Springが提供する抽象インタフェースは、データベースの一貫した方法でアクセスできます.2.3 RepositoryTestの作成
import com.plee.auth.domain.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Optional;
@ExtendWith(SpringExtension.class)
@DataJpaTest
@ActiveProfiles(value = "dev")
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void saveUserTest() {
User user = new User(null,"name", "password");
User savedUser = userRepository.save(user);
Assertions.assertEquals(user.getEmail(), savedUser.getEmail(),
"saveUserTest");
Assertions.assertEquals(user.getPassword(), savedUser.getPassword(),
"saveUserTest");
}
@Test
public void findByEmailSuccessTest() {
User user = new User(null, "name1", "password");
User savedUser = userRepository.save(user);
Optional<User> userFindByEmail = userRepository.findByEmail(user.getEmail());
userFindByEmail.ifPresent(value -> Assertions.assertEquals(savedUser.getEmail(), value.getEmail()));
}
@Test
public void findByEmailFailureTest() {
Optional<User> userFindByEmail = userRepository.findByEmail("not exist email");
Assertions.assertEquals(Optional.empty(), userFindByEmail);
}
@Test
public void idStrategyTest() {
User user1 = new User(null, "name1", "password");
User user2 = new User(null,"name2", "password");
User savedUser1 = userRepository.save(user1);
User savedUser2 = userRepository.save(user2);
Assertions.assertEquals(1, Math.abs(savedUser1.getId() - savedUser2.getId()));
}
}
ユーザーの作成と保存をテストしてみましょう.2.3.1 @DataJpaTest
初めて登場したマネです
次に、JPAに関するテスト設定のみをロードできます.
DataSourceの設定やJPAに関するテストを実行できます.
2.3.2 saveUserTest
新しいユーザーを作成し、保存したユーザーと同じであることを確認します.
2.3.3 findByNameTest
ユーザー・レポートの作成時にメソッドを追加し、保存した名前でメソッドが見つかったかどうかをテストします.
2.3.4 idStrategyTest
Databaseにidの生成を管理させます.従って、連続して格納される2つのデータのid値は1差である.
もちろん、本番環境で適切なIsolationが指定されていない場合は、テストが正常に動作しない可能性があります.
user 1を保存するときに別のuserを保存する人がいる場合、差は2になる可能性があります.
3. UserService
まずサービスレイヤーを作成します.
3.1 UserService Interface
import com.plee.auth.domain.User;
public interface UserService {
User add(User user);
User get(Long id);
User update(User user);
boolean delete(Long id);
User findByEmail(String email);
}
その後、コントローラは、複数のユーザサービスを使用するためのインタフェースを作成します.実際,これらのサービスはあまり変化していないが,インタフェースを作成する習慣を身につけることで依存性を低減することはメンテナンスにおいて非常に重要な役割を果たす.
3.2 UserServiceImpl
import com.plee.auth.domain.User;
import com.plee.auth.exception.UserExistedException;
import com.plee.auth.exception.UserNotFoundException;
import com.plee.auth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
final boolean DELETE_SUCCESS = true;
final boolean DELETE_FAILED = false;
private UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User add(User user) {
if (userRepository.findByEmail(user.getEmail()).isPresent())
throw new UserExistedException("User is already joined : "+user.getEmail());
else
return userRepository.save(user);
}
@Override
public User get(Long id) {
return userRepository.findById(id).orElseThrow(
() -> new UserNotFoundException("delete: User not found by : " + id));
}
@Override
public User update(User user) {
if (userRepository.findById(user.getId()).isPresent())
return userRepository.save(user);
else
throw new UserNotFoundException("update: User not found by : " + user.getId());
}
@Override
public boolean delete(Long id) {
userRepository.deleteById(id);
if (!userRepository.findById(id).isPresent())
return DELETE_SUCCESS;
else
return DELETE_FAILED;
}
@Override
public User findByEmail(String email) {
return userRepository.findByEmail(email).orElseThrow(
() -> new UserNotFoundException("findbyEmail: User not found by : " + email));
}
}
ユーザーレポートを入力して、各メソッドを実装します.
3.2.1 CustomException
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.CONFLICT)
public class UserExistedException extends RuntimeException{
public UserExistedException(String message) {
super(message);
}
}
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException{
public UserNotFoundException(String message) {
super(message);
}
}
サービス層に使用するExceptionを作成します.
@ResponseStatus定義後、コントローラテストにエラーが発生した場合、どのHttpStatusが返されますか.
3.3 UserServiceImplTest
通常、各レイヤをテストするときは、クラスが持つ役割を考慮する必要があります.
通常、サービス・レベルには次の役割があります.
import com.plee.auth.domain.User;
public interface UserService {
User add(User user);
User get(Long id);
User update(User user);
boolean delete(Long id);
User findByEmail(String email);
}
import com.plee.auth.domain.User;
import com.plee.auth.exception.UserExistedException;
import com.plee.auth.exception.UserNotFoundException;
import com.plee.auth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
final boolean DELETE_SUCCESS = true;
final boolean DELETE_FAILED = false;
private UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User add(User user) {
if (userRepository.findByEmail(user.getEmail()).isPresent())
throw new UserExistedException("User is already joined : "+user.getEmail());
else
return userRepository.save(user);
}
@Override
public User get(Long id) {
return userRepository.findById(id).orElseThrow(
() -> new UserNotFoundException("delete: User not found by : " + id));
}
@Override
public User update(User user) {
if (userRepository.findById(user.getId()).isPresent())
return userRepository.save(user);
else
throw new UserNotFoundException("update: User not found by : " + user.getId());
}
@Override
public boolean delete(Long id) {
userRepository.deleteById(id);
if (!userRepository.findById(id).isPresent())
return DELETE_SUCCESS;
else
return DELETE_FAILED;
}
@Override
public User findByEmail(String email) {
return userRepository.findByEmail(email).orElseThrow(
() -> new UserNotFoundException("findbyEmail: User not found by : " + email));
}
}
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.CONFLICT)
public class UserExistedException extends RuntimeException{
public UserExistedException(String message) {
super(message);
}
}
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException{
public UserNotFoundException(String message) {
super(message);
}
}
まず、ビジネスロジックについてユニットテストを行います.
3.3.1ユーザーServiceImplデバイステストの依存性の変更
UserServiceImplは、UserRepositoryに依存します.
しかし、ユニットテストは対応する方法に集中しなければならない.他の依存性を直接注入してテストすることを避ける.
したがって、これらの依存性がある場合は、これらのライブラリを使用できます.
3.3.1.1 Mockito
テスト時にターゲットオブジェクトを使用してテストを支援するライブラリ.
スプリングのデフォルトで提供されるライブラリバージョンにはホットスポットが含まれています.これは依存性を変える.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.1'
既存の5.2版では、いくつかの問題があります.3.3.2 UserServiceImplTest
import com.plee.auth.domain.User;
import com.plee.auth.exception.UserNotFoundException;
import com.plee.auth.repository.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class UserServiceImplTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService = new UserServiceImpl(this.userRepository);
@Test
void addUserSuccessfully() {
final User user = new User(null, "name", "password");
given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.empty());
given(userRepository.save(user)).willReturn(user);
User savedUser = userService.add(user);
Assertions.assertNotNull(savedUser);
verify(userRepository).findByEmail(anyString());
verify(userRepository).save(any(User.class));
}
@Test
void addUserFailure() {
final User user = new User(1L, "name", "password");
given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.of(user));
Assertions.assertThrows(UserNotFoundException.class, () -> userService.add(user));
verify(userRepository, never()).save(any(User.class));
}
@Test
void getUserSuccessfully() {
final User user = new User(1L, "name", "password");
given(userRepository.findById(user.getId())).willReturn(Optional.of(user));
User userByGet = userService.get(user.getId());
Assertions.assertEquals(user, userByGet);
verify(userRepository).findById(anyLong());
}
@Test
void getUserFailure() {
given(userRepository.findById(anyLong())).willReturn(Optional.empty());
Assertions.assertThrows(UserNotFoundException.class, () -> userService.get(anyLong()));
verify(userRepository).findById(anyLong());
}
@Test
void updateSuccessfully() {
final User user = new User(1L, "name", "password");
given(userRepository.findById(user.getId())).willReturn(Optional.of(user));
given(userRepository.save(user)).willReturn(user);
User updatedUser = userService.update(user);
Assertions.assertNotNull(updatedUser);
verify(userRepository).save(any(User.class));
}
@Test
void updateFailure() {
final User user = new User(1L, "name", "password");
given(userRepository.findById(anyLong())).willReturn(Optional.empty());
Assertions.assertThrows(UserNotFoundException.class, () -> userService.update(user));
verify(userRepository).findById(anyLong());
}
@Test
void deleteSuccessfully() {
final Long id = 1L;
userService.delete(id);
verify(userRepository, times(1)).deleteById(anyLong());
verify(userRepository, times(1)).findById(anyLong());
}
@Test
void findByEmailSuccessfully() {
final User user = new User(1L, "name", "password");
given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.of(user));
User userByEmail = userService.findByEmail(user.getEmail());
Assertions.assertEquals(user, userByEmail);
verify(userRepository).findByEmail(any(String.class));
}
@Test
void findByEmailFailure() {
final String email = "email";
given(userRepository.findByEmail(email)).willReturn(Optional.empty());
Assertions.assertThrows(UserNotFoundException.class, () -> userService.findByEmail(email));
verify(userRepository).findByEmail(any(String.class));
}
}
3.3.2.1 Mockオブジェクトを使用したテスト
コードの理解を助けるために、いくつかの説明を追加します.
@Mock
ターゲットオブジェクトを作成します.
@InjectMock
依存性を注入するターゲットオブジェクトを作成します.
3.2.2.2コードで使用する方法を見てみましょう。
@Test
void addUserSuccessfully() {
final User user = new User(null, "name", "password");
given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.empty());
given(userRepository.save(user)).willReturn(user);
User savedUser = userService.add(user);
Assertions.assertNotNull(savedUser);
verify(userRepository).findByEmail(anyString());
verify(userRepository).save(any(User.class));
}
given
メソッドの条件.
given(userRepository.findByEmail(user.getEmail())) : userRepository.findByEmail(user.getEmail()を呼び出すと
willReturn(Optional.empty()) : Optional.空に戻る()
verify(userRepository).findByEmail(anyString()); : userRepositoryがfindByEmailを呼び出すかどうかを確認します
3.3.3ユニットテスト
既存のユーザー・レポートを考慮しない場合は、サービス・レイヤのテストのみが作成されます.
ユーザーが正常に稼働していることを報告し、サービス・レベルのテストを実施すると仮定します.
このように孤立したユニットテストを行ってこそ、完全なビジネスロジックテストに集中することができます.
次のヒントを続けましょう
Reference
この問題について(Spring Boot-1 Spring Boot RepositoryとService Layerテスト), 我々は、より多くの情報をここで見つけました https://velog.io/@devsh/스프링-부트-1-Spring-Boot-Repository-and-Service-Layer-Testingテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol