我处理转换的方法是查看系统中永久修改状态的任何部分;文件、数据库、外部内容。一旦改变并重新阅读,它是否已经改变了?这是要改变它的第一个地方。
所以你要做的第一件事就是找到一个修改源的地方,如下所示:
class MyXmlFileWriter
{
public bool WriteData(string fileName, string xmlText)
{
// TODO: Sort out exception handling
try
{
File.WriteAllText(fileName, xmlText);
return true;
}
catch(Exception ex)
{
return false;
}
}
}
其次,您编写单元测试以确保在重构时不会破坏代码。
[TestClass]
class MyXmlWriterTests
{
[TestMethod]
public void WriteData_WithValidFileAndContent_ExpectTrue()
{
var target = new MyXmlFileWriter();
var filePath = Path.GetTempFile();
target.WriteData(filePath, "<Xml/>");
Assert.IsTrue(File.Exists(filePath));
}
// TODO: Check other cases
}
接下来,从原始类中提取一个接口:
interface IFileWriter
{
bool WriteData(string location, string content);
}
class MyXmlFileWriter : IFileWriter
{
/* As before */
}
重新运行测试,希望一切顺利。保留原始测试,因为它正在检查您的旧实现是否有效。
接下来编写一个什么都不做的假实现。我们只想在这里实现一个非常基本的行为。
// Put this class in the test suite, not the main project
class FakeFileWriter : IFileWriter
{
internal bool WriteDataCalled { get; private set; }
public bool WriteData(string file, string content)
{
this.WriteDataCalled = true;
return true;
}
}
然后进行单元测试...
class FakeFileWriterTests
{
private IFileWriter writer;
[TestInitialize()]
public void Initialize()
{
writer = new FakeFileWriter();
}
[TestMethod]
public void WriteData_WhenCalled_ExpectSuccess()
{
writer.WriteData(null,null);
Assert.IsTrue(writer.WriteDataCalled);
}
}
现在它的单元测试和重构版本仍然有效,我们需要确保在注入时,调用类使用的是接口,而不是具体版本!
// Before
class FileRepository
{
public FileRepository() { }
public void Save( string content, string xml )
{
var writer = new MyXmlFileWriter();
writer.WriteData(content,xml);
}
}
// After
class FileRepository
{
private IFileWriter writer = null;
public FileRepository() : this( new MyXmlFileWriter() ){ }
public FileRepository(IFileWriter writer)
{
this.writer = writer;
}
public void Save( string path, string xml)
{
this.writer.WriteData(path, xml);
}
}
那么我们做了什么?
- 有一个使用普通类型的默认构造函数
- 有一个接受
IFileWriter
类型的构造函数
- 使用实例字段来保存引用的对象。
然后是为 编写单元测试FileRepository
并检查该方法是否被调用的情况:
[TestClass]
class FileRepositoryTests
{
private FileRepository repository = null;
[TestInitialize()]
public void Initialize()
{
this.repository = new FileRepository( new FakeFileWriter() );
}
[TestMethod]
public void WriteData_WhenCalled_ExpectSuccess()
{
// Arrange
var target = repository;
// Act
var actual = repository.Save(null,null);
// Assert
Assert.IsTrue(actual);
}
}
好的,但是在这里,我们真的在测试FileRepository
还是FakeFileWriter
?我们正在测试,FileRepository
因为我们的其他测试正在FakeFileWriter
单独测试。此类 -FileRepositoryTests
对于测试传入参数的空值会更有用。
假货没有做任何聪明的事——没有参数验证,没有 I/O。它只是坐在那里,以便 FileRepository 可以保存任何工作的内容。它的目的有两个;显着加快单元测试并且不破坏系统状态。
如果这个 FileRepository 也必须读取文件,您也可以实现 IFileReader(这有点极端),或者只是将最后写入的 filePath/xml 存储到内存中的字符串并取而代之。
因此,随着基础知识的结束 - 你如何处理这个?
在需要大量重构的大型项目中,最好将单元测试合并到任何经历 DI 更改的类中。理论上,你的数据不应该被提交到数百个位置[在你的代码中],而是通过几个关键位置推送。在代码中找到它们并为它们添加一个接口。我使用的一个技巧是将每个数据库或类似索引的源隐藏在这样的接口后面:
interface IReadOnlyRepository<TKey, TValue>
{
TValue Retrieve(TKey key);
}
interface IRepository<TKey, TValue> : IReadOnlyRepository<TKey, TValue>
{
void Create(TKey key, TValue value);
void Update(TKey key, TValue);
void Delete(TKey key);
}
这使您可以以非常通用的方式从数据源中检索。您可以通过仅替换注入的位置来从XmlRepository
to切换。DbRepository
这对于从一个数据源迁移到另一个数据源而不影响系统内部的项目非常有用。将 XML 操作更改为使用对象可能很容易,但使用这种方法维护和实现新功能要容易得多。
我能给出的唯一其他建议是一次做 1 个数据源并坚持做。抵制一次做太多事情的诱惑。如果您真的不得不一键保存到文件、数据库和 Web 服务,请使用提取接口,伪造调用并且不返回任何内容。一口气做很多事情是一种真正的杂耍行为,但是与从第一原则开始相比,您可以更容易地将它们放回原处。
祝你好运!