Kiểm tra đơn vị với bảng tra cứu lớn?


8

Hệ thống của chúng tôi được cấu trúc theo cách mà chúng tôi nhận được nhiều thông tin chính cho các tính toán của chúng tôi và logic khác như vậy từ các bảng loại tra cứu. Ví dụ sẽ là tất cả các loại tỷ lệ khác nhau (như lãi suất hoặc tỷ lệ đóng góp), ngày (như ngày có hiệu lực) và tất cả các loại thông tin linh tinh khác nhau.

Tại sao họ quyết định cấu trúc mọi thứ như thế này? Bởi vì một số thông tin này thay đổi khá thường xuyên. Ví dụ, một số mức giá của chúng tôi thay đổi hàng năm. Họ muốn cố gắng giảm thiểu thay đổi mã. Hy vọng chỉ là các bảng tra cứu sẽ thay đổi và mã sẽ chỉ hoạt động (không thay đổi mã).

Thật không may, tôi nghĩ rằng nó sẽ làm cho thử nghiệm đơn vị đầy thách thức. Một số logic có thể làm cho hơn 100 tra cứu khác nhau. Trong khi tôi chắc chắn có thể tạo ra một đối tượng có thể nhạo báng trả về giá của chúng tôi, sẽ có thiết lập đáng kể. Tôi nghĩ rằng nó hoặc tôi phải kết thúc bằng cách sử dụng các bài kiểm tra tích hợp (và nhấn vào cơ sở dữ liệu đó). Tôi đúng hay có cách nào tốt hơn? Bất kỳ đề xuất?

Chỉnh sửa:
Xin lỗi vì phản hồi chậm nhưng tôi đã cố gắng ngâm mọi thứ trong khi cùng một thứ tung hứng nhiều thứ khác. Tôi cũng muốn cố gắng thực hiện thông qua việc thực hiện và đồng thời. Tôi đã thử một loạt các mẫu để cố gắng kiến ​​trúc sư giải pháp cho một cái gì đó tôi hài lòng. Tôi đã thử mô hình khách truy cập mà tôi không hài lòng. Cuối cùng, tôi đã sử dụng kiến ​​trúc củ hành. Tôi có hài lòng với kết quả không? Sắp xếp Tôi nghĩ đó là những gì nó được. Các bảng tra cứu làm cho nó khó khăn hơn rất nhiều.

Dưới đây là một ví dụ nhỏ (tôi đang sử dụng fakeiteasy) mã thiết lập cho các thử nghiệm để có tỷ lệ thay đổi hàng năm:

private void CreateStubsForCrsOS39Int()
{
    CreateMacIntStub(0, 1.00000m);
    CreateMacIntStub(1, 1.03000m);
    CreateMacIntStub(2, 1.06090m);
    CreateMacIntStub(3, 1.09273m);
    CreateMacIntStub(4, 1.12551m);
    CreateMacIntStub(5, 1.15928m);
    CreateMacIntStub(6, 1.19406m);
    CreateMacIntStub(7, 1.22988m);
    CreateMacIntStub(8, 1.26678m);
    CreateMacIntStub(9, 1.30478m);
    CreateMacIntStub(10, 1.34392m);
    CreateMacIntStub(11, 1.38424m);
    CreateMacIntStub(12, 1.42577m);
    CreateMacIntStub(13, 1.46854m);
    CreateMacIntStub(14, 1.51260m);
    CreateMacIntStub(15, 1.55798m);
    CreateMacIntStub(16, 1.60472m);
    CreateMacIntStub(17, 1.65286m);
    CreateMacIntStub(18, 1.70245m);
    CreateMacIntStub(19, 1.75352m);
    CreateMacIntStub(20, 1.80613m);
    CreateMacIntStub(21, 1.86031m);
    CreateMacIntStub(22, 1.91612m);
    CreateMacIntStub(23, 1.97360m);
    CreateMacIntStub(24, 2.03281m);
    CreateMacIntStub(25, 2.09379m);
    CreateMacIntStub(26, 2.15660m);
    CreateMacIntStub(27, 2.24286m);
    CreateMacIntStub(28, 2.28794m);
    CreateMacIntStub(29, 2.35658m);
    CreateMacIntStub(30, 2.42728m);
    CreateMacIntStub(31, 2.50010m);
    CreateMacIntStub(32, 2.57510m);
    CreateMacIntStub(33, 2.67810m);
    CreateMacIntStub(34, 2.78522m);
    CreateMacIntStub(35, 2.89663m);
    CreateMacIntStub(36, 3.01250m);
    CreateMacIntStub(37, 3.13300m);
    CreateMacIntStub(38, 3.25832m);
    CreateMacIntStub(39, 3.42124m);
    CreateMacIntStub(40, 3.59230m);
    CreateMacIntStub(41, 3.77192m);
    CreateMacIntStub(42, 3.96052m);
    CreateMacIntStub(43, 4.19815m);
    CreateMacIntStub(44, 4.45004m);
    CreateMacIntStub(45, 4.71704m);
    CreateMacIntStub(46, 5.00006m);
    CreateMacIntStub(47, 5.30006m);
    CreateMacIntStub(48, 5.61806m);
    CreateMacIntStub(49, 5.95514m);
    CreateMacIntStub(50, 6.31245m);
    CreateMacIntStub(51, 6.69120m);
    CreateMacIntStub(52, 7.09267m);
    CreateMacIntStub(53, 7.51823m);
    CreateMacIntStub(54, 7.96932m);
    CreateMacIntStub(55, 8.44748m);
    CreateMacIntStub(56, 8.95433m);
    CreateMacIntStub(57, 9.49159m);
    CreateMacIntStub(58, 10.06109m);
    CreateMacIntStub(59, 10.66476m);
    CreateMacIntStub(60, 11.30465m);
    CreateMacIntStub(61, 11.98293m);
    CreateMacIntStub(62, 12.70191m);
    CreateMacIntStub(63, 13.46402m);
    CreateMacIntStub(64, 14.27186m);
    CreateMacIntStub(65, 15.12817m);
    CreateMacIntStub(66, 16.03586m);
    CreateMacIntStub(67, 16.99801m);
    CreateMacIntStub(68, 18.01789m);
    CreateMacIntStub(69, 19.09896m);
    CreateMacIntStub(70, 20.24490m);
    CreateMacIntStub(71, 21.45959m);
    CreateMacIntStub(72, 22.74717m);
    CreateMacIntStub(73, 24.11200m);
    CreateMacIntStub(74, 25.55872m);
    CreateMacIntStub(75, 27.09224m);
    CreateMacIntStub(76, 28.71778m);

}

private void CreateMacIntStub(byte numberOfYears, decimal returnValue)
{
    A.CallTo(() => _macRateRepository.GetMacArIntFactor(numberOfYears)).Returns(returnValue);
}

Dưới đây là một số mã thiết lập cho một tỷ lệ có thể thay đổi tại bất kỳ thời điểm nào (có thể là vài năm trước khi lãi suất mới được đưa ra):

private void CreateStubForGenMbrRateTable()
{
    _rate = A.Fake<IRate>();
    A.CallTo(() => _rate.GetRateFigure(17, A<System.DateTime>.That.Matches(x => x < new System.DateTime(1971, 7, 1)))).Returns(1.030000000m);

    A.CallTo(() => _rate.GetRateFigure(17, 
        A<System.DateTime>.That.Matches(x => x < new System.DateTime(1977, 7, 1) && x >= new System.DateTime(1971,7,1)))).Returns(1.040000000m);

    A.CallTo(() => _rate.GetRateFigure(17,
        A<System.DateTime>.That.Matches(x => x < new System.DateTime(1981, 7, 1) && x >= new System.DateTime(1971, 7, 1)))).Returns(1.050000000m);
    A.CallTo(
        () => _rate.GetRateFigure(17, A<System.DateTime>.That.IsGreaterThan(new System.DateTime(1981, 6, 30).AddHours(23)))).Returns(1.060000000m);
}

Đây là hàm tạo cho một trong các Đối tượng Miền của tôi:

public abstract class OsEarnDetail: IOsCalcableDetail
{
    private readonly OsEarnDetailPoco _data;
    private readonly IOsMacRateRepository _macRates;
    private readonly IRate _rate;
    private const int RdRate = (int) TRSEnums.RateTypeConstants.ertRD;

    public OsEarnDetail(IOsMacRateRepository macRates,IRate rate, OsEarnDetailPoco data)
    {
        _macRates = macRates;
        _rate = rate;
        _data = data;
    }

Vậy tại sao tôi không thích nó? Các thử nghiệm hiện có sẽ hoạt động nhưng bất kỳ ai thêm bất kỳ thử nghiệm mới nào trong tương lai sẽ phải xem qua mã thiết lập này để đảm bảo mọi mức giá mới được thêm vào. Tôi đã cố gắng làm cho nó rõ ràng nhất có thể bằng cách sử dụng tên bảng như một phần của tên hàm nhưng tôi đoán đó là tên của nó :)

Câu trả lời:


16

Bạn vẫn có thể viết bài kiểm tra đơn vị. Những gì câu hỏi của bạn mô tả là một kịch bản trong đó bạn có một số nguồn dữ liệu mà mã của bạn phụ thuộc vào. Những nguồn dữ liệu này cần tạo ra cùng một dữ liệu giả mạo trong tất cả các thử nghiệm của bạn. Tuy nhiên, bạn không muốn sự lộn xộn liên quan đến việc thiết lập câu trả lời cho mỗi bài kiểm tra. Những gì bạn cần là hàng giả

Thử nghiệm giả là việc thực hiện một cái gì đó trông giống như một con vịt và quạ giống như một con vịt, nhưng không làm gì ngoài việc cung cấp các phản ứng nhất quán cho mục đích thử nghiệm.


Trong trường hợp của bạn, bạn có thể có một IExchangeRateLookupgiao diện và triển khai sản xuất của nó

public interface IExchangeRateLookup
{
    float Find(Currency currency);
}

public class DatabaseExchangeRateLookup : IExchangeRateLookup
{
    public float Find(Currency currency)
    {
        return SomethingFromTheDatabase(currency);
    }
}

Bằng cách tùy thuộc vào giao diện trong mã được kiểm tra, bạn có thể chuyển bất kỳ thứ gì thực hiện nó, bao gồm cả giả mạo

public class ExchangeRateLookupFake : IExchangeRateLookup
{
    private Dictionary<Currency, float> _lookup = new Dictionary<Currency, float>();

    public ExchangeRateLookupFake()
    {
        _lookup = IntialiseLookupWithFakeValues();
    }

    public float Find(Currency currency)
    {
        return _lookup[currency];
    }
}

8

Thực tế là:

Một số logic có thể làm cho hơn 100 tra cứu khác nhau.

là không liên quan trong bối cảnh thử nghiệm đơn vị. Một thử nghiệm đơn vị tập trung vào một phần nhỏ của mã, thường là một phương thức và không chắc là một phương thức duy nhất cần hơn 100 bảng tra cứu (nếu có, tái cấu trúc nên là mối quan tâm hàng đầu của bạn; thử nghiệm đến sau đó). Trừ khi bạn có nghĩa là hơn 100 lần tra cứu trong một vòng lặp vào cùng một bảng, trong trường hợp đó, bạn vẫn ổn.

Sự phức tạp của việc thêm sơ khai và giả cho những lần tra cứu đó không làm phiền bạn ở quy mô của một bài kiểm tra đơn vị. Trong thử nghiệm, bạn sẽ chỉ sơ khai / giả lập những cái tra cứu thực sự được sử dụng bởi phương pháp. Không chỉ bạn sẽ không có nhiều người trong số họ, mà cả những cuống hoặc giả đó sẽ rất đơn giản. Chẳng hạn, chúng có thể trả về một giá trị duy nhất, bất kể phương thức đang tìm kiếm là gì (như thể một tra cứu thực tế được điền cùng một số).

Khi sự phức tạp sẽ là vấn đề khi bạn sẽ phải kiểm tra logic kinh doanh. Hơn 100 tra cứu có thể có nghĩa là hàng ngàn và hàng ngàn trường hợp kinh doanh khác nhau để kiểm tra (thậm chí tra cứu bên ngoài), có nghĩa là hàng ngàn và hàng ngàn bài kiểm tra đơn vị.

Hình minh họa

Ví dụ: trong ngữ cảnh của khối OLAP, bạn có thể có một phương thức dựa trên hai khối, một có hai chiều và một có năm chiều:

public class HelloWorld
{
    // Intentionally hardcoded cubes.
    private readonly OlapCube olapVersions = new VersionsOlapCube();
    private readonly OlapCube olapStatistics = new StatisticsOlapCube();

    ...

    public int Demo(...)
    {
        ...
        this.olapVersions.Find(a, b);
        ...
        this.olapStatistics.Find(c, d, 0, e, 0);
        ...
    }
}

Như là, phương pháp không thể được thử nghiệm đơn vị. Bước đầu tiên là làm cho nó có thể thay thế các khối OLAP bằng sơ khai. Một cách để làm điều đó là thông qua Dependency Injection.

public class HelloWorld
{
    // Notice the interface instead of a class.
    private readonly IOlapCube olapVersions;
    private readonly IOlapCube olapStatistics;

    // Constructor.
    public HelloWorld(
        IVersionsOlapCube olapVersions, IStatisticsOlapCube olapStatistics)
    {
    }

    ...

    public void Demo(...)
    {
        ...
        this.olapVersions.Find(a, b);
        ...
        this.olapStatistics.Find(c, d, 0, e, 0);
        ...
    }
}

Bây giờ một bài kiểm tra đơn vị có thể tiêm một sơ khai như thế này:

class OlapCubeStub : IOlapCube
{
    public OlapValue Find(params int[] values)
    {
        return OlapValue.FromInt(1); // Constant value here.
    }
}

và được sử dụng như thế:

var helloWorld = new HelloWorld(new OlapCubeStub(), new OlapCubeStub());
var actual = helloWorld.Demo();
var expected = 9;
this.AssertEquals(expected, actual);

Cảm ơn vi đa trả lơi. Trong khi tôi nghĩ tái cấu trúc chắc chắn là thông minh, bạn sẽ làm gì trong trường hợp bạn có một phép tính rất phức tạp (gọi nó là CalcFoo ()). CalcFoo là điều duy nhất tôi muốn được tiếp xúc. Việc tái cấu trúc sẽ là các chức năng riêng tư. Tôi đã nói với bạn rằng không bao giờ nên kiểm tra các chức năng riêng tư. Vì vậy, bên trái của bạn đang cố gắng kiểm tra đơn vị CalcFoo (có nhiều tra cứu) hoặc các chức năng mở của bạn (thay đổi chúng thành công khai) chỉ để chúng có thể được kiểm tra đơn vị nhưng người gọi không bao giờ nên sử dụng chúng.
mã hóa4fun

3
"tái cấu trúc nên là mối quan tâm hàng đầu của bạn; thử nghiệm sẽ diễn ra sau đó" - Tôi hoàn toàn không đồng ý! Một điểm chính của các bài kiểm tra đơn vị là làm cho việc tái cấu trúc ít rủi ro hơn.
JacquesB

@oding4fun: bạn có chắc rằng mã của bạn được kiến ​​trúc chính xác và nó có tuân thủ nguyên tắc trách nhiệm đơn không? Có lẽ lớp học của bạn đang làm quá nhiều và nên được chia thành nhiều lớp nhỏ hơn?
Arseni Mourzenko

@JacquesB: nếu một phương thức sử dụng hơn 100 lần tra cứu (và có lẽ nó cũng làm những việc khác), không có cách nào bạn có thể viết bài kiểm tra đơn vị cho nó. Tích hợp, hệ thống và kiểm tra chức năng Có lẽ (có thể sẽ giảm nguy cơ hồi quy khi tái cấu trúc quái vật).
Arseni Mourzenko

1
@ user2357112: lỗi của tôi, tôi nghĩ rằng mã đang thực hiện các cuộc gọi đến hơn 100 lần tra cứu, nghĩa là hơn 100 bảng tra cứu. Tôi chỉnh sửa câu trả lời. Cảm ơn bạn đã chỉ ra điều này.
Arseni Mourzenko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.