我接近轉換的方式是查看永久修改狀態的系統的任何部分;文件,數據庫,外部內容。一旦改變並重新閱讀,它有沒有改變?這是第一個想要改變它的地方。
所以你要做的第一件事,就是發現修改這樣一個源的地方:
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更改的任何類中。理論上說,你的數據不應該被提交給[在你的代碼中]的數百個位置,而是通過幾個關鍵位置。在代碼中找到它們併爲它們添加一個接口。我用一個技巧是隱藏每個DB或指數類源這樣的界面背後:
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
切換到DbRepository
,只需更換它的注入位置即可。這對於從一個數據源遷移到另一個數據源而不影響系統內部的項目非常有用。它可以簡單地將XML操作更改爲使用對象,但使用此方法維護和實現新功能要容易得多。
我可以給出的唯一的其他建議是每次做1個數據源並堅持下去。抵制一次性做太多的誘惑。如果你最終不得不一次保存到文件,數據庫和網絡服務中,請使用Extract Interface,假冒這些調用並不返回任何內容。這是一個真正的雜耍行爲,一次做很多,但你可以比從第一個原則開始更容易地插入。
祝你好運!
您可以通過重構僅爲當前任務所需的代碼的小部分開始。通過這種方式,你和你的團隊將獲得DI的感受並收集一些DI經驗。由於DI激勵的解耦架構非常友好,您可以使用單元測試來確保不會破壞任何東西。 – lasseeskildsen
這個問題可能更適合http://programmers.stackexchange.com –
這裏沒有工具。考慮一下你將如何自己分析每個類來找出要提取的依賴關係。一個工具不能可靠地爲你做這個分析(或者它會提取太多)。然而,有些工具(如Resharper和Code Rush)可以幫助您提取提取方法並提取類重構,但這仍然適用於當時的單個類;而不是整個項目的一次點擊。 – Steven