英文:
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<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:
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?
答案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 ) => {
#[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()
}
}
};
}
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(&mut self, fmt: std::fmt::Arguments) -> std::io::Result<()> {
// the first implementation
}
}
impl TermCursor for SecondMockStdout {
fn write_term(&mut self, fmt: std::fmt::Arguments) -> std::io::Result<()> {
// the second implementation
}
}
#[test]
fn my_test() {
let first = FirstMockStdout::new();
...
let second = SecondMockStdout::new();
...
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论