私が変換にアプローチする方法は、状態を永続的に変更するシステムのあらゆる部分を調べることです。ファイル、データベース、外部コンテンツ。一度変更して再読すると、完全に変更されましたか? これは、それを変更する最初の場所です。
したがって、最初に行うことは、次のようにソースを変更する場所を見つけることです。
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
入ってくるパラメータの null をテストするのにより便利です。
偽物は巧妙なことは何もしていません - パラメータの検証も I/O もありません。FileRepository がコンテンツを保存できるように、ただ座っているだけです。その目的は 2 つあります。単体テストを大幅にスピードアップし、システムの状態を乱さないようにする。
この FileRepository がファイルも読み取る必要がある場合は、IFileReader も実装するか (これは少し極端です)、最後に書き込まれた filePath/xml をメモリ内の文字列に保存し、代わりにそれを取得することができます。
基本的なことは終わりにします - どのようにこれに取り組みますか?
多くのリファクタリングが必要な大規模なプロジェクトでは、DI の変更を受けるクラスに単体テストを組み込むことが常に最善です。理論的には、データを [コード内の] 何百もの場所にコミットするのではなく、いくつかの重要な場所にプッシュする必要があります。コード内でそれらを見つけて、それらのインターフェイスを追加します。私が使用した 1 つのトリックは、次のようなインターフェイスの背後にある各 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 つのデータ ソースを実行し、それを実行し続けることです。一度に多くのことをやりすぎないようにしましょう。本当にファイル、DB、および Web サービスに一度に保存する必要がある場合は、Extract Interface を使用して呼び出しを偽造し、何も返さないでください。一度にたくさんのことをするのは本当にジャグリング行為ですが、最初の原則から始めるよりも簡単にそれらをスロットに戻すことができます.
幸運を!