如何避免在我的测试中重复使用这个结构体代码?

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

How can I avoid repeating this struct code for my tests

问题

This is the part that's being repeated in many files. It's to mock stdout (Terminal standard output).

#[cfg(test)]
mod tests {
    use std::io::Write;

    use super::*;

    #[derive(Default, Debug)]
    pub struct MockStdout {
        pub buffer: Vec<u8>,
        pub cursor_pos: (u16, u16),
    }

    impl MockStdout {
        pub fn new() -> Self {
            let buffer = Vec::new();

            Self {
                buffer,
                cursor_pos: (1, 1),
            }
        }
    }

    impl Write for MockStdout {
        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
            self.buffer.write(buf)
        }

        fn flush(&mut self) -> std::io::Result<()> {
            self.buffer.flush()
        }
    }
}

In each file, the implementation of `write_term` will be different:

```rust
impl TermCursor for MockStdout {
    fn write_term(&mut self, fmt: std::fmt::Arguments) -> std::io::Result<()> {
        // the different implementation
    }
}

I could put the repeated code in a file called mock_stdout.rs, then import it in tests. But this will cause some problems:

a. Having impl TermCursor for MockStdout in many files will cause an implementation conflict.

b. If I do:

MyMockStdout {
    stdout: MockStdout
}

All the code will break because it's expecting stdout, not stdout.stdout.

What's the best way to prevent repetition in a situation like this?

英文:

This is the part that's being repeated in many files. It's to mock stdout (Terminal standard output).

#[cfg(test)]
mod tests {
    use std::io::Write;

    use super::*;

    #[derive(Default, Debug)]
    pub struct MockStdout {
        pub buffer: Vec&lt;u8&gt;,
        pub cursor_pos: (u16, u16),
    }

    impl MockStdout {
        pub fn new() -&gt; Self {
            let buffer = Vec::new();

            Self {
                buffer,
                cursor_pos: (1, 1),
            }
        }
    }

    impl Write for MockStdout {
        fn write(&amp;mut self, buf: &amp;[u8]) -&gt; std::io::Result&lt;usize&gt; {
            self.buffer.write(buf)
        }

        fn flush(&amp;mut self) -&gt; std::io::Result&lt;()&gt; {
            self.buffer.flush()
        }
    }

In each file, the implementation of write_term will be different:

    impl TermCursor for MockStdout {
        fn write_term(&amp;mut self, fmt: std::fmt::Arguments) -&gt; std::io::Result&lt;()&gt; {
            // the different implementation
        }
    }
}

I could put the repeated code in a file called mock_stdout.rs, then import it in tests. But this will cause some problems:

a. Having impl TermCursor for MockStdout in many files will cause an implementation conflict.

b. If I do:

MyMockStdout {
    stdout: MockStdout
}

All the code will break because it's expecting stdout, not stdout.stdout.

What's the best way to prevent repetition in a situation like this?

答案1

得分: 2

这是您提供的代码的翻译:

似乎对我来说,处理这个问题的最佳方式,因为您需要为相同的特性使用多个实现,是使用一个(声明性的)宏,它接受一个结构体名称并生成您需要的样板代码:

#[macro_export]
macro_rules! mock_stdout {
    ( $name:tt ) => {
        #[derive(Default, Debug)]
        pub struct $name {
            pub buffer: Vec<u8>,
            pub cursor_pos: (u16, u16),
        }

        impl $name {
            pub fn new() -> Self {
                let buffer = Vec::new();

                Self {
                    buffer,
                    cursor_pos: (1, 1),
                }
            }
        }

        impl std::io::Write for $name {
            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
                self.buffer.write(buf)
            }

            fn flush(&mut self) -> std::io::Result<()> {
                self.buffer.flush()
            }
        }
    };
}

然后,您可以使用您想要在测试代码中使用的名称调用宏。类似这样的内容将起作用:

#[cfg(test)]
mod tests {
    use path::to::mock_stdout;

    mock_stdout!(FirstMockStdout);
    mock_stdout!(SecondMockStdout);

    impl TermCursor for FirstMockStdout {
        fn write_term(&mut self, fmt: std::fmt::Arguments) -> std::io::Result<()> {
            // 第一个实现
        }
    }

    impl TermCursor for SecondMockStdout {
        fn write_term(&mut self, fmt: std::fmt::Arguments) -> std::io::Result<()> {
            // 第二个实现
        }
    }

    #[test]
    fn my_test() {
        let first = FirstMockStdout::new();
        ...

        let second = SecondMockStdout::new();
        ...
    }
}
英文:

Seems to me like the best way to go about doing this, since you need multiple implementations for the same trait, is to use a (declarative) macro which takes in a name for the struct and produces the boilerplate code you need:

#[macro_export]
macro_rules! mock_stdout {
    ( $name:tt ) =&gt; {
        #[derive(Default, Debug)]
        pub struct $name {
            pub buffer: Vec&lt;u8&gt;,
            pub cursor_pos: (u16, u16),
        }

        impl $name {
            pub fn new() -&gt; Self {
                let buffer = Vec::new();

                Self {
                    buffer,
                    cursor_pos: (1, 1),
                }
            }
        }

        impl std::io::Write for $name {
            fn write(&amp;mut self, buf: &amp;[u8]) -&gt; std::io::Result&lt;usize&gt; {
                self.buffer.write(buf)
            }

            fn flush(&amp;mut self) -&gt; std::io::Result&lt;()&gt; {
                self.buffer.flush()
            }
        }
    };
}

You can then invoke the macro with whatever name you want to use in that local segment of your code for testing. Something like this will then work:

#[cfg(test)]
mod tests {
    use path::to::mock_stdout;

    mock_stdout!(FirstMockStdout);
    mock_stdout!(SecondMockStdout);

    impl TermCursor for FirstMockStdout {
        fn write_term(&amp;mut self, fmt: std::fmt::Arguments) -&gt; std::io::Result&lt;()&gt; {
            // the first implementation
        }
    }

    impl TermCursor for SecondMockStdout {
        fn write_term(&amp;mut self, fmt: std::fmt::Arguments) -&gt; std::io::Result&lt;()&gt; {
            // the second implementation
        }
    }

    #[test]
    fn my_test() {
        let first = FirstMockStdout::new();
        ...

        let second = SecondMockStdout::new();
        ...
    }
}

huangapple
  • 本文由 发表于 2023年4月17日 00:28:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76028999.html
匿名

发表评论

匿名网友

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

确定