課題15: 再利用可能ライブラリの作成
課題概要
この課題では、実用的な文字列処理とバリデーションのライブラリを作成します。適切なパッケージ構造、可視性ルール、ドキュメント、テストを含む、プロダクションレベルのGoライブラリを実装します。
マンダトリー要件
要件1: プロジェクト構造の作成(15点)
標準的なプロジェクト構造を作成してください。
go mod init github.com/yourusername/textkit
ディレクトリ構造:
textkit/
├── go.mod
├── README.md
├── cmd/
│ └── textkit/
│ └── main.go # デモアプリケーション
├── pkg/
│ ├── stringutil/
│ │ ├── stringutil.go # 文字列ユーティリティ
│ │ └── stringutil_test.go
│ └── validator/
│ ├── validator.go # バリデーション
│ └── validator_test.go
└── internal/
└── helper/
└── helper.go # 内部ヘルパー関数
要件2: 文字列ユーティリティパッケージ(25点)
ファイル: pkg/stringutil/stringutil.go
// Package stringutil provides utility functions for string manipulation.
package stringutil
import (
"strings"
"unicode"
)
// Reverse reverses the given string.
// It correctly handles multi-byte UTF-8 characters.
//
// Example:
// Reverse("hello") // returns "olleh"
// Reverse("こんにちは") // returns "はちにんこ"
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
// IsPalindrome checks if the given string is a palindrome.
// The check is case-insensitive and ignores spaces.
//
// Example:
// IsPalindrome("racecar") // returns true
// IsPalindrome("A man a plan a canal Panama") // returns true
func IsPalindrome(s string) bool {
// スペースを除去し、小文字に変換
cleaned := removeSpaces(strings.ToLower(s))
return cleaned == Reverse(cleaned)
}
// Title converts the first letter of each word to uppercase.
//
// Example:
// Title("hello world") // returns "Hello World"
func Title(s string) string {
return strings.Title(strings.ToLower(s))
}
// CamelCase converts a string to camelCase.
//
// Example:
// CamelCase("hello world") // returns "helloWorld"
// CamelCase("user_name") // returns "userName"
func CamelCase(s string) string {
// スペースとアンダースコアで分割
words := strings.FieldsFunc(s, func(r rune) bool {
return r == ' ' || r == '_' || r == '-'
})
if len(words) == 0 {
return ""
}
result := strings.ToLower(words[0])
for i := 1; i < len(words); i++ {
result += strings.Title(strings.ToLower(words[i]))
}
return result
}
// SnakeCase converts a string to snake_case.
//
// Example:
// SnakeCase("HelloWorld") // returns "hello_world"
// SnakeCase("userName") // returns "user_name"
func SnakeCase(s string) string {
var result []rune
for i, r := range s {
if unicode.IsUpper(r) {
if i > 0 {
result = append(result, '_')
}
result = append(result, unicode.ToLower(r))
} else {
result = append(result, r)
}
}
return string(result)
}
// Truncate truncates the string to the specified length.
// If the string is longer than maxLen, it adds "..." at the end.
//
// Example:
// Truncate("Hello, World!", 8) // returns "Hello..."
func Truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen <= 3 {
return s[:maxLen]
}
return s[:maxLen-3] + "..."
}
// WordCount returns the number of words in the string.
//
// Example:
// WordCount("Hello World") // returns 2
func WordCount(s string) int {
return len(strings.Fields(s))
}
// Contains checks if the string contains the substring (case-insensitive).
//
// Example:
// Contains("Hello World", "world") // returns true
func Contains(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
// removeSpaces removes all spaces from the string.
func removeSpaces(s string) string {
return strings.ReplaceAll(s, " ", "")
}
テストファイル: pkg/stringutil/stringutil_test.go
package stringutil
import "testing"
func TestReverse(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello", "olleh"},
{"Go", "oG"},
{"こんにちは", "はちにんこ"},
{"", ""},
}
for _, tt := range tests {
result := Reverse(tt.input)
if result != tt.expected {
t.Errorf("Reverse(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestIsPalindrome(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"racecar", true},
{"hello", false},
{"A man a plan a canal Panama", true},
{"", true},
}
for _, tt := range tests {
result := IsPalindrome(tt.input)
if result != tt.expected {
t.Errorf("IsPalindrome(%q) = %v; want %v", tt.input, result, tt.expected)
}
}
}
func TestCamelCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello world", "helloWorld"},
{"user_name", "userName"},
{"hello-world", "helloWorld"},
{"", ""},
}
for _, tt := range tests {
result := CamelCase(tt.input)
if result != tt.expected {
t.Errorf("CamelCase(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestSnakeCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"HelloWorld", "hello_world"},
{"userName", "user_name"},
{"HTTPServer", "h_t_t_p_server"},
{"", ""},
}
for _, tt := range tests {
result := SnakeCase(tt.input)
if result != tt.expected {
t.Errorf("SnakeCase(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestTruncate(t *testing.T) {
tests := []struct {
input string
maxLen int
expected string
}{
{"Hello, World!", 8, "Hello..."},
{"Hi", 10, "Hi"},
{"Hello", 3, "Hel"},
{"", 5, ""},
}
for _, tt := range tests {
result := Truncate(tt.input, tt.maxLen)
if result != tt.expected {
t.Errorf("Truncate(%q, %d) = %q; want %q", tt.input, tt.maxLen, result, tt.expected)
}
}
}
実装すべき内容:
Reverse: 文字列の反転(UTF-8対応)IsPalindrome: 回文判定Title: タイトルケース変換CamelCase: キャメルケース変換SnakeCase: スネークケース変換Truncate: 文字列の切り詰めWordCount: 単語数カウントContains: 大文字小文字を区別しない検索- 包括的なテスト
要件3: バリデーションパッケージ(30点)
ファイル: pkg/validator/validator.go
// Package validator provides validation functions for common data types.
package validator
import (
"errors"
"regexp"
"strings"
"unicode"
)
var (
// ErrInvalidEmail is returned when an email is invalid.
ErrInvalidEmail = errors.New("無効なメールアドレスです")
// ErrInvalidURL is returned when a URL is invalid.
ErrInvalidURL = errors.New("無効なURLです")
// ErrPasswordTooShort is returned when a password is too short.
ErrPasswordTooShort = errors.New("パスワードが短すぎます")
// ErrPasswordTooWeak is returned when a password is too weak.
ErrPasswordTooWeak = errors.New("パスワードが弱すぎます")
)
// emailRegex はメールアドレスの正規表現
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}課題15: 再利用可能ライブラリの作成
課題概要
この課題では、実用的な文字列処理とバリデーションのライブラリを作成します。適切なパッケージ構造、可視性ルール、ドキュメント、テストを含む、プロダクションレベルのGoライブラリを実装します。
マンダトリー要件
要件1: プロジェクト構造の作成(15点)
標準的なプロジェクト構造を作成してください。
go mod init github.com/yourusername/textkit
ディレクトリ構造:
textkit/
├── go.mod
├── README.md
├── cmd/
│ └── textkit/
│ └── main.go # デモアプリケーション
├── pkg/
│ ├── stringutil/
│ │ ├── stringutil.go # 文字列ユーティリティ
│ │ └── stringutil_test.go
│ └── validator/
│ ├── validator.go # バリデーション
│ └── validator_test.go
└── internal/
└── helper/
└── helper.go # 内部ヘルパー関数
要件2: 文字列ユーティリティパッケージ(25点)
ファイル: pkg/stringutil/stringutil.go
// Package stringutil provides utility functions for string manipulation.
package stringutil
import (
"strings"
"unicode"
)
// Reverse reverses the given string.
// It correctly handles multi-byte UTF-8 characters.
//
// Example:
// Reverse("hello") // returns "olleh"
// Reverse("こんにちは") // returns "はちにんこ"
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
// IsPalindrome checks if the given string is a palindrome.
// The check is case-insensitive and ignores spaces.
//
// Example:
// IsPalindrome("racecar") // returns true
// IsPalindrome("A man a plan a canal Panama") // returns true
func IsPalindrome(s string) bool {
// スペースを除去し、小文字に変換
cleaned := removeSpaces(strings.ToLower(s))
return cleaned == Reverse(cleaned)
}
// Title converts the first letter of each word to uppercase.
//
// Example:
// Title("hello world") // returns "Hello World"
func Title(s string) string {
return strings.Title(strings.ToLower(s))
}
// CamelCase converts a string to camelCase.
//
// Example:
// CamelCase("hello world") // returns "helloWorld"
// CamelCase("user_name") // returns "userName"
func CamelCase(s string) string {
// スペースとアンダースコアで分割
words := strings.FieldsFunc(s, func(r rune) bool {
return r == ' ' || r == '_' || r == '-'
})
if len(words) == 0 {
return ""
}
result := strings.ToLower(words[0])
for i := 1; i < len(words); i++ {
result += strings.Title(strings.ToLower(words[i]))
}
return result
}
// SnakeCase converts a string to snake_case.
//
// Example:
// SnakeCase("HelloWorld") // returns "hello_world"
// SnakeCase("userName") // returns "user_name"
func SnakeCase(s string) string {
var result []rune
for i, r := range s {
if unicode.IsUpper(r) {
if i > 0 {
result = append(result, '_')
}
result = append(result, unicode.ToLower(r))
} else {
result = append(result, r)
}
}
return string(result)
}
// Truncate truncates the string to the specified length.
// If the string is longer than maxLen, it adds "..." at the end.
//
// Example:
// Truncate("Hello, World!", 8) // returns "Hello..."
func Truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen <= 3 {
return s[:maxLen]
}
return s[:maxLen-3] + "..."
}
// WordCount returns the number of words in the string.
//
// Example:
// WordCount("Hello World") // returns 2
func WordCount(s string) int {
return len(strings.Fields(s))
}
// Contains checks if the string contains the substring (case-insensitive).
//
// Example:
// Contains("Hello World", "world") // returns true
func Contains(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
// removeSpaces removes all spaces from the string.
func removeSpaces(s string) string {
return strings.ReplaceAll(s, " ", "")
}
テストファイル: pkg/stringutil/stringutil_test.go
package stringutil
import "testing"
func TestReverse(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello", "olleh"},
{"Go", "oG"},
{"こんにちは", "はちにんこ"},
{"", ""},
}
for _, tt := range tests {
result := Reverse(tt.input)
if result != tt.expected {
t.Errorf("Reverse(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestIsPalindrome(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"racecar", true},
{"hello", false},
{"A man a plan a canal Panama", true},
{"", true},
}
for _, tt := range tests {
result := IsPalindrome(tt.input)
if result != tt.expected {
t.Errorf("IsPalindrome(%q) = %v; want %v", tt.input, result, tt.expected)
}
}
}
func TestCamelCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello world", "helloWorld"},
{"user_name", "userName"},
{"hello-world", "helloWorld"},
{"", ""},
}
for _, tt := range tests {
result := CamelCase(tt.input)
if result != tt.expected {
t.Errorf("CamelCase(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestSnakeCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"HelloWorld", "hello_world"},
{"userName", "user_name"},
{"HTTPServer", "h_t_t_p_server"},
{"", ""},
}
for _, tt := range tests {
result := SnakeCase(tt.input)
if result != tt.expected {
t.Errorf("SnakeCase(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestTruncate(t *testing.T) {
tests := []struct {
input string
maxLen int
expected string
}{
{"Hello, World!", 8, "Hello..."},
{"Hi", 10, "Hi"},
{"Hello", 3, "Hel"},
{"", 5, ""},
}
for _, tt := range tests {
result := Truncate(tt.input, tt.maxLen)
if result != tt.expected {
t.Errorf("Truncate(%q, %d) = %q; want %q", tt.input, tt.maxLen, result, tt.expected)
}
}
}
実装すべき内容:
Reverse: 文字列の反転(UTF-8対応)
IsPalindrome: 回文判定
Title: タイトルケース変換
CamelCase: キャメルケース変換
SnakeCase: スネークケース変換
Truncate: 文字列の切り詰め
WordCount: 単語数カウント
Contains: 大文字小文字を区別しない検索
- 包括的なテスト
要件3: バリデーションパッケージ(30点)
ファイル: pkg/validator/validator.go
)
// urlRegex はURLの正規表現
var urlRegex = regexp.MustCompile(`^https?://[a-zA-Z0-9.-]+(:[0-9]+)?(/.*)?
課題15: 再利用可能ライブラリの作成
課題概要
この課題では、実用的な文字列処理とバリデーションのライブラリを作成します。適切なパッケージ構造、可視性ルール、ドキュメント、テストを含む、プロダクションレベルのGoライブラリを実装します。
マンダトリー要件
要件1: プロジェクト構造の作成(15点)
標準的なプロジェクト構造を作成してください。
go mod init github.com/yourusername/textkit
ディレクトリ構造:
textkit/
├── go.mod
├── README.md
├── cmd/
│ └── textkit/
│ └── main.go # デモアプリケーション
├── pkg/
│ ├── stringutil/
│ │ ├── stringutil.go # 文字列ユーティリティ
│ │ └── stringutil_test.go
│ └── validator/
│ ├── validator.go # バリデーション
│ └── validator_test.go
└── internal/
└── helper/
└── helper.go # 内部ヘルパー関数
要件2: 文字列ユーティリティパッケージ(25点)
ファイル: pkg/stringutil/stringutil.go
// Package stringutil provides utility functions for string manipulation.
package stringutil
import (
"strings"
"unicode"
)
// Reverse reverses the given string.
// It correctly handles multi-byte UTF-8 characters.
//
// Example:
// Reverse("hello") // returns "olleh"
// Reverse("こんにちは") // returns "はちにんこ"
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
// IsPalindrome checks if the given string is a palindrome.
// The check is case-insensitive and ignores spaces.
//
// Example:
// IsPalindrome("racecar") // returns true
// IsPalindrome("A man a plan a canal Panama") // returns true
func IsPalindrome(s string) bool {
// スペースを除去し、小文字に変換
cleaned := removeSpaces(strings.ToLower(s))
return cleaned == Reverse(cleaned)
}
// Title converts the first letter of each word to uppercase.
//
// Example:
// Title("hello world") // returns "Hello World"
func Title(s string) string {
return strings.Title(strings.ToLower(s))
}
// CamelCase converts a string to camelCase.
//
// Example:
// CamelCase("hello world") // returns "helloWorld"
// CamelCase("user_name") // returns "userName"
func CamelCase(s string) string {
// スペースとアンダースコアで分割
words := strings.FieldsFunc(s, func(r rune) bool {
return r == ' ' || r == '_' || r == '-'
})
if len(words) == 0 {
return ""
}
result := strings.ToLower(words[0])
for i := 1; i < len(words); i++ {
result += strings.Title(strings.ToLower(words[i]))
}
return result
}
// SnakeCase converts a string to snake_case.
//
// Example:
// SnakeCase("HelloWorld") // returns "hello_world"
// SnakeCase("userName") // returns "user_name"
func SnakeCase(s string) string {
var result []rune
for i, r := range s {
if unicode.IsUpper(r) {
if i > 0 {
result = append(result, '_')
}
result = append(result, unicode.ToLower(r))
} else {
result = append(result, r)
}
}
return string(result)
}
// Truncate truncates the string to the specified length.
// If the string is longer than maxLen, it adds "..." at the end.
//
// Example:
// Truncate("Hello, World!", 8) // returns "Hello..."
func Truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen <= 3 {
return s[:maxLen]
}
return s[:maxLen-3] + "..."
}
// WordCount returns the number of words in the string.
//
// Example:
// WordCount("Hello World") // returns 2
func WordCount(s string) int {
return len(strings.Fields(s))
}
// Contains checks if the string contains the substring (case-insensitive).
//
// Example:
// Contains("Hello World", "world") // returns true
func Contains(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
// removeSpaces removes all spaces from the string.
func removeSpaces(s string) string {
return strings.ReplaceAll(s, " ", "")
}
テストファイル: pkg/stringutil/stringutil_test.go
package stringutil
import "testing"
func TestReverse(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello", "olleh"},
{"Go", "oG"},
{"こんにちは", "はちにんこ"},
{"", ""},
}
for _, tt := range tests {
result := Reverse(tt.input)
if result != tt.expected {
t.Errorf("Reverse(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestIsPalindrome(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"racecar", true},
{"hello", false},
{"A man a plan a canal Panama", true},
{"", true},
}
for _, tt := range tests {
result := IsPalindrome(tt.input)
if result != tt.expected {
t.Errorf("IsPalindrome(%q) = %v; want %v", tt.input, result, tt.expected)
}
}
}
func TestCamelCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello world", "helloWorld"},
{"user_name", "userName"},
{"hello-world", "helloWorld"},
{"", ""},
}
for _, tt := range tests {
result := CamelCase(tt.input)
if result != tt.expected {
t.Errorf("CamelCase(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestSnakeCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"HelloWorld", "hello_world"},
{"userName", "user_name"},
{"HTTPServer", "h_t_t_p_server"},
{"", ""},
}
for _, tt := range tests {
result := SnakeCase(tt.input)
if result != tt.expected {
t.Errorf("SnakeCase(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}
func TestTruncate(t *testing.T) {
tests := []struct {
input string
maxLen int
expected string
}{
{"Hello, World!", 8, "Hello..."},
{"Hi", 10, "Hi"},
{"Hello", 3, "Hel"},
{"", 5, ""},
}
for _, tt := range tests {
result := Truncate(tt.input, tt.maxLen)
if result != tt.expected {
t.Errorf("Truncate(%q, %d) = %q; want %q", tt.input, tt.maxLen, result, tt.expected)
}
}
}
実装すべき内容:
Reverse: 文字列の反転(UTF-8対応)
IsPalindrome: 回文判定
Title: タイトルケース変換
CamelCase: キャメルケース変換
SnakeCase: スネークケース変換
Truncate: 文字列の切り詰め
WordCount: 単語数カウント
Contains: 大文字小文字を区別しない検索
- 包括的なテスト
要件3: バリデーションパッケージ(30点)
ファイル: pkg/validator/validator.go
)
// ValidateEmail validates an email address.
//
// Returns ErrInvalidEmail if the email is invalid.
//
// Example:
// ValidateEmail("user@example.com") // returns nil
// ValidateEmail("invalid") // returns ErrInvalidEmail
func ValidateEmail(email string) error {
if !emailRegex.MatchString(email) {
return ErrInvalidEmail
}
return nil
}
// ValidateURL validates a URL.
//
// Returns ErrInvalidURL if the URL is invalid.
//
// Example:
// ValidateURL("https://example.com") // returns nil
// ValidateURL("invalid") // returns ErrInvalidURL
func ValidateURL(url string) error {
if !urlRegex.MatchString(url) {
return ErrInvalidURL
}
return nil
}
// PasswordStrength represents the strength of a password.
type PasswordStrength int
const (
// Weak password
Weak PasswordStrength = iota
// Medium password
Medium
// Strong password
Strong
)
// ValidatePassword validates a password.
//
// Requirements:
// - Minimum 8 characters
// - At least one uppercase letter
// - At least one lowercase letter
// - At least one digit
//
// Returns ErrPasswordTooShort or ErrPasswordTooWeak if invalid.
func ValidatePassword(password string) error {
if len(password) < 8 {
return ErrPasswordTooShort
}
var (
hasUpper bool
hasLower bool
hasDigit bool
hasSpecial bool
)
for _, r := range password {
switch {
case unicode.IsUpper(r):
hasUpper = true
case unicode.IsLower(r):
hasLower = true
case unicode.IsDigit(r):
hasDigit = true
case unicode.IsPunct(r) || unicode.IsSymbol(r):
hasSpecial = true
}
}
if !hasUpper || !hasLower || !hasDigit {
return ErrPasswordTooWeak
}
return nil
}
// CheckPasswordStrength returns the strength of a password.
func CheckPasswordStrength(password string) PasswordStrength {
if len(password) < 8 {
return Weak
}
var (
hasUpper bool
hasLower bool
hasDigit bool
hasSpecial bool
)
for _, r := range password {
switch {
case unicode.IsUpper(r):
hasUpper = true
case unicode.IsLower(r):
hasLower = true
case unicode.IsDigit(r):
hasDigit = true
case unicode.IsPunct(r) || unicode.IsSymbol(r):
hasSpecial = true
}
}
count := 0
if hasUpper {
count++
}
if hasLower {
count++
}
if hasDigit {
count++
}
if hasSpecial {
count++
}
if count >= 4 && len(password) >= 12 {
return Strong
} else if count >= 3 {
return Medium
}
return Weak
}
// ValidateUsername validates a username.
//
// Requirements:
// - 3-20 characters
// - Only alphanumeric and underscores
// - Must start with a letter
func ValidateUsername(username string) error {
if len(username) < 3 || len(username) > 20 {
return errors.New("ユーザー名は3-20文字である必要があります")
}
if !unicode.IsLetter(rune(username[0])) {
return errors.New("ユーザー名は文字で始まる必要があります")
}
for _, r := range username {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' {
return errors.New("ユーザー名は英数字とアンダースコアのみ使用できます")
}
}
return nil
}
// ValidatePhone validates a phone number.
//
// Accepts formats: 090-1234-5678, 09012345678
func ValidatePhone(phone string) error {
// ハイフンを除去
cleaned := strings.ReplaceAll(phone, "-", "")
if len(cleaned) != 10 && len(cleaned) != 11 {
return errors.New("無効な電話番号です")
}
for _, r := range cleaned {
if !unicode.IsDigit(r) {
return errors.New("電話番号は数字のみ使用できます")
}
}
return nil
}
テストファイル: pkg/validator/validator_test.go
package validator
import (
"testing"
)
func TestValidateEmail(t *testing.T) {
tests := []struct {
input string
isValid bool
}{
{"user@example.com", true},
{"test.user@example.co.jp", true},
{"invalid", false},
{"@example.com", false},
{"user@", false},
}
for _, tt := range tests {
err := ValidateEmail(tt.input)
if (err == nil) != tt.isValid {
t.Errorf("ValidateEmail(%q) error = %v; want valid = %v", tt.input, err, tt.isValid)
}
}
}
func TestValidatePassword(t *testing.T) {
tests := []struct {
input string
isValid bool
}{
{"Password123", true},
{"Pass1", false}, // too short
{"password123", false}, // no uppercase
{"PASSWORD123", false}, // no lowercase
{"Password", false}, // no digit
}
for _, tt := range tests {
err := ValidatePassword(tt.input)
if (err == nil) != tt.isValid {
t.Errorf("ValidatePassword(%q) error = %v; want valid = %v", tt.input, err, tt.isValid)
}
}
}
func TestCheckPasswordStrength(t *testing.T) {
tests := []struct {
input string
expected PasswordStrength
}{
{"Pass1", Weak},
{"Password123", Medium},
{"P@ssw0rd123!", Strong},
}
for _, tt := range tests {
result := CheckPasswordStrength(tt.input)
if result != tt.expected {
t.Errorf("CheckPasswordStrength(%q) = %v; want %v", tt.input, result, tt.expected)
}
}
}
func TestValidateUsername(t *testing.T) {
tests := []struct {
input string
isValid bool
}{
{"user123", true},
{"ab", false}, // too short
{"123user", false}, // starts with digit
{"user@name", false}, // invalid character
{"valid_user", true},
}
for _, tt := range tests {
err := ValidateUsername(tt.input)
if (err == nil) != tt.isValid {
t.Errorf("ValidateUsername(%q) error = %v; want valid = %v", tt.input, err, tt.isValid)
}
}
}
実装すべき内容:
ValidateEmail: メールアドレス検証ValidateURL: URL検証ValidatePassword: パスワード検証CheckPasswordStrength: パスワード強度チェックValidateUsername: ユーザー名検証ValidatePhone: 電話番号検証- 包括的なテスト
要件4: 内部ヘルパーパッケージ(10点)
ファイル: internal/helper/helper.go
// Package helper provides internal utility functions.
// This package is not accessible from outside the module.
package helper
import "unicode"
// IsAlphanumeric checks if all characters are alphanumeric.
func IsAlphanumeric(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
return false
}
}
return true
}
// CountChars counts the number of characters by type.
func CountChars(s string) (letters, digits, others int) {
for _, r := range s {
switch {
case unicode.IsLetter(r):
letters++
case unicode.IsDigit(r):
digits++
default:
others++
}
}
return
}
要件5: デモアプリケーション(20点)
ファイル: cmd/textkit/main.go
package main
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/yourusername/textkit/pkg/stringutil"
"github.com/yourusername/textkit/pkg/validator"
)
func main() {
fmt.Println("=== TextKit Demo ===")
fmt.Println()
demoStringUtil()
fmt.Println()
demoValidator()
}
func demoStringUtil() {
fmt.Println("--- String Utilities ---")
text := "hello world"
fmt.Printf("Original: %s\n", text)
fmt.Printf("Reversed: %s\n", stringutil.Reverse(text))
fmt.Printf("Title: %s\n", stringutil.Title(text))
fmt.Printf("CamelCase: %s\n", stringutil.CamelCase(text))
fmt.Printf("Word Count: %d\n", stringutil.WordCount(text))
palindrome := "racecar"
fmt.Printf("\nIs '%s' a palindrome? %v\n", palindrome, stringutil.IsPalindrome(palindrome))
long := "This is a very long string that needs truncation"
fmt.Printf("\nTruncated: %s\n", stringutil.Truncate(long, 20))
}
func demoValidator() {
fmt.Println("--- Validators ---")
reader := bufio.NewReader(os.Stdin)
// Email validation
fmt.Print("\nメールアドレスを入力してください: ")
email, _ := reader.ReadString('\n')
email = strings.TrimSpace(email)
if err := validator.ValidateEmail(email); err != nil {
fmt.Printf("❌ %v\n", err)
} else {
fmt.Println("✅ 有効なメールアドレスです")
}
// Password validation
fmt.Print("\nパスワードを入力してください: ")
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
if err := validator.ValidatePassword(password); err != nil {
fmt.Printf("❌ %v\n", err)
} else {
strength := validator.CheckPasswordStrength(password)
strengthText := map[validator.PasswordStrength]string{
validator.Weak: "弱い",
validator.Medium: "普通",
validator.Strong: "強い",
}
fmt.Printf("✅ 有効なパスワードです (強度: %s)\n", strengthText[strength])
}
}
実装すべき内容:
- ライブラリの機能をデモ
- インタラクティブな入力
- 結果の表示
期待される出力
$ go run cmd/textkit/main.go
=== TextKit Demo ===
--- String Utilities ---
Original: hello world
Reversed: dlrow olleh
Title: Hello World
CamelCase: helloWorld
Word Count: 2
Is 'racecar' a palindrome? true
Truncated: This is a very lo...
--- Validators ---
メールアドレスを入力してください: user@example.com
✅ 有効なメールアドレスです
パスワードを入力してください: Password123!
✅ 有効なパスワードです (強度: 強い)
ボーナス課題
> ボーナス: これらはオプションです。マンダトリー部分が完了してから取り組んでください。
ボーナス1: READMEとドキュメント(10点)
包括的なREADME.mdを作成してください。
# TextKit
A comprehensive Go library for string manipulation and validation.
## Installation
go get github.com/yourusername/textkit
## Usage
### String Utilities
// ... examples
### Validators
// ... examples
## API Documentation
Visit [pkg.go.dev](https://pkg.go.dev/github.com/yourusername/textkit)
## Testing
go test ./...
## License
MIT
ボーナス2: ベンチマーク(5点)
パフォーマンステストを追加してください。
func BenchmarkReverse(b *testing.B) {
s := "hello world"
for i := 0; i < b.N; i++ {
Reverse(s)
}
}
ボーナス3: CLI ツール(5点)
コマンドラインツールを実装してください。
$ textkit reverse "hello"
olleh
$ textkit validate-email user@example.com
✅ Valid email
評価基準
| 項目 | 配点 | 詳細 |
|---|---|---|
| プロジェクト構造 | 15点 | 適切なディレクトリ構成 |
| 文字列ユーティリティ | 25点 | 全機能が正しく動作する |
| バリデーション | 30点 | 包括的な検証機能 |
| 内部ヘルパー | 10点 | internal/が適切に使われている |
| デモアプリケーション | 20点 | ライブラリの使い方を示している |
| **ボーナス1** | 10点 | 詳細なドキュメント |
| **ボーナス2** | 5点 | ベンチマークテスト |
| **ボーナス3** | 5点 | CLIツール |
提出方法
以下の構造で提出してください:
textkit/
├── go.mod
├── go.sum (if any)
├── README.md
├── cmd/
│ └── textkit/
│ └── main.go
├── pkg/
│ ├── stringutil/
│ │ ├── stringutil.go
│ │ └── stringutil_test.go
│ └── validator/
│ ├── validator.go
│ └── validator_test.go
└── internal/
└── helper/
└── helper.go
ヒント
go test -v ./... で全テスト実行go test -cover ./... で確認go mod tidy で依存関係を整理