4

カスタムスレッド セーフ ジェネリック リストの作成について質問したところ、単体テストをしようとしていますが、その方法がまったくわかりません。ロックは ThreadSafeList クラス内で発生するため、複数の追加呼び出しを模倣しようとしている間、リストを一定期間ロックする方法がわかりません。ありがとう。

Can_add_one_item_at_a_time

[Test]
public void Can_add_one_item_at_a_time() //this test won't pass
{
    //I am not sure how to do this test...
    var list = new ThreadSafeList<string>();

    //some how need to call lock and sleep inside list instance
    //say somehow list locks for 1 sec

    var ta = new Thread(x => list.Add("a"));
    ta.Start(); //does it need to aboard say before 1 sec if locked

    var tb = new Thread(x => list.Add("b"));
    tb.Start(); //does it need to aboard say before 1 sec if locked

    //it involves using GetSnapshot()
    //which is bad idea for unit testing I think
    var snapshot = list.GetSnapshot(); 
    Assert.IsFalse(snapshot.Contains("a"), "Should not contain a.");
    Assert.IsFalse(snapshot.Contains("b"), "Should not contain b.");
}

Snapshot_should_be_point_of_time_only

[Test]
public void Snapshot_should_be_point_of_time_only()
{
    var list = new ThreadSafeList<string>();
    var ta = new Thread(x => list.Add("a"));
    ta.Start();

    ta.Join();
    var snapshot = list.GetSnapshot();

    var tb = new Thread(x => list.Add("b"));
    tb.Start();

    var tc = new Thread(x => list.Add("c"));
    tc.Start();

    tb.Join();
    tc.Join();

    Assert.IsTrue(snapshot.Count == 1, "Snapshot should only contain 1 item.");
    Assert.IsFalse(snapshot.Contains("b"), "Should not contain a.");
    Assert.IsFalse(snapshot.Contains("c"), "Should not contain b.");
}

インスタンスメソッド

public ThreadSafeList<T> Instance<T>()
{
    return new ThreadSafeList<T>();
}
4

3 に答える 3

6

最初のテストを見てみましょうCan_add_one_item_at_a_time

まず第一に、あなたの終了条件は意味がありません。両方のアイテムを一度に 1 つずつ追加する必要があります もちろん、あなたのテストは失敗します。

スナップショットを作成する必要もありません。覚えておいてください、これはテストです。テストの実行中にリストに触れるものは他にありません。

最後になりましたが、すべてのスレッドが実際に終了するまで、終了条件を評価しようとしていないことを確認する必要があります。最も簡単な方法は、カウンターと待機イベントを使用することです。次に例を示します。

[Test]
public void Can_add_from_multiple_threads()
{
    const int MaxWorkers = 10;

    var list = new ThreadSafeList<int>(MaxWorkers);
    int remainingWorkers = MaxWorkers;
    var workCompletedEvent = new ManualResetEvent(false);
    for (int i = 0; i < MaxWorkers; i++)
    {
        int workerNum = i;  // Make a copy of local variable for next thread
        ThreadPool.QueueUserWorkItem(s =>
        {
            list.Add(workerNum);
            if (Interlocked.Decrement(ref remainingWorkers) == 0)
                workCompletedEvent.Set();
        });
    }
    workCompletedEvent.WaitOne();
    workCompletedEvent.Close();
    for (int i = 0; i < MaxWorkers; i++)
    {
        Assert.IsTrue(list.Contains(i), "Element was not added");
    }
    Assert.AreEqual(MaxWorkers, list.Count,
        "List count does not match worker count.");
}

現在、これはAdd非常に迅速に発生する可能性があり、2 つのスレッドが同時に実行しようとすることはありません。 No Refunds No Returnsは、条件付き遅延を挿入する方法を部分的に説明しました。の代わりに、実際には特別なテスト フラグを定義しますDEBUG。ビルド構成で、というフラグをTEST追加し、これをThreadSafeListクラスに追加します。

public class ThreadSafeList<T>
{
    // snip fields

    public void Add(T item)
    {
        lock (sync)
        {
            TestUtil.WaitStandardThreadDelay();
            innerList.Add(item);
        }
    }

    // snip other methods/properties
}

static class TestUtil
{
    [Conditional("TEST")]
    public static void WaitStandardThreadDelay()
    {
        Thread.Sleep(1000);
    }
}

これにより、ビルド構成でフラグAddが定義されている限り、メソッドは実際に項目を追加する前に 1 秒間待機します。TESTテスト全体には少なくとも 10 秒かかります。それよりも早く終了する場合は、何か問題があります。

それを念頭に置いて、私はあなたに2番目のテストを任せます。似ています。

于 2010-02-26T01:25:42.740 に答える
1

あなたはチェスを見てみたいと思うかもしれません。これは、マルチスレッドコードで競合状態を見つけるために特別に設計されたプログラムです。

于 2010-02-26T00:34:22.323 に答える
1

ロックに遅延を追加する TESTONLY コードを挿入する必要があります。次のような関数を作成できます。

 [Conditional("DEBUG")]
 void SleepForABit(int delay) { thread.current.sleep(delay); }

そしてそれをあなたのクラスで呼び出します。Conditional 属性により、DEBUG ビルドでのみ呼び出され、コンパイル済みコードに残すことができます。

一貫して 100 ミリ秒ほど遅延するものと、決して待たないものを書き、それを長引かせます。

于 2010-02-25T23:38:10.697 に答える