本項目Github地址(同時包括兩個作業項目):
Assignment03 -- https://github.com/Oberon-Zheng/SoftwareEngineeringAssignments
st=>start: Start
e=>end: End
cond=>condition: Option
op1=>operation: solution_1
op2=>operation: solution_2
st->cond
cond(yes)->op1->e
cond(no)->op2->e
測試案例
A - 最大子列和(Sum of the maximum subarray)問題
問題: 給定n個整數(可能為負數)組成的序列a[1],a[2],a[3],…,a[n],求該序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。當所給的整數均為負數時定義子段和為0,依此定義,所求的最優值為: Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n
例如,當(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)時,最大子段和為20。
- Baidu Baike
關於我個人對於此問題的算法考慮我將另起一個博文(會吾倚馬萬言,易此幟以外鏈)發布,這里直接使用Kadane算法(使用Python完成的一個子程序,這里改寫為C++的)加以實現,關於這個算法的具體實現細節將同樣在那篇博客連同我自己的想法一同發布,這里先給出這個程序的流程圖(根據mermaid語法繪出):
I'll be your mermaid, caught on your rod
- Memorized Mermaid
-ScottSkott
注意,由於cnblogs的mermaid組件需要經過google的code analytics服務器,因此在轉換為正確的流程圖之前需要花費一段時間
So, stay tuned, plz!
我有雅各布的天梯,我天下無敵,我藐視銀河系
ESO黑洞警告
上述程序能夠求得關於傳入序列arry的一個最大子列和(下文簡稱最大和),並且能夠得到這個最大子列在arry中的位置。
案例實現
本案例以C++語言加以實現,代碼如下:
///File : MaxSubarray.h
#pragma once
#include <vector>
using namespace std;
struct RangeBlock
{
int sum;
int start;
int end;
};
/// File : MaxSubarray.c
#include "MaxSubarray.h"
#define POSCLIP(X) (X>0)?X:0
#define MAX(X,Y) (X>Y)?X:Y
using namespace std;
RangeBlock MaxSubArray(vector<int> arry)
{
RangeBlock rb = { 0,-1,-1 };
if (arry.size() == 0)
{
return rb;
}
int maxsum = 0;
int sum = 0;
int kickoff = 0;
for (int i = 0; i < arry.size(); i++)
{
sum = POSCLIP(sum + arry[i]);
maxsum = MAX(sum, maxsum);
if (sum == 0 && arry[i] < 0)
{
kickoff = i + 1;
//rb.end = rb.start;
}
else if(sum == maxsum)
{
rb.start = kickoff;
rb.end = i;
}
}
rb.sum = maxsum;
return rb;
}
這個程序傳入一個標准庫vector<int>容器作為輸入(之所以使用vector是因為vector的長度可變),然后通過一個RangeBlock型對象返回最大子列的和與區間。
根據上面的流程圖,這里面存在一個長度不定的循環,並且在循環內仍然有一個分支,因此這里面想要對循環內的所有可能情況做出測試是不可能的,因此for內的測試僅對若干種條件組合(每個數據會分別將覆蓋內部的三個分支)。
單元測試
本單元測試將滿足判定-條件覆蓋(所有的判定都走一次,所有的條件分兩個分支都滿足一次)
歸納上述代碼可以得到如下的條件判定列表:
arry.size()==0 <---> arry.size()!=0 --[1]
i < arry.size() <---> i >= arry.size() --[2]
sum==0 <--->sum!=0 --[3]
arry[i]<0 <---> arry[i] >=0 --[4]
sum == maxsum <---> sum != maxsum --[5]
根據代碼的執行情況來看,其中[3]和[4]存在條件組合,實際上當[3]的左命題成立時,[4]的右命題一定不成立,同時若[2]右、[3]右、[4]左成立則[5]左一定不成立(arry[i]<0,maxsum>=sum ==>> sum+arry[i]<maxsum),而[2]的命題在執行一句for循環的時候總會有成立和不成立(維持循環和跳出循環),對於上述推斷為一定不成立的關系下,無法找出具體的測試用例(因為是邏輯關系制約的不可能發生,比如我無法找到sum==0 && arry[i]>0的測試數據,因為根本寫不出來,除非遭到某種硬件錯誤導致arry[i]或者sum發生翻轉,但這不是單元測試能夠解決的),對於for的測試主要聚焦於OBOE問題(一步之差),而當[1]的前命題成立時,實際上之后的條件實際上都走不到,但如果必要考慮后面的條件的話,實際上只能討論i>=arry.size(),for循環實際上也執行不了,因此我們歸納出如下的條件組合:
arry.size() == 0 --[S1]
arry.size() != 0 && sum == 0 && arry[i]<0 && sum == maxsum --[S2]
arry.size() != 0 && sum == 0 && arry[i]<0 && sum != maxsum --[S3]
array.size() != 0 && sum != 0 && arry[i] < 0 && sum != maxsum --[S4]
array.size() != 0 && sum != 0 && arry[i] >= 0 && sum == maxsum --[S5]
array.size() != 0 && sum != 0 && arry[i] >= 0 && sum != maxsum --[S6]
單元測試將圍繞[S1]到[S6]產生測試數據如下:
| S1 | S2 | S3 | S4 | S5 | S6 |
|---|---|---|---|---|---|
| 空數組 | 全負數組 | 第一個負數 | 有正數的時候加入負數 | 加入非負數(開始) | 加入非負數(繼續加入) |
根據上述歸納,編訂了如下的測試數據:
#include "stdafx.h"
#include "CppUnitTest.h"
#include "..\Assignment03\MaxSubarray.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestA03
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestEmpty) //S1
{
std::vector<int> a;
RangeBlock rb = MaxSubArray(a);
Assert::AreEqual(rb.sum, 0);
Assert::AreEqual(rb.start, -1);
Assert::AreEqual(rb.end, -1);
}
TEST_METHOD(TestAllNegative) //S2
{
std::vector<int> a = { -1,-5,-8,-10,-6,-40,-1630,-5 };
RangeBlock rb = MaxSubArray(a);
Assert::AreEqual(rb.sum, 0);
Assert::AreEqual(rb.start, -1);
Assert::AreEqual(rb.end, -1);
}
TEST_METHOD(TestAllZero) //S4
{
std::vector<int> a(10, 0);
RangeBlock rb = MaxSubArray(a);
Assert::AreEqual(rb.sum, 0);
Assert::AreEqual(rb.start, 0);
Assert::AreEqual(rb.end, 9);
}
TEST_METHOD(TestOscillating) // S3和S5
{
std::vector<int> a = {0, 1,-2,3,-4,5,-6,7 };
RangeBlock rb = MaxSubArray(a);
Assert::AreEqual(rb.sum, 7);
Assert::AreEqual(rb.start, 7);
Assert::AreEqual(rb.end, 7);
}
TEST_METHOD(TestInhaling) // S4和S5
{
std::vector<int> a = {1, 3,-2,-3,0,0,-6,-10 };
RangeBlock rb = MaxSubArray(a);
Assert::AreEqual(rb.sum, 4);
Assert::AreEqual(rb.start, 0);
Assert::AreEqual(rb.end, 1);
}
TEST_METHOD(TestNormal) //綜合測試
{
std::vector<int> a = { 1,3,2,-4,0,5,8,-1,-10 };
RangeBlock rb = MaxSubArray(a);
Assert::AreEqual(rb.sum, 15);
Assert::AreEqual(rb.start, 0);
Assert::AreEqual(rb.end, 6);
}
};
}
最終各測試均通過:

Kadane算法在\(\omicron(n+k)\)的復雜度下得到了正確的結果。
B - 階梯營業稅問題
下表為某商場每日營業額與應收稅率的對照表,請編寫一小程序根據該商場每日營業額計算其實際應繳納稅費。
營業額X/¥|稅率
:---😐:---:
1000≤X<5000|5%
5000 ≤X<10000|8%
X≥10000|10%
這個項目使用C#及其單元測試功能。
(為什么不用C++呢,emmmm……因為玩的就是花里胡哨!)
案例實現
這個項目相對比較簡單,這里直接給出代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Assignment03_2
{
public class Business
{
private float turnover;
public static Dictionary<float, float> TaxRate = new Dictionary<float, float> { { 0.00f, 0.00f }, { 1000.00f, 0.05f }, { 5000.00f, 0.08f }, { 10000.00f, 0.10f } };
public float Turnover
{
get
{
return turnover;
}
set
{
turnover = value;
}
}
public float Tax
{
get
{
if(float.IsNaN(turnover) || float.IsInfinity(turnover) || turnover < 0.00f)
{
throw new Exception("Invalid turnover");
}
else
{
if(turnover < 1000.00f)
{
return 0.00f;
}
else if(turnover < 5000.00f)
{
return (turnover - 1000.00f) * 0.05f;
}
else if(turnover < 10000.00f)
{
return 200 + (turnover - 5000.00f) * 0.08f;
}
else
{
return 200 + 400 + (turnover - 10000.00f) * 0.10f;
}
}
}
}
public Business()
{
turnover = 0;
}
public Business(float turnover)
{
this.turnover = turnover;
}
}
}
單元測試
分析上述代碼發現,這里面的條件分支主要是圍繞浮點型的turnover字段展開的。
關於浮點型,首先有稅率表中的取值划分以外,float型還存在負取值以及float.NaN和float.PositiveInfinity和float.NegativeInfinity這些取值,基於這些考慮,單元測試所使用的數據也必須涵蓋這些可能的范圍,因此給出單元測試程序如下:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Assignment03_2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Assignment03_2.Tests
{
[TestClass()]
public class BusinessTests
{
[TestMethod()]
public void TestInvalid()
{
Business b = new Business(float.NaN);
try
{
float t = b.Tax;
}
catch(Exception e)
{
Assert.AreEqual(e.Message, "Invalid turnover");
}
b.Turnover = float.NegativeInfinity;
try
{
float t = b.Tax;
}
catch (Exception e)
{
Assert.AreEqual(e.Message, "Invalid turnover");
}
b.Turnover = float.PositiveInfinity;
try
{
float t = b.Tax;
}
catch (Exception e)
{
Assert.AreEqual(e.Message, "Invalid turnover");
}
b.Turnover = -5000.00f;
try
{
float t = b.Tax;
}
catch (Exception e)
{
Assert.AreEqual(e.Message, "Invalid turnover");
}
}
public void TestTax()
{
Business b = new Business();
b.Turnover = 500.00f;
Assert.AreEqual(b.Tax, 0.00f, float.Epsilon);
b.Turnover = 1000.00f;
Assert.AreEqual(b.Tax, 0.00f, float.Epsilon);
b.Turnover = 2500.00f;
Assert.AreEqual(b.Tax, 75.00f, float.Epsilon);
b.Turnover = 5000.00f;
Assert.AreEqual(b.Tax, 200.00f, float.Epsilon);
b.Turnover = 7500.00f;
Assert.AreEqual(b.Tax, 400.00f, float.Epsilon);
b.Turnover = 10000.00f;
Assert.AreEqual(b.Tax, 600.00f, float.Epsilon);
b.Turnover = 20000.00f;
Assert.AreEqual(b.Tax, 1600.00f, float.Epsilon);
}
}
}
最終,各項測試全部通過

