軟件工程第3次做業——Visual Studio 2017下針對代碼覆蓋率的C/C++單元測試

本項目Github地址(同時包括兩個做業項目):
Assignment03 -- https://github.com/Oberon-Zheng/SoftwareEngineeringAssignmentsgit

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 Baikegithub

關於我我的對於此問題的算法考慮我將另起一個博文(會吾倚馬萬言,易此幟之外鏈)發佈,這裏直接使用Kadane算法(使用Python完成的一個子程序,這裏改寫爲C++的)加以實現,關於這個算法的具體實現細節將一樣在那篇博客連同我本身的想法一同發佈,這裏先給出這個程序的流程圖(根據mermaid語法繪出):算法

I'll be your mermaid, caught on your rod
- Memorized Mermaid
- Scott Skott小程序

注意,因爲cnblogs的mermaid組件須要通過google的code analytics服務器,所以在轉換爲正確的流程圖以前須要花費一段時間
So, stay tuned, plz!
我有雅各布的天梯,我天下無敵,我藐視銀河系
ESO黑洞警告數組

graph TD start(開始)-->inputarry>"傳入arry"] inputarry-->domakeres["(rb:RangeBlock):={sum:=0,start:=-1,end:=-1}"] domakeres-->tryempty{"arry.size()=0?"} tryempty-->|true|return>"返回rb"] return-->e("結束") tryempty-->|false|init["maxsum=0<br />sum=0<br />kickoff=0<br />i=0"] init-->tryloop{"i&gt;arry.size()"} tryloop-->|true|accumulate["sum:=arry<br />若sum&lt;0則sum:=0"] accumulate-->max["maxsum:=max{sum,maxsum}"] max-->tryneg{"arry[i]是負的同時arry[i]的絕對值大於原來sum<br />(sum=0且arry[i]&lt;0)"} tryneg-->|true|rststart["kickoff:=i+1"] tryneg-->|false|inhale["rb.start:=kickoff<br />rb.end:=i"] sendmax["rb.sum:=maxsum"] inhale-->sendmax rststart-->sendmax sendmax-->return

上述程序可以求得關於傳入序列arry的一個最大子列和(下文簡稱最大和),而且可以獲得這個最大子列在arry中的位置。服務器

案例實現

本案例以C++語言加以實現,代碼以下:dom

///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內的測試僅對若干種條件組合(每一個數據會分別將覆蓋內部的三個分支)。oop

單元測試

本單元測試將知足斷定-條件覆蓋(全部的斷定都走一次,全部的條件分兩個分支都知足一次) 概括上述代碼能夠獲得以下的條件斷定列表:單元測試

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.NaNfloat.PositiveInfinityfloat.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);
        }
    }
}

最終,各項測試所有經過

相關文章
相關標籤/搜索