ReactiveExtensions を使ってテストデータを生成する


`ReactiveExtensions` (System.Reactive) を使ってテストデータを生成する例を紹介します。

## Observable.Generate メソッド ##

System.Reactive.Linq.Observable クラスには Generate というメソッドがあります。これ以外にもスケジューラーなどを引数にとるオーバーロードがあります。今回の実装ではこのメソッドを使用します。

```c#

/// <summary>
/// 値を生成します。
/// </summary>
/// <typeparam name="TState">繰り返しの状態を管理するためのオブジェクトの型</typeparam>
/// <typeparam name="TResult">生成する値の型</typeparam>
/// <param name="initialState">繰り返しの状態を管理するためのオブジェクト</param>
/// <param name="condition">繰り返し条件</param>
/// <param name="iterate">繰り返しの状態を更新する処理</param>
/// <param name="resultSelector">値を返す処理</param>
/// <returns></returns>
public static IObservable<TResult> Generate<TState, TResult>(
    TState initialState
    , Func<TState, bool> condition
    , Func<TState, TState> iterate
    , Func<TState, TResult> resultSelector
);
```

TState がややこしい印象を与えるかもしれませんが、繰り返しの状態を管理する代表的なものは int 型のカウンターです。n 回繰り返す場合は次のように書くことができます。

```c#
int n = 繰り返し回数;
Func<int, TResult> resultSelector = 値を生成する処理;

IObservable<TResult> observable = Observable.Generate<int, TResult>(
    0
    , i => i < n
    , i => ++i
    , i => resultSelector
);
```

イテレータで表すと次のようなイメージになります。

```C#

IEnumerable<TState> Generate(int n, Func<int, TResult> resultSelector))
{
    for (int i = 0; i < n; ++i)
    {
        yield return resultSelector(i);
    }
}
```
## IObservable&lt;T&gt; を生成するメソッドの実装 ##

Observable.Generate メソッドを使って IObservable&lt;T&gt; オブジェクトを生成するメソッドを実装します。

### 順番に値を返す ###

[1, 2, 3] が指定された場合、1, 2, 3, 1, 2, 3, … のように順番に値を返します。

```C#

public IObservable<T> Each<T>(int count, params T[] expectedValues)
{
    return Each(count, (IList<T>)expectedValues);
}

public IObservable<T> Each<T>(int count, IList<T> expectedValues)
{
    int valuesCount = expectedValues.Count;
    return Observable.Generate(0, i => i < count, i => ++i, i =>
    {
        Math.DivRem(i, valuesCount, out int r);
        return expectedValues[r];
    });
}
```

### 何れか一つの値を返す ###

[1, 2, 3] が指定された場合、1, 3, 1, 2, 2, 1, … のように何れかの値を返します。

```C#

public IObservable<T> Any<T>(int count, params T[] expectedValues)
{
    return Observable.Generate(0, i => i < count, i => ++i, i => NextAny(expectedValues));
}

public IObservable<T> Any<T>(int count, IList<T> expectedValues)
{
    return Observable.Generate(0, i => i < count, i => ++i, i => NextAny(expectedValues));
}

private T NextAny<T>(IList<T> expectedValues)
{
    // NextRandomInt32メソッドは指定された範囲内でランダムな値を返すメソッド
    return expectedValues[NextRandomInt32(0, expectedValues.Count - 1)];
}
```

### 確率に従って何れか一つの値を返す ###

値に [1, 2, 3]、確率に [0.5, 0.3] が指定された場合、50% の確率で 1、30% の確率で 2、それ以外の場合 3 を返します。

```C#

public IObservable<T> AnyWithProbability<T>(int count, IList<T> expectedValues, IList<double> probabilities)
{
    return Observable.Generate(0, i => i < count, i => ++i, i => NextAny(expectedValues, probabilities));
}

private T NextAny<T>(IList<T> expectedValues, IList<double> probabilities)
{
    double probability = NextRandomDouble(0, 1);

    for (int i = 0; i < probabilities.Count; ++i)
    {
        summary += probabilities[i];
        if (probability <= summary) { return expectedValues[i]; }
    }

    if (expectedValues.Count > probabilities.Count)
    {
        return expectedValues[probabilities.Count];
    }
    else
    {
        return default(T);
    }
}
```

### ランダムな値を返す ###

指定された範囲内でランダムな値を返します。
以下は Int32 の実装です。他の型については [GitHub](https://github.com/mxProject/Misc/tree/master/source/TestDataGenerator) を見てください。

```C#

public RandomValueGenerator<Int32> RandomInt32 { get; set; } = s_DefaultRandomInt32;
private static readonly RandomValueGenerator<Int32> s_DefaultRandomInt32 = SimpleRandomUtility.RandomInt32;

public IObservable<Int32> Random(int count, Int32 minValue, Int32 maxValue)
{
    return (RandomInt32 ?? s_DefaultRandomInt32).Values(count, minValue, maxValue);
}

public IObservable<Int32> RandomOrDefault(int count, double defaultProbability, Int32 minValue, Int32 maxValue)
{
    return (RandomInt32 ?? s_DefaultRandomInt32).ValuesOrDefault(count, defaultProbability, minValue, maxValue);
}

public IObservable<Int32?> RandomOrNull(int count, double nullProbability, Int32 minValue, Int32 maxValue)
{
    return (RandomInt32 ?? s_DefaultRandomInt32).ValuesOrNull(count, nullProbability, minValue, maxValue);
}

// NextAnyメソッドから呼び出しているメソッドです。
private int NextRandomInt32(int minValue, int maxValue)
{
    return (RandomInt32 ?? s_DefaultRandomInt32).NextValue(minValue, maxValue);
}
```

上のコードで使用している RandomValueGenerator&lt;T&gt; クラスの実装です。ランダム値の生成処理はデリゲートで受け取るようにしています。.NET Framework 標準の Random クラスや `Math.NET` (MathNet.Numerics) を用いるなど任意に実装した生成処理を受け取る想定です。

```C#:RandomValueGenerator&lt;T&gt;

public delegate T NextRandomValue<T>(T minValue, T maxValue);

public class RandomValueGenerator<T> where T : struct
{

    #region ctor

    public RandomValueGenerator(Func<T> generator, NextRandomValue<T> generatorInRange, Func<double> nullProbabilityGenerator)
    {
        m_Generator = generator;
        m_GeneratorInRange = generatorInRange;
        m_NullProbabilityGenerator = nullProbabilityGenerator;
    }

    #endregion

    private readonly Func<T> m_Generator;
    private readonly NextRandomValue<T> m_GeneratorInRange;
    private readonly Func<double> m_NullProbabilityGenerator;

    #region NextValue

    public T NextValue()
    {
        return m_Generator();
    }

    public T NextValue(T minValue, T maxValue)
    {
        return m_GeneratorInRange(minValue, maxValue);
    }

    #endregion

    #region Values

    public IObservable<T> Values(int count)
    {
        return Observable.Generate(0, i => i < count, i => ++i, i => m_Generator());
    }

    public IObservable<T> Values(int count, T minValue, T maxValue)
    {
        return Observable.Generate(0, i => i < count, i => ++i, i => m_GeneratorInRange(minValue, maxValue));
    }

    #endregion

    #region ValuesOrDefault

    public IObservable<T> ValuesOrDefault(int count, double defaultProbability)
    {
        return Observable.Generate(0, i => i < count, i => ++i, i =>
        {
            if (IsNextNull(defaultProbability)) { return default(T); }
            return m_Generator();
        });
    }

    public IObservable<T> ValuesOrDefault(int count, double defaultProbability, T minValue, T maxValue)
    {
        return Observable.Generate(0, i => i < count, i => ++i, i =>
        {
            if (IsNextNull(defaultProbability)) { return default(T); }
            return m_GeneratorInRange(minValue, maxValue);
        });
    }

    #endregion

    #region ValuesOrNull

    public IObservable<T?> ValuesOrNull(int count, double nullProbability)
    {
        return Observable.Generate<int, T?>(0, i => i < count, i => ++i, i =>
        {
            if (IsNextNull(nullProbability)) { return null; }
            return m_Generator();
        });
    }

    public IObservable<T?> ValuesOrNull(int count, double nullProbability, T minValue, T maxValue)
    {
        return Observable.Generate<int, T?>(0, i => i < count, i => ++i, i =>
        {
            if (IsNextNull(nullProbability)) { return null; }
            return m_GeneratorInRange(minValue, maxValue);
        });
    }

    #endregion

    private bool IsNextNull(double probability)
    {
        if (probability <= 0) { return false; }
        if (probability >= 1) { return true; }
        return m_NullProbabilityGenerator() <= probability;
    }

}
```

## データの生成例 ##

### レコード形式のデータ ###

各データ項目の値を生成する IObservable&lt;T&gt; オブジェクトを生成し、値を object 配列にまとめて配信する Observable オブジェクトを生成しています。

```C#

ObservableDataGenerator generator = new ObservableDataGenerator();

// 生成するデータ数
int dataCount = 20;

// データ項目
var fields = Observable
    // 連番
    .Range(1, dataCount)
    // 今日から一か月間のランダムな日付
    .And(generator.Random(dataCount, DateTime.Today, DateTime.Today.AddMonths(1)).Select(d => d.Date))
    // 1～100 のランダムな整数を前ゼロ編集した文字列
    .And(generator.Random(dataCount, 1, 100).Select(value => value.ToString("d3")))
    // a, b, c, d, e を順番に返す
    .And(generator.Each(dataCount, "a", "b", "c", "d", "e"))
    // A, B, C, D, E の何れかを返す
    .And(generator.Any(dataCount, "A", "B", "C", "D", "E"))
    // A, B, C, D, E の何れかを返す（A:50%, B:30%, C:10%, D:5%, E:5%）
    .And(generator.AnyWithProbability(dataCount
        , new string[] { "A", "B", "C", "D", "E" }, new double[] { 0.5, 0.3, 0.1, 0.05 }))
    // [a, b, c] と [1, 2, 3] の何れかを組み合わせた文字列
    .And(generator.Any(dataCount, "a", "b", "c").Zip(generator.Any(dataCount, 1, 2, 3), (a, i) => a + i))
    // -10000～10000 のランダムな整数または25%の確率でnull
    .And(generator.RandomOrNull(dataCount, 0.25, -10000, 10000))
    ;

// データ項目の配列にまとめて返す
var plan = fields.Then((field1, field2, field3, field4, field5, field6, field7, field8) => 
    new object[] { field1, field2, field3, field4, field5, field6, field7, field8 }
);

// Observable なデータソースを生成
var source = Observable.When(plan);

// 生成された値を列挙してみる
foreach (object[] values in source.ToEnumerable())
{
    System.Diagnostics.Debug.WriteLine(string.Join(",", values));
}

```

| field1 | field2 | field3 | field4 | field5 | field6 | field7 | field8 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|1|2018/11/01 0:00:00|022|a|A|A|c2|2701|
|2|2018/10/30 0:00:00|042|b|E|A|c3|-4946|
|3|2018/11/04 0:00:00|044|c|C|A|a1||
|4|2018/11/15 0:00:00|068|d|C|A|a3|8086|
|5|2018/11/15 0:00:00|012|e|B|B|a1|-9654|
|6|2018/11/01 0:00:00|039|a|B|E|a1||
|7|2018/11/05 0:00:00|026|b|A|A|b1|924|
|8|2018/11/05 0:00:00|034|c|E|A|b3||
|9|2018/11/26 0:00:00|086|d|E|A|b1||
|10|2018/10/27 0:00:00|091|e|E|B|b1|8331|
|11|2018/11/26 0:00:00|037|a|A|E|c2||
|12|2018/11/02 0:00:00|098|b|B|A|b2||
|13|2018/11/07 0:00:00|059|c|C|A|b3|1853|
|14|2018/11/04 0:00:00|095|d|A|E|b1|757|
|15|2018/11/02 0:00:00|019|e|E|B|a2|-5812|
|16|2018/11/05 0:00:00|028|a|B|B|a3|990|
|17|2018/11/16 0:00:00|046|b|D|D|b2|-726|
|18|2018/11/08 0:00:00|026|c|D|D|a3|8069|
|19|2018/11/07 0:00:00|089|d|B|A|a3|4677|
|20|2018/11/16 0:00:00|018|e|B|C|b3|-8997|

### データリーダー ###

データ項目名と IObservable&lt;T&gt; オブジェクトのペアを受け取ってデータを返す IDataReader 実装クラスを定義すれば、データベースの代わりにデータを返すデータスタブをつくることができると思います。

```c#

internal class ObservableDataReader : IDataReader
{
    internal ObservableDataReader(IObservable<object[]> dataSource, string[] fieldNames)
    {
        m_DataSource = dataSource;
        m_Enumerator = dataSource.GetEnumerator();
        m_FieldNames = fieldNames;
    }

    private readonly IObservable<object[]> m_DataSource;
    private readonly IEnumerator<object[]> m_Enumerator;
    private string[] m_FieldNames;

    // IDataReader の各メンバを実装します。ここでは割愛します。

    public object GetValue(int i)
    {
        return m_Enumerator.Current[i];
    }

    public int GetValues(object[] values)
    {
        m_Enumerator.Current.CopyTo(values, 0);
        return Math.Min(m_Enumerator.Current.Length, values.Length);
    }

    public bool Read()
    {
        return m_Enumerator.MoveNext();
    }
}
```