英文:
How to write unit test for a program that contains channels, filewalk and api call in golang?
问题
我的程序整体如下所示。
func main() {
flag.Parse()
if *token == "" {
log.Fatal(Red + "please provide a client token => -token={$token}")
}
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *token})
oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
client := putio.NewClient(oauthClient)
//paths := make(chan string)
var wg = new(sync.WaitGroup)
for i := 0; i < 50; i++ {
wg.Add(1)
go worker(paths, wg, client)
}
WalkFilePath()
//if err := filepath.Walk(*rootpath, func(path string, info os.FileInfo, err error) error {
// if err != nil {
// return fmt.Errorf("Failed to walk directory: %T %w", err, err)
// }
// if !info.IsDir() {
// paths <- path
// }
// return nil
//}); err != nil {
// panic(fmt.Errorf("failed Walk: %w", err))
//}
close(paths)
wg.Wait()
}
// walks the file path and sends paths to channel
func WalkFilePath() {
if err := filepath.Walk(*rootpath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("Failed to walk directory: %T %w", err, err)
}
if !info.IsDir() {
paths <- path
}
return nil
}); err != nil {
panic(fmt.Errorf("failed Walk: %w", err))
}
}
func worker(paths <-chan string, wg *sync.WaitGroup, client *putio.Client) {
defer wg.Done()
for path := range paths {
f, err := os.Open(path)
if err != nil {
log.Printf(Red + "Failed to open file %v for reading" + Reset, f.Name())
}
upload, err := client.Files.Upload(context.TODO(), f, path, 0)
if err != nil {
log.Printf(Red + "Failed to upload file %v" + Reset, upload.File.Name)
}
log.Printf(Green+ "File %v has been uploaded succesfully" + Reset, upload.File.Name)
}
}
我写了这段代码。这是我能做到的最简洁的写法,我被告知要为程序编写一个单元测试。我感到困惑。例如,考虑WalkFilePath
函数。为了测试这个函数,我应该提供什么样的输入,以及期望得到什么样的结果?因为它涉及到channel
通信,也就是goroutine
。有没有办法清晰地为这个程序编写单元测试?或者我应该改变代码结构,这对我来说不是很好。顺便说一下,程序运行正常。
英文:
My program is as follows as a whole.
func main() {
flag.Parse()
if *token == "" {
log.Fatal(Red + "please provide a client token => -token={$token}")
}
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *token})
oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
client := putio.NewClient(oauthClient)
//paths := make(chan string)
var wg = new(sync.WaitGroup)
for i := 0; i < 50; i++ {
wg.Add(1)
go worker(paths, wg, client)
}
WalkFilePath()
//if err := filepath.Walk(*rootpath, func(path string, info os.FileInfo, err error) error {
// if err != nil {
// return fmt.Errorf("Failed to walk directory: %T %w", err, err)
// }
// if !info.IsDir() {
// paths <- path
// }
// return nil
//}); err != nil {
// panic(fmt.Errorf("failed Walk: %w", err))
//}
close(paths)
wg.Wait()
}
// walks the file path and sends paths to channel
func WalkFilePath() {
if err := filepath.Walk(*rootpath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("Failed to walk directory: %T %w", err, err)
}
if !info.IsDir() {
paths <- path
}
return nil
}); err != nil {
panic(fmt.Errorf("failed Walk: %w", err))
}
}
func worker(paths <-chan string, wg *sync.WaitGroup, client *putio.Client) {
defer wg.Done()
for path := range paths {
f, err := os.Open(path)
if err != nil {
log.Printf(Red + "Failed to open file %v for reading" + Reset, f.Name())
}
upload, err := client.Files.Upload(context.TODO(), f, path, 0)
if err != nil {
log.Printf(Red + "Failed to upload file %v" + Reset, upload.File.Name)
}
log.Printf(Green+ "File %v has been uploaded succesfully" + Reset, upload.File.Name)
}
}
I did write the code. That's the cleanest I can do and I was told to write a unit test for the program. I'm confused. For example, considering the WalkFilePath
function. What should I provide and what kind of result I should expect to test the function. Because it contains channel
communication meaning goroutines
. Is there any way to write unit tests for this program clearly? Or should I change the code structure which is not good in this case for me. Btw, the program runs properly.
答案1
得分: 1
像大多数事物一样,Go语言对于如何进行测试有着非常明确的意见。请确保阅读https://go.dev/doc/tutorial/add-a-test。
例如,考虑
WalkFilePath
函数。我应该提供什么参数,并期望得到什么样的结果来测试这个函数?
WalkFilePath
函数的输入应该是paths
和rootpath
。你的WalkFilePath
函数没有从任何地方获取paths
或rootpath
,所以这段代码在原样情况下无法编译通过(当然,测试将有助于捕捉到这些问题)。
对于WalkFilePath
的测试可以按照以下步骤进行:
-
在你的项目下的
testdata/
目录中创建一个文件系统结构,这是一个专门用于测试数据的目录。创建子目录和文件。例如,目录结构可以是这样的:testdata/ walktest/ dir1/ file1.txt dir2/ file2.txt dir3/ file3.txt
-
现在你可以定义你将从通道中获取的预期数据。
expected_paths := []string{ "testdata/walktest/dir1/file1.txt", "testdata/walktest/dir2/file2.txt", "testdata/walktest/dir3/file3.txt", }
-
现在你需要修改
WalkFilePath
函数,使其接受rootpath
和paths
作为参数。func WalkFilePath(rootdir string, paths chan<- string) {
-
现在你可以编写测试了。
func TestWalkFilePath(t *testing.T) { paths := make(chan string) go WalkFilePath("testdata/walktest", paths) results := make([]string, 0) for path := range paths { results = append(results, path) } exp, res := strings.Join(expected_paths, ""), strings.Join(results, "") if exp != res { t.Errorf("Expected %s got %s", exp, res) } }
因为它涉及到通道通信,也就是goroutine。
在单元测试中使用通道和goroutine是完全正常和有效的。
英文:
Like most things, Go is very opinionated about how to test. Make sure to read https://go.dev/doc/tutorial/add-a-test
> For example, considering the WalkFilePath function. What should I provide and what kind of result I should expect to test the function.
The input to WalkFilePath
should be paths
and a rootpath
. Your WalkFilePath
doesn't get paths
or rootpath
from anywhere, so this code wouldn't compile as is (testing will help catch that stuff of course).
A test for WalkFilePath
might be done something like this:
-
Create a filesystem structure in your project under
testdata/
, a directory expressly set aside for data used for testing. Create subdirectories and files. For an example that might look like:testdata/ walktest/ dir1/ file1.txt dir2/ file2.txt dir3/ file3.txt
-
Now you can define the expected data you'll be getting out of your channel.
expected_paths := []string{ "testdata/walktest/dir1/file1.txt", "testdata/walktest/dir2/file2.txt", "testdata/walktest/dir3/file3.txt" }
-
Now you need to change
WalkFilePath
to take arguments forrootpath
andpaths
.func WalkFilePath(rootdir string, paths chan<- string) {
-
Now you're ready to write your test.
func TestWalkFilePath(t *testing.T( paths := make(chan string) go WalkFilePath("testdata/walktest") results := make([]string,0) for path := range paths { results = append(results, path) } exp, res := strings.Join(expected_paths, ""), strings.Join(results, "") if exp != res { t.Errorf("Expected %s got %s", exp, res) } }
> Because it contains channel communication meaning goroutines.
It's totally normal and valid to use channels and goroutines in unit tests.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论