Skip to content

Commit 4cef135

Browse files
committed
feat: Optimize code and cover more test cases
✨ 主要改进: - 总体覆盖率从78.3%提升至94.3% (+16%) - 新增20+个测试函数,覆盖50+个边界情况 - 全面测试错误处理和异常场景 🔧 具体改进: - count.go: 添加无效输入类型测试 (87.5%) - file.go: 添加空文件、权限错误测试 (93.3%) - server.go: 添加空请求体、无效JSON测试 - export.go: 添加无文件名、空数据导出测试 (80-85.7%) - helper.go: 添加路径处理边界情况测试 (75-87.5%) - dir.go: 添加并发处理、相对路径测试 (90.9%) - ignore.go: 添加混合内容解析测试 (92.3%) - errors.go: 添加复杂错误上下文测试 (100%) 🎯 新增测试场景: - ✅ 空文件处理 - ✅ 文件权限错误 - ✅ 无效输入类型 - ✅ 网络请求错误 - ✅ 并发处理边界情况 - ✅ 路径解析错误 - ✅ 文件导出错误 - ✅ CLI集成测试 📊 覆盖率达到100%的函数: - getTotal (helper.go) - Run (server.go) - IsIgnored & IsIgnoredWithError (dir.go) - WithContext (errors.go) - 所有cmd_utils.go函数 - 所有错误构造函数
1 parent bb9652e commit 4cef135

15 files changed

Lines changed: 1138 additions & 68 deletions

.gitignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,6 @@ test_output*
114114
counter.xlsx
115115
*.csv
116116
*.xlsx
117-
test.csv
118-
test.xlsx
119117

120118
# Coverage files
121-
coverage.out
122-
coverage.html
119+
coverage*

cmd_utils_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,220 @@ func TestCounterExporter_Export(t *testing.T) {
196196
})
197197
}
198198
}
199+
200+
// TestCounterExporter_ExportErrorHandling tests error handling in export functions
201+
func TestCounterExporter_ExportErrorHandling(t *testing.T) {
202+
// Create a test file counter
203+
tmpFile, err := os.CreateTemp("", "test_export_error")
204+
if err != nil {
205+
t.Fatalf("Failed to create temp file: %v", err)
206+
}
207+
defer os.Remove(tmpFile.Name())
208+
209+
content := "测试内容 test content"
210+
if err := os.WriteFile(tmpFile.Name(), []byte(content), 0644); err != nil {
211+
t.Fatalf("Failed to write test file: %v", err)
212+
}
213+
214+
counter := wcg.NewFileCounter(tmpFile.Name())
215+
if err := counter.Count(); err != nil {
216+
t.Fatalf("Failed to count: %v", err)
217+
}
218+
219+
tests := []struct {
220+
name string
221+
exportType string
222+
path string
223+
wantErr bool
224+
setupFunc func() func() // setup function that returns cleanup function
225+
}{
226+
{
227+
name: "CSV export to invalid directory",
228+
exportType: "csv",
229+
path: "/invalid/directory/test.csv",
230+
wantErr: true,
231+
setupFunc: func() func() { return func() {} },
232+
},
233+
{
234+
name: "Excel export to invalid directory",
235+
exportType: "excel",
236+
path: "/invalid/directory/test.xlsx",
237+
wantErr: true,
238+
setupFunc: func() func() { return func() {} },
239+
},
240+
{
241+
name: "CSV export to read-only directory",
242+
exportType: "csv",
243+
path: "/tmp/readonly/test.csv",
244+
wantErr: true,
245+
setupFunc: func() func() {
246+
// Create read-only directory
247+
if err := os.MkdirAll("/tmp/readonly", 0444); err != nil {
248+
return func() {}
249+
}
250+
return func() { os.RemoveAll("/tmp/readonly") }
251+
},
252+
},
253+
{
254+
name: "Excel export to read-only directory",
255+
exportType: "excel",
256+
path: "/tmp/readonly_excel/test.xlsx",
257+
wantErr: true,
258+
setupFunc: func() func() {
259+
// Create read-only directory
260+
if err := os.MkdirAll("/tmp/readonly_excel", 0444); err != nil {
261+
return func() {}
262+
}
263+
return func() { os.RemoveAll("/tmp/readonly_excel") }
264+
},
265+
},
266+
}
267+
268+
for _, tt := range tests {
269+
t.Run(tt.name, func(t *testing.T) {
270+
cleanup := tt.setupFunc()
271+
defer cleanup()
272+
273+
config := wcg.ExportConfig{
274+
Type: tt.exportType,
275+
Path: tt.path,
276+
}
277+
exporter := wcg.NewCounterExporter(counter, config)
278+
err := exporter.Export()
279+
if (err != nil) != tt.wantErr {
280+
t.Errorf("CounterExporter.Export() error = %v, wantErr %v", err, tt.wantErr)
281+
}
282+
283+
// Clean up any created files
284+
if !tt.wantErr {
285+
os.Remove(tt.path)
286+
}
287+
})
288+
}
289+
}
290+
291+
// TestCounterExporter_ExportWithEmptyData tests export with empty data
292+
func TestCounterExporter_ExportWithEmptyData(t *testing.T) {
293+
// Create an empty file
294+
tmpFile, err := os.CreateTemp("", "test_empty_export")
295+
if err != nil {
296+
t.Fatalf("Failed to create temp file: %v", err)
297+
}
298+
defer os.Remove(tmpFile.Name())
299+
300+
// Create counter with empty file
301+
counter := wcg.NewFileCounter(tmpFile.Name())
302+
if err := counter.Count(); err != nil {
303+
t.Fatalf("Failed to count: %v", err)
304+
}
305+
306+
tests := []struct {
307+
name string
308+
exportType string
309+
path string
310+
wantErr bool
311+
}{
312+
{
313+
name: "CSV export with empty data",
314+
exportType: "csv",
315+
path: "empty_test.csv",
316+
wantErr: false,
317+
},
318+
{
319+
name: "Excel export with empty data",
320+
exportType: "excel",
321+
path: "empty_test.xlsx",
322+
wantErr: false,
323+
},
324+
{
325+
name: "Table export with empty data",
326+
exportType: "table",
327+
path: "",
328+
wantErr: false,
329+
},
330+
}
331+
332+
for _, tt := range tests {
333+
t.Run(tt.name, func(t *testing.T) {
334+
config := wcg.ExportConfig{
335+
Type: tt.exportType,
336+
Path: tt.path,
337+
}
338+
exporter := wcg.NewCounterExporter(counter, config)
339+
err := exporter.Export()
340+
if (err != nil) != tt.wantErr {
341+
t.Errorf("CounterExporter.Export() error = %v, wantErr %v", err, tt.wantErr)
342+
}
343+
344+
// Clean up created files
345+
if !tt.wantErr && tt.path != "" {
346+
os.Remove(tt.path)
347+
}
348+
})
349+
}
350+
}
351+
352+
// TestCounterExporter_ExportPathEdgeCases tests edge cases with export paths
353+
func TestCounterExporter_ExportPathEdgeCases(t *testing.T) {
354+
// Create a test file counter
355+
tmpFile, err := os.CreateTemp("", "test_path_edge")
356+
if err != nil {
357+
t.Fatalf("Failed to create temp file: %v", err)
358+
}
359+
defer os.Remove(tmpFile.Name())
360+
361+
content := "测试 test"
362+
if err := os.WriteFile(tmpFile.Name(), []byte(content), 0644); err != nil {
363+
t.Fatalf("Failed to write test file: %v", err)
364+
}
365+
366+
counter := wcg.NewFileCounter(tmpFile.Name())
367+
if err := counter.Count(); err != nil {
368+
t.Fatalf("Failed to count: %v", err)
369+
}
370+
371+
tests := []struct {
372+
name string
373+
exportType string
374+
path string
375+
wantErr bool
376+
}{
377+
{
378+
name: "CSV export with relative path",
379+
exportType: "csv",
380+
path: "./relative_test.csv",
381+
wantErr: false,
382+
},
383+
{
384+
name: "Excel export with relative path",
385+
exportType: "excel",
386+
path: "./relative_test.xlsx",
387+
wantErr: false,
388+
},
389+
{
390+
name: "CSV export with long filename",
391+
exportType: "csv",
392+
path: "very_long_filename_that_should_still_work_fine_test.csv",
393+
wantErr: false,
394+
},
395+
}
396+
397+
for _, tt := range tests {
398+
t.Run(tt.name, func(t *testing.T) {
399+
config := wcg.ExportConfig{
400+
Type: tt.exportType,
401+
Path: tt.path,
402+
}
403+
exporter := wcg.NewCounterExporter(counter, config)
404+
err := exporter.Export()
405+
if (err != nil) != tt.wantErr {
406+
t.Errorf("CounterExporter.Export() error = %v, wantErr %v", err, tt.wantErr)
407+
}
408+
409+
// Clean up created files
410+
if !tt.wantErr {
411+
os.Remove(tt.path)
412+
}
413+
})
414+
}
415+
}

count.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,20 @@ import (
3838
// Counter provides character counting functionality for text content.
3939
// It implements the CharacterCounter interface and tracks statistics
4040
// including lines, Chinese characters, non-Chinese characters, and total characters.
41+
// Stats is embedded to allow direct access to statistical fields.
4142
type Counter struct {
42-
S *Stats // Statistics collected during counting
43+
*Stats // Embedded statistics for direct field access
4344
}
4445

4546
// NewCounter creates a new Counter instance with initialized statistics.
4647
// The returned counter is ready to use for counting operations.
4748
func NewCounter() *Counter {
48-
return &Counter{S: &Stats{}}
49+
return &Counter{Stats: &Stats{}}
4950
}
5051

51-
// GetStats returns the counting statistics
52+
// GetStats returns the counting statistics for backward compatibility
5253
func (c *Counter) GetStats() *Stats {
53-
return c.S
54+
return c.Stats
5455
}
5556

5657
// Count analyzes the provided input and updates the character statistics.
@@ -148,10 +149,10 @@ func (c *Counter) CountBytes(data []byte) error {
148149
}
149150

150151
// Update statistics in batch to minimize memory writes
151-
c.S.Lines += lines
152-
c.S.ChineseChars += chineseChars
153-
c.S.NonChineseChars += nonChineseChars
154-
c.S.TotalChars += chineseChars + nonChineseChars
152+
c.Lines += lines
153+
c.ChineseChars += chineseChars
154+
c.NonChineseChars += nonChineseChars
155+
c.TotalChars += chineseChars + nonChineseChars
155156

156157
return nil
157158
}

count_test.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,25 @@ func TestCounter_Count(t *testing.T) {
2929
wantErr: false,
3030
},
3131
{
32-
name: "Invalid input type",
32+
name: "Invalid input type - int",
3333
input: 42,
3434
wantErr: true,
3535
},
36+
{
37+
name: "Invalid input type - float",
38+
input: 3.14,
39+
wantErr: true,
40+
},
41+
{
42+
name: "Invalid input type - bool",
43+
input: true,
44+
wantErr: true,
45+
},
46+
{
47+
name: "Invalid input type - nil",
48+
input: nil,
49+
wantErr: true,
50+
},
3651
}
3752

3853
for _, tt := range tests {
@@ -47,16 +62,16 @@ func TestCounter_Count(t *testing.T) {
4762
}
4863

4964
if !tt.wantErr {
50-
if tc.S.TotalChars != 8 {
51-
t.Errorf("Counter.Count() total chars = %d, want 8", tc.S.TotalChars)
65+
if tc.TotalChars != 8 {
66+
t.Errorf("Counter.Count() total chars = %d, want 8", tc.TotalChars)
5267
}
5368

54-
if tc.S.ChineseChars != 2 {
55-
t.Errorf("Counter.Count() chinese chars = %d, want 2", tc.S.ChineseChars)
69+
if tc.ChineseChars != 2 {
70+
t.Errorf("Counter.Count() chinese chars = %d, want 2", tc.ChineseChars)
5671
}
5772

58-
if tc.S.Lines != 1 {
59-
t.Errorf("Counter.Count() space chars = %d, want 1", tc.S.Lines)
73+
if tc.Lines != 1 {
74+
t.Errorf("Counter.Count() space chars = %d, want 1", tc.Lines)
6075
}
6176
}
6277
})

0 commit comments

Comments
 (0)