using FsCheck;
using FsCheck.Xunit;
using WorkCameraExport.Forms;
using Xunit;
using SystemRandom = System.Random;
namespace WorkCameraExport.Tests
{
///
/// 图片上传数量属性测试
/// Feature: work-camera-2.0.1, Property 3: 图片上传数量限制
/// Feature: work-camera-2.0.1, Property 4: 图片删除后列表更新
/// Validates: Requirements 4.5, 4.6
///
public class ImageUploadPropertyTests
{
private const int MaxImageCount = 15;
#region Property 3: 图片上传数量限制
///
/// Property 3: 图片上传数量限制
/// For any 上传操作,图片列表长度应始终不超过 15。
/// 当尝试添加图片使总数超过 15 时,应拒绝添加并提示用户。
///
[Property(MaxTest = 100)]
public Property ImageUpload_ShouldNeverExceedMaxCount()
{
return Prop.ForAll(
Gen.Choose(0, 20).ToArbitrary(), // 初始图片数量 0-20
Gen.Choose(1, 10).ToArbitrary(), // 尝试添加的图片数量 1-10
(initialCount, addCount) =>
{
// 模拟图片列表
var imageList = new List();
// 添加初始图片(模拟已有图片)
var actualInitial = Math.Min(initialCount, MaxImageCount);
for (int i = 0; i < actualInitial; i++)
{
imageList.Add(new ImageItem { SortOrder = i, IsExisting = true });
}
// 计算可添加的数量
var remainingSlots = MaxImageCount - imageList.Count;
var canAdd = Math.Min(addCount, remainingSlots);
// 模拟添加图片
for (int i = 0; i < canAdd; i++)
{
imageList.Add(new ImageItem { SortOrder = imageList.Count, IsExisting = false });
}
// 验证:图片数量不超过最大值
var countWithinLimit = imageList.Count <= MaxImageCount;
return countWithinLimit
.Label($"Initial: {actualInitial}, Tried to add: {addCount}, " +
$"Actually added: {canAdd}, Final count: {imageList.Count} (max: {MaxImageCount})");
});
}
///
/// Property 3 扩展: 当已达到最大数量时,不应添加任何图片
///
[Property(MaxTest = 100)]
public Property ImageUpload_WhenAtMax_ShouldNotAddMore()
{
return Prop.ForAll(
Gen.Choose(1, 10).ToArbitrary(), // 尝试添加的图片数量
(addCount) =>
{
// 模拟已满的图片列表
var imageList = new List();
for (int i = 0; i < MaxImageCount; i++)
{
imageList.Add(new ImageItem { SortOrder = i, IsExisting = true });
}
var initialCount = imageList.Count;
// 计算可添加的数量(应该是0)
var remainingSlots = MaxImageCount - imageList.Count;
var canAdd = Math.Min(addCount, remainingSlots);
// 验证:不应添加任何图片
var noAddition = canAdd == 0;
var countUnchanged = imageList.Count == initialCount;
return (noAddition && countUnchanged)
.Label($"Tried to add: {addCount}, Can add: {canAdd}, " +
$"Count unchanged: {countUnchanged}");
});
}
///
/// Property 3 扩展: 添加的图片数量应等于 min(请求数量, 剩余槽位)
///
[Property(MaxTest = 100)]
public Property ImageUpload_AddedCount_ShouldEqualMinOfRequestedAndRemaining()
{
return Prop.ForAll(
Gen.Choose(0, MaxImageCount).ToArbitrary(), // 初始图片数量
Gen.Choose(1, 20).ToArbitrary(), // 尝试添加的图片数量
(initialCount, addCount) =>
{
var remainingSlots = MaxImageCount - initialCount;
var expectedAdded = Math.Min(addCount, Math.Max(0, remainingSlots));
var expectedFinal = initialCount + expectedAdded;
// 验证计算逻辑
var addedCorrect = expectedAdded == Math.Min(addCount, Math.Max(0, remainingSlots));
var finalCorrect = expectedFinal <= MaxImageCount;
return (addedCorrect && finalCorrect)
.Label($"Initial: {initialCount}, Requested: {addCount}, " +
$"Expected added: {expectedAdded}, Expected final: {expectedFinal}");
});
}
///
/// Property 3 扩展: 边界值测试 - 恰好 15 张图片
///
[Fact]
public void ImageUpload_ExactlyMax_ShouldNotAllowMore()
{
var imageList = new List();
for (int i = 0; i < MaxImageCount; i++)
{
imageList.Add(new ImageItem { SortOrder = i });
}
var remainingSlots = MaxImageCount - imageList.Count;
Assert.Equal(MaxImageCount, imageList.Count);
Assert.Equal(0, remainingSlots);
}
///
/// Property 3 扩展: 边界值测试 - 14 张图片,添加 1 张
///
[Fact]
public void ImageUpload_OneBelowMax_ShouldAllowOneMore()
{
var imageList = new List();
for (int i = 0; i < MaxImageCount - 1; i++)
{
imageList.Add(new ImageItem { SortOrder = i });
}
var remainingSlots = MaxImageCount - imageList.Count;
Assert.Equal(MaxImageCount - 1, imageList.Count);
Assert.Equal(1, remainingSlots);
}
#endregion
#region Property 4: 图片删除后列表更新
///
/// Property 4: 图片删除后列表更新
/// For any 图片删除操作,删除后图片列表长度应减少 1,
/// 且被删除的图片不应存在于列表中。
///
[Property(MaxTest = 100)]
public Property ImageDelete_ShouldReduceCountByOne()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量 1-15
(initialCount) =>
{
// 创建图片列表
var imageList = new List();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem
{
Id = i,
SortOrder = i,
Url = $"http://example.com/image{i}.jpg"
});
}
// 随机选择一个索引删除
var deleteIndex = new SystemRandom().Next(0, imageList.Count);
var deletedItem = imageList[deleteIndex];
// 执行删除
imageList.RemoveAt(deleteIndex);
// 验证:数量减少 1
var countReduced = imageList.Count == initialCount - 1;
// 验证:被删除的图片不在列表中
var itemRemoved = !imageList.Any(i => i.Id == deletedItem.Id);
return (countReduced && itemRemoved)
.Label($"Initial: {initialCount}, After delete: {imageList.Count}, " +
$"Deleted item removed: {itemRemoved}");
});
}
///
/// Property 4 扩展: 删除后排序应保持连续
///
[Property(MaxTest = 100)]
public Property ImageDelete_ShouldMaintainSortOrder()
{
return Prop.ForAll(
Gen.Choose(2, MaxImageCount).ToArbitrary(), // 初始图片数量 2-15(至少2张才能测试删除后排序)
(initialCount) =>
{
// 创建图片列表
var imageList = new List();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 随机选择一个索引删除
var deleteIndex = new SystemRandom().Next(0, imageList.Count);
imageList.RemoveAt(deleteIndex);
// 重新排序
for (int i = 0; i < imageList.Count; i++)
{
imageList[i].SortOrder = i;
}
// 验证:排序是连续的 0, 1, 2, ...
var sortOrderCorrect = true;
for (int i = 0; i < imageList.Count; i++)
{
if (imageList[i].SortOrder != i)
{
sortOrderCorrect = false;
break;
}
}
return sortOrderCorrect
.Label($"Initial: {initialCount}, After delete: {imageList.Count}, " +
$"Sort order correct: {sortOrderCorrect}");
});
}
///
/// Property 4 扩展: 删除所有图片后列表应为空
///
[Property(MaxTest = 100)]
public Property ImageDelete_AllImages_ShouldResultInEmptyList()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量
(initialCount) =>
{
// 创建图片列表
var imageList = new List();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 删除所有图片
while (imageList.Count > 0)
{
imageList.RemoveAt(0);
}
// 验证:列表为空
return (imageList.Count == 0)
.Label($"Initial: {initialCount}, After deleting all: {imageList.Count}");
});
}
///
/// Property 4 扩展: 删除无效索引应不改变列表
///
[Property(MaxTest = 100)]
public Property ImageDelete_InvalidIndex_ShouldNotChangeList()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量
Gen.Choose(-10, -1).ToArbitrary(), // 无效的负索引
(initialCount, invalidIndex) =>
{
// 创建图片列表
var imageList = new List();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 尝试删除无效索引(模拟边界检查)
var canDelete = invalidIndex >= 0 && invalidIndex < imageList.Count;
// 验证:无效索引不应允许删除
return (!canDelete)
.Label($"Initial: {initialCount}, Invalid index: {invalidIndex}, " +
$"Can delete: {canDelete}");
});
}
///
/// Property 4 扩展: 删除后可以继续添加图片
///
[Property(MaxTest = 100)]
public Property ImageDelete_ThenAdd_ShouldWork()
{
return Prop.ForAll(
Gen.Choose(1, MaxImageCount).ToArbitrary(), // 初始图片数量
Gen.Choose(1, 5).ToArbitrary(), // 删除数量
Gen.Choose(1, 5).ToArbitrary(), // 添加数量
(initialCount, deleteCount, addCount) =>
{
// 创建图片列表
var imageList = new List();
for (int i = 0; i < initialCount; i++)
{
imageList.Add(new ImageItem { Id = i, SortOrder = i });
}
// 删除图片
var actualDelete = Math.Min(deleteCount, imageList.Count);
for (int i = 0; i < actualDelete; i++)
{
if (imageList.Count > 0)
{
imageList.RemoveAt(0);
}
}
var afterDelete = imageList.Count;
// 添加图片
var remainingSlots = MaxImageCount - imageList.Count;
var actualAdd = Math.Min(addCount, remainingSlots);
for (int i = 0; i < actualAdd; i++)
{
imageList.Add(new ImageItem { Id = 100 + i, SortOrder = imageList.Count });
}
var finalCount = imageList.Count;
// 验证:最终数量正确
var expectedFinal = afterDelete + actualAdd;
var countCorrect = finalCount == expectedFinal;
var withinLimit = finalCount <= MaxImageCount;
return (countCorrect && withinLimit)
.Label($"Initial: {initialCount}, Deleted: {actualDelete}, " +
$"Added: {actualAdd}, Final: {finalCount} (expected: {expectedFinal})");
});
}
///
/// Property 4 扩展: 边界值测试 - 删除唯一的图片
///
[Fact]
public void ImageDelete_SingleImage_ShouldResultInEmptyList()
{
var imageList = new List
{
new ImageItem { Id = 1, SortOrder = 0 }
};
imageList.RemoveAt(0);
Assert.Empty(imageList);
}
///
/// Property 4 扩展: 边界值测试 - 删除第一张图片
///
[Fact]
public void ImageDelete_FirstImage_ShouldShiftOthers()
{
var imageList = new List
{
new ImageItem { Id = 1, SortOrder = 0 },
new ImageItem { Id = 2, SortOrder = 1 },
new ImageItem { Id = 3, SortOrder = 2 }
};
imageList.RemoveAt(0);
// 重新排序
for (int i = 0; i < imageList.Count; i++)
{
imageList[i].SortOrder = i;
}
Assert.Equal(2, imageList.Count);
Assert.Equal(2, imageList[0].Id);
Assert.Equal(0, imageList[0].SortOrder);
}
///
/// Property 4 扩展: 边界值测试 - 删除最后一张图片
///
[Fact]
public void ImageDelete_LastImage_ShouldNotAffectOthers()
{
var imageList = new List
{
new ImageItem { Id = 1, SortOrder = 0 },
new ImageItem { Id = 2, SortOrder = 1 },
new ImageItem { Id = 3, SortOrder = 2 }
};
imageList.RemoveAt(2);
Assert.Equal(2, imageList.Count);
Assert.Equal(1, imageList[0].Id);
Assert.Equal(2, imageList[1].Id);
}
#endregion
}
}