プログラミング/テスト
表示
< プログラミング
テストの基本
[編集]テストは、ソフトウェアの品質を保証し、期待通りに動作することを確認するプロセスです。効果的なテストは、バグの早期発見、コードの信頼性向上、リファクタリングの安全性確保に不可欠です。
テストの種類
[編集]ユニットテスト
[編集]個々の関数やメソッドの動作を検証します。
Java (JUnit) による例
[編集]import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class CalculatorTest { @Test void testAddition() { Calculator calculator = new Calculator(); assertEquals(5, calculator.add(2, 3), "2 + 3 should equal 5"); assertEquals(0, calculator.add(-1, 1), "Negative and positive numbers should balance"); } @Test void testDivision() { Calculator calculator = new Calculator(); assertEquals(2, calculator.divide(6, 3), "6 divided by 3 should be 2"); assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0), "Division by zero should throw exception"); } } class Calculator { public int add(int a, int b) { return a + b; } public int divide(int a, int b) { if (b == 0) { throw new ArithmeticException("Division by zero"); } return a / b; } }
モック(Mock)テスト
[編集]外部依存関係をシミュレートします。
Kotlin (Mockito) による例
[編集]import org.junit.jupiter.api.Test import org.mockito.Mockito.* import org.mockito.kotlin.mock import kotlin.test.assertEquals class UserServiceTest { @Test fun `should create user with valid data`() { // モックの作成 val userRepository = mock<UserRepository>() val emailService = mock<EmailService>() // スタブの設定 `when`(userRepository.save(any())).thenReturn(true) // テスト対象のサービス val userService = UserService(userRepository, emailService) // テスト実行 val user = User("john@example.com", "password123") val result = userService.registerUser(user) // 検証 assertTrue(result) verify(userRepository).save(user) verify(emailService).sendWelcomeEmail(user.email) } } class UserService( private val userRepository: UserRepository, private val emailService: EmailService ) { fun registerUser(user: User): Boolean { // ユーザー登録のビジネスロジック val saved = userRepository.save(user) if (saved) { emailService.sendWelcomeEmail(user.email) } return saved } } // インターフェースの定義 interface UserRepository { fun save(user: User): Boolean } interface EmailService { fun sendWelcomeEmail(email: String) }
統合テスト
[編集]異なるコンポーネント間の相互作用を検証します。
TypeScript (Jest) による例
[編集]import axios from 'axios'; import { UserService } from './UserService'; // 実際のAPIとの統合テスト describe('UserService Integration', () => { let userService: UserService; beforeEach(() => { userService = new UserService(axios); }); it('should fetch user details from API', async () => { // モック化されたAPIレスポンス const mockResponse = { data: { id: 1, name: 'John Doe', email: 'john@example.com' } }; // axiosのモック jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); // テスト実行 const user = await userService.getUserById(1); // 検証 expect(user).toEqual(mockResponse.data); expect(axios.get).toHaveBeenCalledWith('/users/1'); }); it('should handle API errors', async () => { // エラーシナリオのテスト jest.spyOn(axios, 'get').mockRejectedValue(new Error('Network Error')); // エラーハンドリングのテスト await expect(userService.getUserById(1)) .rejects .toThrow('Failed to fetch user'); }); }); // サービスクラスの実装例 class UserService { constructor(private httpClient: typeof axios) {} async getUserById(id: number) { try { const response = await this.httpClient.get(`/users/${id}`); return response.data; } catch (error) { throw new Error('Failed to fetch user'); } } }
プロパティベーステスト
[編集]ランダムな入力で広範囲のテストを行います。
Scala (ScalaCheck) による例
[編集]import org.scalacheck.Properties import org.scalacheck.Prop.forAll object MathPropertyTest extends Properties("MathProperties") { // 可換性のテスト property("addition is commutative") = forAll { (a: Int, b: Int) => a + b == b + a } // 結合法則のテスト property("addition is associative") = forAll { (a: Int, b: Int, c: Int) => (a + b) + c == a + (b + c) } // 逆元の存在のテスト property("subtraction has inverse") = forAll { (a: Int) => a - a == 0 } // 複雑な関数のプロパティテスト def sortedListProperty(list: List[Int]): Boolean = { val sorted = list.sorted sorted.length == list.length && sorted.forall(list.contains) && (sorted === sorted.distinct) } property("sorting preserves list properties") = forAll { (list: List[Int]) => sortedListProperty(list) } }
ふるまい駆動開発 (BDD)
[編集]Rust (Cucumber-like スタイル)
[編集]// 特徴ファイル Feature: User Authentication Scenario: Successful user login Given a registered user with email "user@example.com" When the user enters correct password Then the login should be successful And a session token should be generated // テストコード #[cfg(test)] mod authentication_tests { use super::*; struct AuthContext { user_email: String, password: String, login_result: Option<Result<String, AuthError>> } impl AuthContext { fn new() -> Self { AuthContext { user_email: String::new(), password: String::new(), login_result: None } } fn with_registered_user(&mut self, email: &str, password: &str) { // ユーザー登録のロジック self.user_email = email.to_string(); self.password = password.to_string(); } fn attempt_login(&mut self) { let auth_service = AuthService::new(); self.login_result = Some( auth_service.login(&self.user_email, &self.password) ); } fn assert_login_successful(&self) { assert!(self.login_result.is_some()); assert!(self.login_result.as_ref().unwrap().is_ok()); } } #[test] fn test_user_authentication() { let mut context = AuthContext::new(); // Given context.with_registered_user("user@example.com", "correct_password"); // When context.attempt_login(); // Then context.assert_login_successful(); } }
テストの設計原則
[編集]- 独立性:各テストは独立して実行可能であるべき
- 再現性:同じ入力に対して常に同じ結果を返す
- 網羅性:可能な限り多くのシナリオをカバー
- 簡潔性:テストは読みやすく、理解しやすいこと
テストの注意点
[編集]- テストコードも本番コードと同様に重要
- 過度なテストは避ける
- エッジケースを忘れずに
- テストは継続的に更新する
テストの自動化
[編集]- 継続的インテグレーション (CI) の活用
- テスト実行の自動化
- コードカバレッジの監視
まとめ
[編集]テストは単なる品質保証ツールではなく、ソフトウェア開発の重要な設計手法です。適切なテスト戦略は、コードの信頼性と保守性を大幅に向上させます。