如何测试使用filepath.walk的函数?

huangapple go评论75阅读模式
英文:

How do i test a function which uses filepath.walk

问题

我有一个函数,它可以获取目录中的所有PDF文件,并在存在文件时返回这些文件。

func GetPdfFiles(path string) ([]string, error) {
    var files []string
    err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
        if strings.HasSuffix(path, ".pdf") {
            files = append(files, path)
        }
        return nil
    })
    if err != nil {
        return nil, err
    }
    if files == nil {
        return nil, errors.New("No PdfFiles found.")
    }
    return files, nil
}

我的测试函数在调用filepath.Walk函数时得到了错误:nilfilepath.Walk函数要求传入一个返回错误的匿名函数,但是它应该从if语句中获取错误,就像第二个测试用例中没有文件时应该返回errors.New("No PdfFiles found.")一样。

你如何正确测试它呢?

func TestGetPdfFiles(t *testing.T) {
    type args struct {
        path string
    }

    cwd, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }

    tests := []struct {
        name    string
        args    args
        want    []string
        wantErr bool
    }{
        {name: "2PdfFiles", args: args{path: fmt.Sprintf("%s/testData/", cwd)}, want: []string{fmt.Sprintf("%s/testData/test-1.pdf", cwd), fmt.Sprintf("%s/testData/test-2.pdf", cwd)}, wantErr: false},
        {name: "noPdfFiles", args: args{path: fmt.Sprintf("%s", cwd)}, want: nil, wantErr: true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := GetPdfFiles(tt.args.path)
            if (err != nil) != tt.wantErr {
                t.Errorf("GetPdfFiles() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
            }
        })
    }
}
英文:

I have a function which gets all PDF files in a directory and returns the files if there are some.

func GetPdfFiles(path string) ([]string, error) {
var files []string
err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
	if strings.HasSuffix(path, ".pdf") {
		files = append(files, path)
	}
	return nil
})
if err != nil {
	return nil, err
}
if files == nil {
	return nil, errors.New("No PdfFiles found.")
}
return files, nil
}

My Test function gets the error: nil, from the filepath.Walk function which requires a anonymous function that returns an error, but it should get the error from the if statements like in the case of the second testcase with no files it should return errors.New("No PdfFiles found.").
How can i test it correctly.

func TestGetPdfFiles(t *testing.T) {
type args struct {
	path string
}

cwd, err := os.Getwd()
if err != nil {
	log.Fatal(err)
}

tests := []struct {
	name    string
	args    args
	want    []string
	wantErr bool
}{
	{name: "2PdfFiles", args: args{path: fmt.Sprintf("%s/testData/", cwd)}, want: []string{fmt.Sprintf("%s/testData/test-1.pdf", cwd), fmt.Sprintf("%s/testData/test-2.pdf", cwd)}, wantErr: false},
	{name: "noPdfFiles", args: args{path: fmt.Sprintf("%s", cwd)}, want: nil, wantErr: true},
}
for _, tt := range tests {
	t.Run(tt.name, func(t *testing.T) {
		got, err := GetPdfFiles(tt.args.path)
		if (err != nil) != tt.wantErr {
			t.Errorf("GetPdfFiles() error = %v, wantErr %v", err, tt.wantErr)
			return
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
		}
	})
}
}

答案1

得分: 1

你使用了依赖注入,并修改了你的函数以接受fs.FS的实现。这样可以让你的测试传递一个模拟文件系统。

或者,对于你的用例来说,可能更简单的方法是修改你的GetPdfFiles()函数,使其接受一个与path.WalkDir()具有相同签名的目录遍历函数:

package main

import (
  "io/fs"
  "path"
  "path/filepath"
  "strings"
)

func GetPdfFiles(root string) ([]string, error) {
  return GetPdfFilesWithWalker(root, filepath.WalkDir)
}

type DirectoryWalker = func(string, fs.WalkDirFunc) error

func GetPdfFilesWithWalker(root string, walk DirectoryWalker) (fns []string, err error) {
  
  collectPdfFiles := func(fn string, info fs.DirEntry, err error) error {
    if ext := strings.ToLower(path.Ext(fn)); ext == ".pdf" {
      fns = append(fns, fn)
    }
    return nil
  }
  
  err = walk(root, collectPdfFiles)
  
  return fns, err
}

现在,你的GetPdfFiles()函数只是一个无操作的包装器,它注入了默认实现(来自path/filepath),核心代码在GetPdfFilesWithWalker()中,你可以针对它编写测试,并传入适当的模拟。

你甚至可以构建一个会返回错误的模拟,这样你就可以测试错误处理。

你的模拟目录遍历函数可以简单地像这样实现(特别是因为你只使用了传递给回调函数的path参数):

func MockDirectoryWalker(root string, visit fs.WalkDirFunc) (err error) {
  paths := [][]string{
    {root, "a"},
    {root, "a", "a.pdf"},
    {root, "a", "b.txt"},
    {root, "a", "b"},
    {root, "a", "b", "c.pdf"},
    {root, "a", "b", "d.txt"},
  }
  for _, p := range paths {
    fqn := path.Join(p...)
    var di fs.DirEntry
    visit(fqn, di, nil)
  }

  return err
}
英文:

You use dependency injection, and modify your function to accept an implementation of fs.FS. That lets your tests pass it a mock file system.

Or, perhaps simpler for your use case, modify your GetPdfFiles() to accept a directory walker function with the same signature as path.WalkDir():

package main

import (
  "io/fs"
  "path"
  "path/filepath"
  "strings"
)

func GetPdfFiles(root string) ([]string, error) {
  return GetPdfFilesWithWalker(root, filepath.WalkDir)
}

type DirectoryWalker = func(string, fs.WalkDirFunc) error

func GetPdfFilesWithWalker(root string, walk DirectoryWalker) (fns []string, err error) {
  
  collectPdfFiles := func(fn string, info fs.DirEntry, err error) error {
    if ext := strings.ToLower(path.Ext(fn)); ext == ".pdf" {
      fns = append(fns, fn)
    }
    return nil
  }
  
  err = walk(root, collectPdfFiles)
  
  return fns, err
}

Now your GetPdfFiles() is a do-nothing wrapper that injects the default implementation (from path/filepath), and the core is in GetPdfFilesWithWalker(), against which you write your tests, passing in a suitable mock.

you can even construct a mock that will return errors, so you can test your error handling.

Your mock directory walker can be as simple as something like this (especially since you only use the path passed to the callback:

func MockDirectoryWalker(root string, visit fs.WalkDirFunc) (err error) {
  paths := [][]string{
    {root, "a"},
    {root, "a", "a.pdf"},
    {root, "a", "b.txt"},
    {root,"a", "b"},
    {root, "a", "b", "c.pdf"},
    {root, "a", "b", "d.txt"},
  }
  for _, p := range paths {
    fqn := path.Join(p...)
    var di fs.DirEntry
    visit(fqn, di, nil)
  }

  return err
}

答案2

得分: 0

使用T.TempDir()来创建一个唯一的空目录,这样可以保证可重复的结果。测试包也会负责清理该目录:

func TestGetPdfFiles(t *testing.T) {
    type args struct {
        path string
    }

    tests := []struct {
        name    string
        args    args
        want    []string
        wantErr bool
    }{
        {name: "noPdfFiles", args: args{path: t.TempDir()}, want: nil, wantErr: true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := GetPdfFiles(tt.args.path)
            if (err != nil) != tt.wantErr {
                t.Errorf("GetPdfFiles() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
            }
        })
    }
}
英文:

How about using T.TempDir() for a unique empty directory every time. This will guarantee repeatable results. The testing package will also take care of cleaning that:

func TestGetPdfFiles(t *testing.T) {
	type args struct {
		path string
	}

	tests := []struct {
		name    string
		args    args
		want    []string
		wantErr bool
	}{
		{name: "noPdfFiles", args: args{path: t.TempDir()}, want: nil, wantErr: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := GetPdfFiles(tt.args.path)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetPdfFiles() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
			}
		})
	}
}

答案3

得分: 0

我猜,没有一个好的方法来做这件事。
所以我移除了错误检查,只返回一个空数组,否则会变得太乱。
还要感谢@Qasim Sarfraz提供的T.TempDir的想法。

func GetPdfFiles(path string) []string {
    var files []string
    filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
        if strings.HasSuffix(path, ".pdf") {
            files = append(files, path)
        }
        return nil
    })
    return files
}

func TestGetPdfFiles(t *testing.T) {
    type args struct {
        path string
    }

    cwd, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }

    tests := []struct {
        name    string
        args    args
        want    []string
        wantErr bool
    }{
        {name: "2PdfFiles", args: args{path: fmt.Sprintf("%s/testData/", cwd)}, want: []string{fmt.Sprintf("%s/testData/test-1.pdf", cwd), fmt.Sprintf("%s/testData/test-2.pdf", cwd)}},
        {name: "noPdfFiles", args: args{path: t.TempDir()}, want: nil},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := GetPdfFiles(tt.args.path)
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
            }
        })
    }
}
英文:

I guess, there isn't really a good way to do it.
so i removed the error checks and just return an empty array, it would've been too messy.
Also thanks to @Qasim Sarfraz for the T.TempDir idea.

func GetPdfFiles(path string) []string {
	var files []string
	filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
		if strings.HasSuffix(path, ".pdf") {
			files = append(files, path)
		}
		return nil
	})
	return files
}

func TestGetPdfFiles(t *testing.T) {
	type args struct {
		path string
	}

	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}

	tests := []struct {
		name    string
		args    args
		want    []string
		wantErr bool
	}{
		{name: "2PdfFiles", args: args{path: fmt.Sprintf("%s/testData/", cwd)}, want: []string{fmt.Sprintf("%s/testData/test-1.pdf", cwd), fmt.Sprintf("%s/testData/test-2.pdf", cwd)}},
		{name: "noPdfFiles", args: args{path: t.TempDir()}, want: nil},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := GetPdfFiles(tt.args.path)
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
			}
		})
	}
}

huangapple
  • 本文由 发表于 2022年8月11日 17:05:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/73318049.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定