[活動筆記] 變異測試 - 一種改進測試和代碼的 「新」 方法

應該知道的事

  • 範例使用 Java
  • 這場活動使用人肉找尋變異
  • 實務上應使用工具
  • 但是不能完全相信工具
  • 活動聯結
  • 講師是 Odd-e 的姚若舟
  • 簡報 preview 版

什麼是變異

前言

想像一下產品(Production)就是你的身體,
我們可以透過健康檢查(Unit Test);
檢查你的身體有沒有異狀 ?

但是檢查真的可靠嗎 ?
比如說一般的流感的快篩只有 50~60%的準確率,
我們的測試也無法達到 100%準確率(這裡不是指覆蓋率喔).
如何抓到測試抓不到的漏網之魚就是變異測試的目的.

我們透過讓 Production 產生變異(Mutation)
來確認我們的 Unit Test 是否可靠.

題外話,當大流行的時候會跳過快篩節省醫療資源,
因為可能有一半(50%)的患者都是流感,
而快篩準確率也只有 50%,加上時間及醫材成本,
不如直接開克流感能有效抑止疫情

變異測試(Mutation Testing)

變異後導致測試失敗?
yes , good
應該要失敗,表示你的測試有覆蓋到這個變異

no , test not covered
這表示你的測試並未

測試不一定能補捉變異

比如說 邊際值 或是 隱含的互動;
測試覆蓋率 100%也不一定能補捉變異
要麼少了 test case,
要麼多了無意義的代碼
看看以下例子
ex:

1
2
3
4
5
6
foo(x,y)
{
//// logic here
sideeffct();
return z;
}

反思一下, 測試過了代碼就沒問題 ?
不能捉到變異的測試,
有發揮它的功能嗎 ?
一般來說如果透過 TDD 進行軟體開發,
我們的測試應該是會恰巧符合一項 Test Case
而如果是先寫代碼再寫測試,
將很難通過變異測試(容易產生多餘的代碼)

找到變異的幾個方向

  • 邊界條件(< => <=)
  • 反向條件(< => >)
  • 移除條件(永真/永偽)
  • 數學
  • 遞增/遞減
  • 常量
  • 返回值
  • 移除代碼

先寫代碼再寫測試有問題是很正常的

Kata-PokerHands 範例

原碼(使用 java)

變異實例

有問題 ,反向測試案例不足

1
2
3
4
5
6
7
private List<Integer> getPairCardRanks(List<Integer> cardRanks) {
List<Integer> result = new ArrayList<>();
for (int index = 0; index < CARD_COUNT - 1; index++)
if (isTwoNeighborCardRanksEquals(index, cardRanks))
result.add(cardRanks.get(index));
return result;
}

有問題 , -1 但是預期中的行為

1
2
3
4
5
6
protected   Integer   getThreeOfAKindCardRank(List<Integer\> cardRanks) {
for (int index = 0; index < CARD_COUNT - 2; index++)
if (isThreeNeighborCardRanksEquals(index, cardRanks))
return cardRanks.get(index);
throw new IllegalStateException();
}

其它

  1. 沒有 TDD 沒有單元測試,別跑變異測試
  2. 至少要有行級別的覆蓋率(line coverage)
  3. 分支覆蓋(Branch Coverage)好一點 仍不夠
  4. 在需求不變的情況下,再作變異測試
  5. 以變異測試的角度來說,覆蓋率 100%是木有用的(testing coverage is useless)
  6. 發現變異怎麼辦
    • 報告(記錄)
    • 重現
    • 評估
    • 修改 或 補測試
  7. 依靠工具不要相信工具,上一步的評估
    Ex: mock 物件會取代互動實際的行為,導致變異測試失敗

Tools

參與者心得

  1. 變異測試 (Mutation Test) —  一種提高測試和代碼質量的 ”新”  方法速記

  2. Test - 變異(Mutation)測試之你的測試到底是寫爽的,還是有效的?

心得

  1. 佩服當天就能寫出文章的人
  2. 變異測試是好上加好的測試
  3. Odd-e 的講師真的很粉棒, 雖然不致到毀三觀 不過眼界大開

參考

(fin)

[活動筆記] 蝦皮購物新加坡研發團隊技術分享會

應該不用知道的事

  1. 雖然是「技術分享會」實際上在徵才
  2. 不過還是有半場的技術分享
  3. 91app 至少去了 10 個人(含前員工)
  4. 這篇文章對你應該沒有幫助

有關蝦皮

  • 屬於Sea 集團的一部份
  • 東南亞多國服務(新加坡、泰國、馬來西亞、印度、台灣、越南…)
  • 63e Request / Day
  • 8G IO / Mins

選擇

  • Native App / Web / Hybrid / RN ?
  • Cloud / Self machine ?
  • Php/ Nodejs / RoR / Django ?
  • Apache / Nginx ?
  • C ++ / Java / GoLang
  • Memcached / Redis ?
  • SPA / MPA ?
  • Mesos / Kubernetes ?

Qiz & Ans

1

用戶下單的時候, 先收錢還是先扣庫存?
扣掉最後一件庫存後, 收錢失敗怎麼辦?
你已經把「賣完」訊息發給了賣家, 怎麼辦?

2

計算金額用整數還是浮點數?(浮點數不準)

3

Android 一共有幾種螢幕的 DPI ?
Android WebView 和 Chrome 的 Webkit 有何不同 ?

4

Web Service 花最多時間在處理什麼 ?
如何壓搾最高的吞吐量 ?
IO, USE async

5

什麼樣的情境適合增加伺服器數量來增進效能?
stateless
那有狀態怎麼辦 ?

6

load balancer 效能到達瓶頸怎麼辦 ?
IP

7

一天 25TB 的 Log 數量,怎麼不會查到天荒地老

8

Cache & 超賣問題
什麼時候要清 Cache ?

9

Database Master 與 Slave 哪個壓力大 ?(Slave)
增加 index 的代價為何 ?(Space)
Table 多大要 shard ?
Database 多大要分庫 ?
分庫如何作 transaction ?

實踐

  1. Prototype 簡單 Production 困難 (邊際效應/熵)
  2. 可靠:言出必行,作不到也要早點說(知難行易)
  3. Redis 的資料超過 64G 就無法用 bgsave 有效存檔
  4. 在 Production 千萬別用 Redis 的 key 指令
  5. 衡量的基準(benchmark)為何?
  6. 不要對邏輯下 command(不要寫前因後果)
    • Don’t command How
    • Command Why
    • Collect your dots first
    • Connecting the dots

持久發展的研發團隊

  • knowledge
  • 保持開放
  • 尊重事實
  • 信任
  • 可靠
  • 找到根本原因(root cause)
  • 分析 修復 記錄
  • Docs
    • connection docs
    • collection docs

測試

  • 白箱測試
  • 黑箱測試

其它

  • Hypergraph
  • Tech Stack
  • Roles
    • countries PM
    • function PM
  • Scrum 是跑給老闆看的(!!?)
  • 馬來西亞不用小豬 ICON(各地風俗民情不同)

參考

(fin)

[翻譯] C# 的常見錯誤

出處

http://www.dotnetcurry.com/csharp/1417/csharp-common-mistakes

線上工具

https://dotnetfiddle.net

引言

C#是個好棒棒的言語,但是它仍會有超乎你想像的行為,
而且就算你是有經驗的開發者,你也要看一看這篇文章.
這篇文章不講幹話,還會給你代碼喔

C# Quiz

Null Value

Null 很危險啦, 你別在 Null 身上調用方法
(譯注:在公司的維運人員應該還蠻常見這個錯誤的 一 ω 一)

We are all aware that null values can be dangerous, if not handled properly.
Dereferencing a null-valued variable (i.e. calling a method on it or accessing one of its properties)
will result in a NullReferenceException, as demonstrated with the following sample code:

1
2
object nullValue = null;
bool areNullValuesEqual = nullValue.Equals(null);

就安全的角度,好像我們要不停的檢查 reference type 是不是 null ,
雖然這件事常常發生,好像也很難說成是非預期的行為了…
(譯注:又有種中槍的感覺)

To be on the safer side, we should always make sure that reference type values are not null before dereferencing them.
Failing to do so could result in an unhandled exception in a specific edge case.
Although such a mistake occasionally happens to everyone, we could hardly call it unexpected behavior.

看看這個代碼, null 值在 runtime 的時候不會有 type 的

1
2
string nullString = (string)null;
bool isStringType = nullString is string;

No, null 值在 runtime 的時候不會有 type 的
No, null 值在 runtime 的時候不會有 type 的
No, null 值在 runtime 的時候不會有 type 的
很重要所以說三次,
當然你也別想呼叫 GetType() 方法

The correct answer is No.

A null value has no type at runtime.

In a way, this also affects reflection.
Of course, you can’t call GetType() on a null value because a NullReferenceException would get thrown:

1
2
object nullValue = null;
Type nullType = nullValue.GetType();

純量呢?

1
2
3
int intValue = 5;
Nullable<int> nullableIntValue = 5;
bool areTypesEqual = intValue.GetType() == nullableIntValue.GetType();

那我們可不可能用反射(reflection)區分 nullable 跟 non-nullable 的值?
答案是不可能, 看看後面的代碼

Is it possible to distinguish between a nullable and a non-nullable value type using reflection?

The answer is No.

The same type will be returned for both variables in the above code: System.Int32.
This does not mean that reflection has no representation for Nullable, though.

1
2
3
Type intType = typeof(int);
Type nullableIntType = typeof(Nullable<int>);
bool areTypesEqual = intType == nullableIntType;

上面兩段程式在 runtime 拿到的 type 很不一樣喔,
一個是System.Int32一個是 System.Nullable'1\[System.Int32\]

當 null 遇上多載方法 (Handling Null values in Overloaded methods)

1
2
3
4
5
6
7
8
9
private string OverloadedMethod(object arg)
{
return "object parameter";
}

private string OverloadedMethod(string arg)
{
return "string parameter";
}

上面有兩個OverloadedMethod
猜猜看,傳入 null 時會呼叫哪一個方法?

1
var result = OverloadedMethod(null);

有人會猜編譯失敗嗎?
MAGIC ! 竟然可以編譯成功, 而回傳的值是 “string parameter” ,
一般來說,在編譯時期會作型別檢查,相同簽章的方法參數可以被轉型成另一個型別時,是可以編譯成功的喔.
而有明確型別的方法將被優先調用(譯注:求這段.Net Framework 的原碼來看一下,知道的人請告訴我)

如果要指定 null 參數呼叫的多載方法就要對 null 轉型唷,可以參考下面的方法.

1
var result = OverloadedMethod((object)null);

算術運算 (Arithmetic Operations)

好像很少用位移運算吼?
回憶一下 左移移 右移移

1
var shifted = 0b1 << 1; // = 0b10
1
var shifted = 0b1 >> 1; // = 0b0

bits 跑到底並不會重頭開始喔,一直移位到爆掉就變 0 了.
(這裡會用 32 是因為 int 是 32bit 的數值,你可以試試放超過 32 的數值到 for loop 裡會發生什麼事)

The bits don’t wrap around when they reach the end.
That’s why the result of the second expression is 0.
The same would happen if we shifted the bit far enough to the left (32 bits because integer is a 32-bit number):

1
2
3
4
5
var shifted = 0b1;
for (int i = 0; i < 32; i++)
{
shifted = shifted << 1;
}

The result would again be 0.

那我們是不是可以一次移 32 bit,讓它一次變成 0 呢?
靠北啊 竟然不行捏, 你只會拿到 1,
這跟運算子(operator)基本運算有關,在作位元運算的時候,
會拿第一個運算數除以第二個運算數後取餘數,
這導致我們只會拿 32 % 32 的結果 , 也就是 1 啦
(譯注:這段其實我不是很確定,如果錯誤請糾正)

However, the bit shifting operators have a second operand.
Instead of shifting to the left by 1 bit 32 times, we can shift left by 32 bits and get the same result.

1
var shifted = 0b1 << 32;

Right? Wrong.

The result of this expression will be 1. Why?

Because that’s how the operator is defined. Before applying the operation,
the second operand will be normalized to the bit length of the first operand with the modulo operation,
i.e. by calculating the remainder of dividing the second operand by the bit length of the first operand.

The first operand in the example we just saw was a 32-bit number, hence: 32 % 32 = 0.
Our number will be shifted left by 0 bits. That’s not the same as shifting it left by 1 bit 32 times.

好棒棒 你竟然可以看到這裡,
那我們繼續討論 & (and) 跟 | (or) 運算子吧,
這兩個運算子跟一般的運算子有點不一樣

  • 通常只要看運算子的第一個運算數就能得知結果
  • 在有掛 [Flag] attribute 的列舉它們好好用(看一下範例)
1
2
3
4
5
6
7
8
[Flags]
private enum Colors
{
None = 0b0,
Red = 0b1,
Green = 0b10,
Blue = 0b100
}
1
2
Colors color = Colors.Red | Colors.Green;
bool isRed = (color & Colors.Red) == Colors.Red;

上面這個刮號可不能省略喔, 因為(&)運算符的優先順序低於(==)運算符,
不過這段程式沒有刮號的話連編譯都不會過,真是好加在
另外在 .NET framework 4.0 之後的版本提供更棒的方法去檢查 flags

1
bool isRed = color.HasFlag(Colors.Red);

Math.Round()

猜一下這個值會是多少?

1
var rounded = Math.Round(1.5);

猜 2 的就答對了, 下一題
猜一下這個值會是多少?

1
var rounded = Math.Round(2.5);

還是 2 ,
因為預設會取最接近的偶數

No. The result will be 2 again. By default,
the midpoint value will be rounded to the nearest even value.
You could provide the second argument to the method to request such behavior explicitly:

1
var rounded = Math.Round(2.5, MidpointRounding.ToEven);

這個行為可以透過MidpointRounding參數改變

1
var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero);

另外要小心浮點數的精度問題,
以下的例子結果會是 1,( 因為 float 的 0.1 實際上小於 0.1 一 ω 一 )
這提醒我們在處理精確數值時,應轉換成整數處理.
(譯注:使用 dotnet fiddle 時並不會有這個問題, 在 windows 環境下測試的確會有問題)

1
2
3
var value = 1.4f;

var rounded = Math.Round(value + 0.1f);

類別初始化

最佳實踐建我我們應該避免在建構子初始化類別,
特別是靜態建構子.
在初始化一個類別的順序如下

  1. 靜態欄位
  2. 靜態建構子
  3. 實體欄位
  4. 實體建構子

看看這個例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class Config
{
public static bool ThrowException { get; set; } = true;
}

public class FailingClass
{
static FailingClass()
{
if (Config.ThrowException)
{
throw new InvalidOperationException();
}
}
}

當我們嘗試實例化 FailingClass 時,你會得到 Exception;
值得注意的事,你拿到的會是TypeInitializationException
而並不是InvalidOperationException,

那麼我們是不是可以試著透過 try catch 補捉錯誤,
並修改靜態屬性,重新實體化 class 呢?
答案是不行

一個靜態建構值,如果它拋出一個異常,
那麼無論何時你想創建一個實例或以任何其他方式訪問這個類,
這個異常都會被重新拋出.

1
2
3
4
5
6
7
try
{
var failedInstance = new FailingClass();
}
catch (TypeInitializationException) { }
Config.ThrowException = false;
var instance = new FailingClass();

這個類別在程序重啟前是不能再被使用了(會拋出錯誤),
這在 C# 是個非常糟糕的實踐,
千萬別這樣設計你的類別.

The static constructor for a class is only called once.
If it throws an exception, then this exception will be rethrown
whenever you want to create an instance or access the class in any other way.

The class becomes effectively unusable until the process (or the application domain) is restarted.
Yes, having even a minuscule chance that the
static constructor will throw an exception, is a very bad idea.

繼承與類別初始化

繼承的類別初始化執行順序更加複雜,看看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BaseClass
{
public BaseClass()
{
VirtualMethod(1);
}

public virtual int VirtualMethod(int dividend)
{
return dividend / 1;
}
}

public class DerivedClass : BaseClass
{
int divisor;
public DerivedClass()
{
divisor = 1;
}

public override int VirtualMethod(int dividend)
{
return base.VirtualMethod(dividend / divisor);
}
}

當我們初始化 DerivedClass

1
var instance = new DerivedClass();

你會得到一個除 0 的錯誤 DivideByZeroException
這與執行順序有關

  1. 呼叫 BaseClass 建構子
  2. 執行 DerivedClass VirtualMethod (overrid BaseClass)
  3. divisor 未賦值拋出 DivideByZeroException

多形 Polymorphism

這個例子只是要說明多形的概念與應用,
你可以透過轉形呼叫基底類別的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var instance = new DerivedClass();
var result = instance.Method(); // -> Method in DerivedClass
result = ((BaseClass)instance).Method(); // -> Method in BaseClass
// The correct answer is: by using the new modifier.

public class BaseClass
{
public virtual string Method()
{
return "Method in BaseClass ";
}
}

public class DerivedClass : BaseClass
{
public new string Method()
{
return "Method in DerivedClass";
}
}

It’s typically used to hide the interface methods from the consumers of the class implementing it,
unless they cast the instance to that interface.
But it works just as well if we want to have two different implementations of a method inside a single class.
It’s difficult to think of a good reason for doing it, though.

另外一個例子是明確實作介面方法,
如果你的類別已經有同名的方法的話.
雖然沒有什麼好理由建議你這樣作.
(譯注:實務上我有在遇到歷史共業這樣作過…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var instance = new DerivedClass();
var result = instance.Method(); // -> Method in DerivedClass
result = ((IInterface)instance).Method(); // -> Method belonging to IInterface
It’s explicit interface implementation.

public interface IInterface
{
string Method();
}

public class DerivedClass : IInterface
{
public string Method()
{
return "Method in DerivedClass";
}

string IInterface.Method()
{
return "Method belonging to IInterface";
}
}

迭代器 Iterators

小心 Iterators 的陷阱
看看以下代碼:

1
2
3
4
5
6
7
8
private IEnumerable<int> GetEnumerable(StringBuilder log)
{
using (var context = new Context(log))
{
return Enumerable.Range(1, 5);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Context : IDisposable
{
private readonly StringBuilder log;

public Context(StringBuilder log)
{
this.log = log;
this.log.AppendLine("Context created");
}

public void Dispose()
{
this.log.AppendLine("Context disposed");
}
}

假設我們 foreach 呼叫 GetEnumerable 方法,
你預期 Context 類別會有什麼樣的行為?
我們會印出以下的 output 嗎?

Context created
1
2
3
4
5
Context disposed

1
2
3
4
5
var log = new StringBuilder();
foreach (var number in GetEnumerable(log))
{
log.AppendLine($"{number}");
}

不是的,
實際上印出的是

Context created
Context disposed
1
2
3
4
5

這點很重要,
因為實務上你很有可能 using dbconnetion 之類的物件,
那麼你在取得真正的資料之前,
你的連線就已經中斷了

This means that in our real world database example, the code would fail –
the connection would be closed before the values could be read from the database.

看看以下的修正

1
2
3
4
5
6
7
8
9
10
private IEnumerable<int> GetEnumerable(StringBuilder log)
{
using (var context = new Context(log))
{
foreach (var i in Enumerable.Range(1, 5))
{
yield return i;
}
}
}

譯注:看到這裡對 yield return 的使用情境才比較有感啊…

如果你不太熟yield return,其實它只是個語法糖,允許增量執行,
參考以下範例,或許能更容易理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private IEnumerable<int> GetCustomEnumerable(StringBuilder log)
{
log.AppendLine("before 1");
yield return 1;
log.AppendLine("before 2");
yield return 2;
log.AppendLine("before 3");
yield return 3;
log.AppendLine("before 4");
yield return 4;
log.AppendLine("before 5");
yield return 5;
log.AppendLine("before end");
}
1
2
3
4
5
6
7
var log = new StringBuilder();
log.AppendLine("before enumeration");
foreach (var number in GetCustomEnumerable(log))
{
log.AppendLine($"{number}");
}
log.AppendLine("after enumeration");

before enumeration
before 1
1
before 2
2
before 3
3
before 4
4
before 5
5
before end
after enumeration

值得注意的事, 如果你在 loop 當中重複執行以上的代碼,
那麼 Iterators 也會重複執行

1
2
3
4
5
6
7
8
9
10
var log = new StringBuilder();
var enumerable = GetCustomEnumerable(log);
for (int i = 1; i <= 2; i++)
{
log.AppendLine($"enumeration #{i}");
foreach (var number in enumerable)
{
log.AppendLine($"{number}");
}
}

輸出如下,可以明顯看到 GetCustomEnumerable 方法,
實際上被隱含的執行了兩次,
這在 Code Review 的階段也是難以被察覺的.

enumeration #1
before 1
1
before 2
2
before 3
3
before 4
4
before 5
5
before end
enumeration #2
before 1
1
before 2
2
before 3
3
before 4
4
before 5
5
before end

比較好的作法是將 IEnumerable ToList(),
如果你真的需要對 IEnumerable 的結果作 loop 的操作

1
2
3
4
5
6
7
8
9
10
var log = new StringBuilder();
var enumerable = GetCustomEnumerable(log).ToList();
for (int i = 1; i <= 2; i++)
{
log.AppendLine($"enumeration #{i}");
foreach (var number in enumerable)
{
log.AppendLine($"{number}");
}
}

輸出結果

before 1
before 2
before 3
before 4
before 5
before end
enumeration #1
1
2
3
4
5
enumeration #2
1
2
3
4
5

譯者小結

如果真的能夠預期所有的行為的開發人員,
真的是好棒棒,
對我來說 static class constructor 的行為是超乎預期的,
然後對 yield return 的使用場景更有感覺了.
本來預計農曆年就可以完成的翻譯,
竟然也拖了這麼久,看來我英文還是不行啊.

希望對大家有幫助,也請多多看原文 :)

(fin)

[學習筆記] AWS 註冊到建立安全性帳戶

該知道的事

  1. 僅作為學習 AWS 的過程記錄用
  2. 可能對你有幫助
  3. 可能對你沒幫助
  4. 有沒有幫助都歡迎你提出問題與討論

創建帳號

這段蠻簡單的,不贅敘;特別一提的事可以使用有 VISA 的金融卡作金額控管
比起信用卡動輒數萬到數十萬的額度,可以在金融卡存個 3000 之類的。
預算可以更彈性,而且精準的控制預算。

另外說一下,現在 AWS 有一年的免費額度(已經有好一陣子了),
在註冊流程跳過付款方式的設定,
實際上也是可以操作的 AWS 的(目前只有設定 IAM)。
(2018/01/31 更新)
確定可以使用有VISA的金融卡開戶,
另外當要實際使用 AWS 的服務時 EX: EC2, Lambda etc…
需要綁定信用卡(VISA 金融卡也可以)才能繼續使用。

綁定的過程會需要輸入電話,AWS 會撥一通電話給你,
螢幕上會出現 4 個數字,照畫面輸入即可完成信用卡(VISA 金融卡)的驗証。

權限控管

Root User

Root User
完成註冊後,一開始只能使用 email 登入,這個帳號可以存取 AWS 所有服務。
AWS 不建議使用 Root User 作為日常的管理帳戶, Root User 應該只被用來建立第一個 IAM

重點項目

  1. Root User 啟用 MFA
    MFA
    首先要下載驗証程式(Google Authenticator),
    然後下一步到以下畫面時,,用 Authenticator 掃瞄 QR Code,
    連續輸入兩次授權碼(輸入完第一組後,等待新的授權碼出現再輸入)

連續輸入兩次授權碼

可以在 IAM Console 檢查成功了沒

IAM Console 2. 建立 IAM User 與指定 Group 權限
AWS IAM 的權限觀念是透過 User 與 Group 來組合的,
權限是授與 Group , 而 User 隸屬於 Group 便擁有其權限,
同時 AWS 提供多組(347 組)預設的 Policies, 讓人選擇
當然也可以建立自已的 Policy. *不確定有沒有反向的 Policy ,
如果有當不同的 Group Policy 有衝突時該如何處理。

Policy

建立使用者時, 使用 AutoGenerated Password 時
要記得取得 password
在最後一步會按下 Show 就會顯示
Show Password 3. 設定 AWS Account ID 與 Alias
設定 AWS Account ID 與 Alias

Q & A

  1. 如何禁用 Root user 登入 ? 可以停用而不刪除一個 user account 嗎?
1
2
3
4
5
6
7
root account 無法停用
IAM User 可以透過 disable passwd 方式停用
root account 基本的 practice
1. 啟用 MFA
2. 移除 Access Credential
概念就跟 Windows Administrator or Linux root 一樣
需要時再用

參考

(fin)

[好文分享]應用部署的六種策略

引用出處

正文開始

目前有各種各樣的技術來將新應用部署到生產環境,
所以權衡對系統和終端使用者的影響降至最少,選擇正確的方式是非常重要的。
本文將著重討論如下部署策略:

  • 重建部署:版本 A 下線後版本 B 上線
  • 滾動部署(滾動更新或者增量釋出):版本 B 緩慢更新並替代版本 A
  • 藍綠部署:版本 B 並行與版本 A 釋出,然後流量切換到版本 B
  • 金絲雀部署:版本 B 向一部分使用者釋出,然後完全放開
  • A/B 部署布:版本 B 只向特定條件的使用者釋出
  • 影子部署:版本 B 接受真實的流量請求,但是不產生響應

我們來看一下每個策略最適合哪種使用者使用場景。
爲了簡化,我們使用 Kubernetes ,並用 Minikube 進行例子演示。
每個策略的配置例子和詳細步驟都可以在這個 git 倉庫 上找到。

重建部署

重建策略是一個冗餘的方式,它包含下線版本 A,然後部署版本 B。
這個方式意味著服務的宕機時間依賴於應用下線和啟動耗時。
重建部署

優點:

  • 便於設定
  • 應用狀態完整更新

缺點:

  • 對使用者影響很大,預期的宕機時間取決於下線時間和應用啟動耗時

滾動部署

滾動部署策略是指通過逐個替換應用的所有例項,
來緩慢釋出應用的一個新版本。
通常過程如下:
在負載排程後有個版本 A 的應用例項池,
一個版本 B 的例項部署成功,可以響應請求時,
該例項被加入到池中。
然後版本 A 的一個例項從池中刪除並下線。
考慮到滾動部署依賴於系統,
可以調整如下引數來增加部署時間:

  • 並行數,最大批量執行數:同時釋出例項的數目
  • 最大峰值:考慮到當前例項數,例項可以加入的數目
  • 最大不可用數:在滾動更新過程中不可用的例項數

滾動部署

優點:

  • 便於設定
  • 版本在例項間緩慢釋出
  • 對於能夠處理資料重平衡的有狀態應用非常方便

缺點:

  • 釋出/回滾耗時
  • 支援多個 API 很困難
  • 無法控制流量

藍綠部署

藍綠部署策略與滾動部署不同,
版本 B(綠)同等數量的被並排部署在版本 A(藍)旁邊。
當新版本滿足上線條件的測試後,
流量在負載均衡層從版本 A 切換到版本 B。
藍綠部署

優點:

  • 實時釋出、回滾
  • 避免版本衝突問題,整個應用狀態統一一次切換

缺點:

  • 比較昂貴因為需要雙倍的資源
  • 在釋放版本到生產環境之前,整個平臺的主流程測試必須執行
  • 處理有狀態的應用很棘手

金絲雀部署

金絲雀部署是指逐漸將生產環境流量從版本 A 切換到版本 B。
通常流量是按比例分配的。
例如 90%的請求流向版本 A,10%的流向版本 B。
這個技術大多數用於缺少足夠測試,或者缺少可靠測試,
或者對新版本的穩定性缺乏信心的情況下。

金絲雀部署

優點:

  • 版本面向一部分使用者釋出
  • 方便錯誤評估和效能監控
  • 快速回滾

缺點:

  • 釋出緩慢

A/B 測試

A/B 測試是指在特定條件下將一部分使用者路由到新功能上。
它通常用於根據統計來制定商業決策,而不是部署策略。
然而,他們是相關的,可以在金絲雀部署方式上新增額外功能來實現,所以我們這裏簡要介紹一下。
這個技術廣泛用於測試特定功能的切換,併發布使用佔大部分的版本。
下面是可以用於在版本間分散流量的條件:

  • 瀏覽器 cookie
  • 查詢引數
  • 地理位置
  • 技術支援:瀏覽器版本、螢幕尺寸、作業系統等
  • 語言
    A/B測試

優點:

  • 多個版本並行執行
  • 完全控制流量分佈

缺點:

  • 需要智慧負載均衡
  • 對於給定的會話,很難定位問題,分散式跟蹤是必須的

影子部署

影子部署是指在版本 A 旁邊釋出版本 B,
將版本 A 進來的請求同時分發到版本 B,
同時對生產環境流量無影響。
這是測試新特徵在產品負載上表現的很好用的方式。
當滿足上線要求後,則觸發釋出新應用。
這個技術配置非常複雜,而且需要特殊條件,尤其是分出請求。
例如一個購物車平臺,如果你想影子測試支付服務,
你可能最終會是使用者為他們的訂單支付兩次。
這種情況下,可以通過建立一個模擬的服務來重複響應使用者的請求。
影子部署

優點:

  • 可以使用生產環境流量進行效能測試
  • 對使用者無影響
  • 直到應用的穩定性和效能滿足要求後才釋出

缺點:

  • 雙倍資源,成本昂貴
  • 不是真實使用者測試,可能出現誤導
  • 配置複雜
  • 某種情況下需要模擬服務

總結

部署應用有很多種方法,實際採用哪種方式取決於需求和預算。
當釋出到開發或者模擬環境時,重建或者滾動部署是一個好選擇。
當釋出到生產環境時,滾動部署或者藍綠部署通常是一個好選擇,
但是新平臺的主流程測試是必須的。
藍綠部署和影子部署對預算有更高的要求,因為需要雙倍資源。
如果應用缺乏測試或者對軟體的功能和穩定性影響缺乏信心,
那麼可以使用金絲雀部署或者 AB 測試或者影子釋出。
如果業務需要根據地理位置、語言、作業系統或者瀏覽器特徵等這樣引數來給一些特定的使用者測試,那麼可以採用 AB 測試技術。
最後但並不是最不重要的,影子釋出很複雜,且需要額外工作來模擬響應分支流量請求,
當可變操作(郵件、銀行等)呼叫外部依賴時這是必須的,
這個技術在升級新資料庫是非常有用,使用影子流量來監控負載下的系統性能。
下表可以幫助你選擇正確的策略:
總結
取決於雲服務提供商和平臺,如下文件是理解部署的很好開始:

  • Amazon Web Services
  • Docker Swarm
  • Google Cloud
  • Kubernetes

希望這是有幫助的,如果有任何問題或者反饋,可以在下面評論

(正文結束)

補充表格翻譯


| 策略 | 服務不斷線 | 真實環境測試 | 預算成本 | 退版時間 | 使用者影響 | 複雜度 |
| ———- | ———- | ———— | ——– | ——– | ———- | —— | — |
| 重建部署 | ✖ | ✖ | ✖ | ★☆☆ | ★★★ | ★★★ | ☆☆☆ |
| 滾動部署 | ✔ | ✖ | ✖ | ★☆☆ | ★★★ | ★☆☆ | ★☆☆ |
| 藍綠部署 | ✔ | ✖ | ✖ | ★★★ | ☆☆☆ | ★★☆ | ★★☆ |
| 金絲雀部署 | ✔ | ✔ | ✖ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ |
| A/B 部署 | ✔ | ✔ | ✔ | ★☆☆ | ★☆☆ | ★☆☆ | ★★★ |
| 影子部署 | ✔ | ✔ | ✖ | ★★★ | ☆☆☆ | ☆☆☆ | ★★★ |


非常實用的文章,可惜中譯的圖片並非 gif,原文的超聯結也掉失,
特別重新修正以上問題,留作記錄

(fin)

代碼審查與交付的戰爭ー標準、風格與原則

Coding Standard / Code Review / Pull Request & Delivery

故事背景

  1. 團隊的部署流程是 Github Flow 與 Git Flow 混用 , 給它起個名字叫 GG Flow 好了.
  2. GG Flow 的過程需要開發人員需要透過 Pull Request 將修改推送給產品
  3. 擁有權限 Merge Pull Request 的成員被叫作 Reviewer
  4. Reviewer 通常由較資深人員或部門主管擔任,所以通常有比較多的無用會議要開
  5. Reviewer 在 Merge 之前需要作 Code Review
  6. Reviewer 需要遵循 Coding Standard 作 Code Review

實務面臨的問題與副作用

Coding Standard 並不能考慮到所有狀況

  1. 所以 Reviewers 會定期針對不同的狀況開會討論 Coding Standard
    • Coding Standard 會不定期改變 , 但是透過 Reviewer 佈達的方式,讓第一線的 RD 其實難以知道其全貌.
    • Coding Standard 改變後不會全面的翻改程式,實務上是作到哪裡改到哪裡
    • 以上兩點導致 Source Code 裡面有很多符合不同時期的 Coding Standard 的 Code
    • 任一個時間點, 誰都無法保証完全符合最新的 Coding Standard
  2. 人性,開發者會COPY/PASTE 方法開發參考 Legacy Code 開發
    • Legacy Code 不符合新的 Coding Standard
    • Reviewer 也是人, 所以 Code Review 時也會疏漏,而 Merge 進去不符合新的 Coding Standard 的 Code
    • 所以 Source Code 裡面還是有很多不同時期的 Coding Standard 的 Code
  3. 回歸一開始的問題 Coding Standard 並不能考慮到所有狀況
    • 還沒有開會前, 不同的 Reviewer 會有不同的想法
    • 開會後,在執行 Code Review 時, 不同的 Reviewer 會有不同的作法
    • 當一個 PR 有多人 Reviewer 時, 會有不同的意見 PR 因此被延遲 Merge
    • 結果,交付會變慢.

反思,標準還是風格?

思考一下,開發程式碼的目標與價值是什麼 ?
寫出 Clearn Code ?
還是交付產品 ?
這樣子的 Source Code 真的是 Clearn Code 嗎?

自問自答

Q1. 我們該有標準嗎?

A1. 當然要有標準,不過標準之所以為標準,應該有以下幾個特點.

  • 它應該要很簡單, 像是 Class 與欄位的命名規則
  • 它應放諸四海皆準, 不應該輕易被修改
  • 它應該可以被自動化的檢測
    假設能作到這 3 點, 這件事應該可以被自動化工具處理掉 .

Q2. 實務上就是很複雜, 所以才需要討論制訂標準啊

A2.
在實務上遇到很複雜的情況, 大多需要依賴約定成俗方式規範.
這是一種風格原則 ;
簡單的分類方法,
如果無法透過自動化工具作檢測,
就不應該歸類為標準.

註:有機會再介紹自動化的檢測工具

Q3. 風格原則標準有何不同?

A3. 如上所說,標準應該能被自動化,
風格應該是團隊的文化自然形成的產物,
具體的實作可以透過讓開發者彼此之間作代碼審核
或是結對編程培養出屬於團隊的風格,
風格要基於標準之上,但是不能違反原則;

以下的原則可以作為參考

  • 可以建置並通過測試
  • 可讀性
    • self documenting
    • 有用的註解
  • 公開方法要可以被測試
    • 小心使用靜態類別
    • 注意 new Instance 的時機
    • 重複的代碼應重構
  • 保持 SOLID

初期的可能會發生在「{」要不要換行之類的問題上揪結之類的蠢事,
如果可以自動化,就把它作成標準吧…
如果不行的話, 就別揪結了.

實務上可能遇到各種狀況,
把 Reviewer 的權限下放到各個開發者身上,
或是使用結對編程,
就讓團隊成員去討論與決定風格.

以標準為根基,原則為天,
踩穩腳步,不要超出天空,
就讓團隊自由發揮吧.

最後,持續交付會比每兩周花一個小時開會決定 Style 的細節好多了. 不是嗎?

其它團隊分享的具體作法

  1. 超過一定時間就讓成員擁 Merge 權限
  2. Release 權限仍集中控管
  3. 錯了再改就好(保持敏捷)
  4. 給 pair 作 code review 與 merge (避免一人思維陷井)
  5. 兩個人無法解決時找第三方
  6. release 功能 優先於 一致的 coding standard
  7. 品質由測試管控而非 reviewer
  8. 先有測試才有重構
  9. 可讀性 優於 枝微末節的 coding standard 實踐
  10. 善用自動化工具( sonarqube / stylecop )

(fin)

補充 社群觀點
  • coding style 一般不管的。
  • class name/variable name,一定要叫有意義的名字。
  • local scope variable,換多少行,indentation,這些是小事
  • 一個成熟的 developer,隨時會被上司命令這些遠古火星文明(legacy system)去做考古工作
  • coding style 這些事,就像 emacs 和 vim 之戰一樣,戰到 skynet 出來了也不會戰完的
  • 如果要開會去討論 coding style,最終很可能讓團隊口服心不服地去跟隨我的 Coding Style。
  • 在 Coding Style 這種低層次的小事上用光了團隊成員之間互相容忍的能量,而在更重要的大事上無法好好合作。
  • 有很多事是比 Coding Style 重要的。
    • Object Modeling 是否跟 business logic 一致?
    • 還是 Object 有這個 attribute 但是根本沒在用?
    • Code Change 是否有做好測試?
    • 系統架構是否合理
    • 有做好 High-Avalibility 嗎?
    • 有沒有 Race Condition?
  • 是其是,非其非。真正有道理的,你說了對方便自然會聽下去。
  • 「Senior」是代表自己在專業上懂得比別人多,而不是比別人身份高級。

一年後的我想要什麼?

你的目標是什麼?

自由

什麼是自由?

  1. 情感上的自由
  2. 時間上的自由
  3. 經濟上的自由

經濟上的自由是一切的基礎

  1. 更多的收入
    1. 被動的收入
    2. 更高的薪資
  2. 更有成就感的工作
    1. 被同事尊重
    2. 被上司認可
    3. 受人歡迎
  3. 更多的選擇
    1. 技術提昇
    2. 領域擴展
    3. 人脈
  4. 健康的身體
  5. 更多享受生活

一年後的我想要什麼?

一年後的我想要什麼?

  1. 更高的薪資
    1. 維持現狀 K (x)
    2. 跳糟 K * 1.3 (x)
    3. 爭取加薪 K * 1.1
    4. 獎金與分紅
  2. 更有成就感的工作(什麼是成就感)?
    1. 擁有可以引以為豪的產品
      1. 拆解單體為服務導向
      2. 架構升級

    2. 分享與教學
      1. Blog
      2. 內/外部sharing
      3. 單元測試導入

  3. 更多的選擇
    1. 技術轉移
      1. Web 技術 轉移 為 Service導向技術
      2. container 技術
      3. cloud 技術

    2. 技術提昇
      1. .Net
      2. Infra
      3. Domain know how

爭取加薪 K * 1.1

而這些目標需要什麼才能爭取到?

分為三個面向,技術提昇、技術擴展與自我實現,

技術提昇

首先目前我擁有的技術能力有

  1. .Net solution的 Web Developer能力
  2. 同時兼顧有 DB 與 F2E 基礎進階能力

這與我過去的選擇有關, 一直以來都在 Web 深入研究
未來的一年仍要朝這個方向發展.
但是會改由 Web 導向轉變成服務導向,
而 Web 開發只是我本身所能提供給公司的一個服務而已,
跟著 .Net 的腳步我想把前台(包含大馬但不限於)昇級上一個版本,
包含目前使用的舊版 libary 與 Framework,
如此一來可以使用到新版 .Net 的語法,
同時也可以解開一些導入測試與微服務時遇到的困境.

現有能力列表

  1. C# & .Net Framework Solution (inculde Linq & Entity Framework )
  2. javascript (jQuery & Angular etc..)
  3. Database with Sql (MsSQL solution)
  4. Source Controle (Git)

略懂

  1. nodejs (with expressjs) & php(codeigniter) & ruby (RoR)
  2. Jenkins

技術的擴展

我認為公司的單體架構已經面臨到不得不拆的狀況,
公司也有意朝這個方向走那是最好不過的了,
但對現存的.NET 開發者而言,我認為人人都要有危機意識
我看到的現象

  1. 頁面會被CMS取代
  2. 大部份的API可以被Lambda取代
  3. 主流程的部份在跨國的目標下會逐步變成微服務

基於以上幾點, 除了.Net 的 solution 外,
更多情況是要使用別的 solution 或是混用,
對此我的視野必須有所提昇,

  1. Linux Bash
  2. Container 技術
  3. Cloud (AWS/Azure/GCP)
  4. Node.js
    在未來的一年開發流程或是維運流程會有很大很快的變化
    要多聽多看多想多問,公司有很多人才要儘可能的跟他們學習.

自我營銷

最後是自我實現的部份,
自我營銷是我很弱的一部份,
不善交際,不喜歡人群
這點我從去年就開始調整,
多參與公司內部的分享,不要害怕說錯
假裝自已是對的,再虛心接受別人的指點
不需要導師,因為人人都是我的導師
開始寫Blog並且貼到社群網站給人鞭
這是我目前的 https://blog.marsen.me

今年會繼續朝這個方向衝刺.
多分享 多犯錯 然後接受反饋學習.
兩個部份是我可以練習分享的機會
一個是測試的導入,
我們的遺留代碼,有很多可以分享的部份,
二是讀書會,
借由讀書會可以練習分享,
同時學習別人怎麼分享,
並且看完一本書,一舉數得.

具體主管可以幫助我的部份

  1. 加薪(沒有比這個更務實的了)
  2. 明確指出我的錯誤或是作的好的部份

Do more do faster

(fin)

2017年的學習回顧與展望

測試

參加了兩個活動,分別是單元測試這樣玩就對了,與測試即學習;

單元測試這樣玩就對了

最大的收獲是突破了寫測試的心魔, 在那之前總覺得 TDD 只是口號, 或是烏托邦的開發理想.
雖然之前也有花大錢出外受訓, 或是公司內部的內訓.
手上也有一些前輩的測試代碼, 但是就是沒有「感覺」,

1
2
3
不過仍然感謝前幾年的自已有將資源花在測試上面,
累積了兩年的測試經驗,
融會貫通卻只是一瞬間的事

最主要是講者展現了實務上面的需求與改進,
過去寫的加法運算, 或是吊人遊戲,
雖然也是從無到,也是先寫測試,
但或許是太過強調 TDD 太過強調從無到有
總與實際開發經驗相違背,
講者在過程中一句 「不要管先寫測試還是程式」
反而更貼近真實,先寫程式,再想想怎麼測試?
為了測試, 再重構, 逐步分解的過程令我大開眼界.

過去曾與人討論過 TDD 的議題,
總會得到「實務與理論有差別」、「其背景與環境因素不能寫測試」
這類似是而非的回答,當下我也無法辯駁
現在想起來只能莞爾.

測試趨動開發不等於一定要先測試,
特別是習慣於先寫產品程式的人,
不仿先寫產品程式, 再寫測試而趨動重構
當寫習慣了,知道哪些耦合會帶來重構的代價時,
再寫一次就人性就會自動迴避這些不好的 Patten .

別管順序,但是記得寫測試.

測試即學習

梅老師的課也是很毀三觀的,
老實說我跟本不知道他在幹嘛,
塔羅牌拿出來的時候,
我真的以為是美江再現(那個時候Seafood還沒有業力引爆( ・ω・)( ・ω・)( ・ω・))
那堂課的目標族群應該是 QA,
不過這個時代不應自我設限,
複習一下梅老師的分享,

探索測試

  • 儘可能的發散
  • 摹仿別人看事情的角度
  • 有意識的學習(mindful learning)
    • 記錄
    • 總結
    • 歸納問題的核心
    • 給它起一個名字(沒有專有名詞的話)
    • 有意識的逃離第一印象
    • 小心不經意的盲區(inattentional blindness)
    • 數量 X 練習 X 思考 X 學習 = 提昇

對我來說,給它起一個名字真是超級有用的,
新認識的朋友就叫作「王大棰」, 新的技術就叫「起司包」, 新的概念就叫「黑盒子」等…
不要過於專研於名詞, 用自已能理解的名字去框那個新事物的範圍,
最後再把名字用大家通用的名字取代掉就好了.
看了很多的方法論,會發現其實觀念沒什麼改變,
只是新的名詞會一直冒出來,
唯物主義、馬基維利主義、不擇手段、實用主義
改善、KANBAN、敏捷、精實 balabala…

“没有时间”- 完美的借口

2017 年影響我最深的一篇 Blog,
2012 年的文章, 篇幅也不長,
觀念也很簡單, 作就對了, 作壞了就丟掉再作一次,
很多的書都要我們刻意練習,
但是我們的時間哪有那麼多呢?
長大了之後,才來 1 萬小時的修練是不是太晚了呢?

下半年公司開始推行測試,
基層的工程師們其實反彈的聲音一直都在,
雖然一直有教育訓練與培養種子,
不過大多淪為口號的狀態,
我也只能用這篇文章作為勉勵.

其實現在的版本控制系統已經非常便利,
要建立一個 Sandbox 的分支,
在裡面嘗試各種可能性是非常容易的,
作壞了丟掉分支就好了,
身為專業的工程人員,測試是必須的,失敗也是,
在沙箱內失敗,其實是摔不疼的,勇敢嘗試吧.

生產力

2016 年是時間記錄的一年,
那 2017 年就是把記錄的時間,
轉換成生產力的一年.
幾本影響比較多的書,不過或許我仍需要二讀至三讀以上

  • 最有生產力的一年 → 時間 X 精力 X 專注力 = 生產力
  • GTD → 下一步要作什麼?
  • 軟技能 → 自我營銷

新的一年重心會放在習慣上面,
因為讓習慣趨動行為,
比起刻意遵循某些方法要好得多,
「刻意」太浪費精神力了,
下一步會如何呢?
希望能翻轉自已

其它

順利的帶媽媽出國了,或許對一些人來說不算什麼成就吧,
新的一年有些機會,去日本、以色列、俄羅斯、馬來西亞,
雖然八字還沒有一撇, 但至少會去一個地方吧…
其他的地方就只能見機行事了.

新年快樂
(fin)

[踩雷筆記] Visual Studio 2017 MSTest Framework 異常修正

應該要知道的事

  • 這是踩雷筆記
  • 2017 的筆記可能會隨時間變得沒有參考價值
  • Visual Studio 2017 的問題,並不一定適用其他版本
  • 最後面會有不定時補充

情境

載入測試時發生例外狀況
原本使用 Visual Studio 2015 建立的測試專案,
升級到 Visual Studio 2017 後, 發生以下錯誤

1
2
3
4
5
[2017/12/11 上午 02:09:59 Error] 測試探索程式 'SpecRunTestDiscoverer'
載入測試時發生例外狀況。例外狀況: 無法載入檔案或組件
'Microsoft.VisualStudio.QualityTools.UnitTestFramework,
Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
或其相依性的其中之一。 系統找不到指定的檔案。

導致結果

原本的測試數量為1942,變成459,遺失了7成5的測試案例.

  1. 測試專案會找不到測試,或是測試數量不正確.
  2. 可以使用 Visual Studio 2015 重新執行探測索測試,即可排除問題.

VS2015 已移除或未安裝該怎麼辦?

透過 MsTest 直接加入
Microsoft.VisualStudio.QualityTools.UnitTestFramework
的參考已經是舊的方法了,

在 Visual Studio 2017 建議的解決方案如下:

  • 移除方案中所有對 Microsoft.VisualStudio.QualityTools.UnitTestFramework 的參考
  • 透過 Nuget 安裝 MSTest.TestAdapter
  • 透過 Nuget 安裝 MSTest.TestFramework
  • 關閉 Visual Studio 2017
  • 移除 %temp%\VisualStudioTestExplorerExtensions內所有檔案
  • 重啟 Visual Studio 2017 並建置以觸發探索測試
    透過 Nuget 安裝 MSTest.TestAdapter/MSTest.TestFramework
    重啟 Visual Studio 2017 並建置以觸發探索測試

參考

(fin)

補充

  • 2018/06/02 :
    Visual Studio 2017 15.7.* 的版本之後 ,
    %temp%\VisualStudioTestExplorerExtensions 消失了 ,
    不過正常情況建置後 , visual studio 探索測試仍然可以正確找到測試。

[實作筆記] 單元測試與重構記錄(二) 發問篇

Q1 Controller 要測試嗎?

Logics in controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[Route("Member/Get/{Id}")]
public JsonResult GetMemberList(long Id, string cc = "f")
{
var cleanCache = false;
//// logics here
if (this.IsFromCompany() && cc == "t")
{
//// do something ...
}

try
{
var memberList = this.memberService.GetMemberList(Id, cleanCache);
//// logics here
if (memberList.Any())
{
//// do something ...
}
else
{
//// do something ...
}

return this.Json(result, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
//// logics here
//// do something ...
}
}

自問自答

我認為要,
但是對於 WebAPI 回傳的JsonResult或是ActionResult
需要轉形才能作驗証
可以考慮整合測試勝於單元測試,
Controller 的通常是面對 Client Side 的呼叫.

Q2 當 Controller 只有取資料的邏輯

No Logics in Controller

1
2
3
4
public ActionResult Index()
{
return this.Service.GetIndex();
}

Q3 當 Service 只有取資料的邏輯

No Logics in Service

1
2
3
4
public Member Get(long id)
{
return this.DataAccessor.GetMember(id);
}

Q3.自問自答

我認為不要,
要測試商業邏輯,不要在意覆蓋率

Q4. 當 Service 只有取 Catch 資料的邏輯

No Logics in Service , just call another service

1
2
3
4
5
6
7
8
9
10
11
public Member Get(long id)
{
var enableCache = true;
var result = this.CacheService.GetCacheData(
cacheKey,
() => {
return this.DataAccessor.GetMember(id);
},
enableCache
);
}

Q4.自問自答

同上,仍然不需要,
要測試商業邏輯,不要在意覆蓋率,
要注意的或許是CacheService.GetCacheData是不是有包測試 ?
一般來說,Cache 的功能很泛用,測試的報酬率很高

Q5. 承上,當邏輯存在 Func 參數之中?

Logics in Func

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Member Get(long id)
{
var enableCache = true;
var result = this.CacheService.GetCacheData(
cacheKey,
() => {
//// logics here
if(id > 9487)
{
return this.MemberAccessor.GetMember(id);
}else
{
return this.MemberV2Accessor.GetMember(id);
}

},
enableCache
);
}

Q5.自問自答

暫時無解,
或許是這樣 Pattern 不適合測試,需要調整架構嗎?
為了測試多包成一個公開方法,反而失去匿名函數的彈性優點,
不在匿名函數內寫邏輯更不合理,待求解答

Q6.當邏輯在 DA 層或 ORM 的 Query 中要如何測試?

Logics in ORM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上略...
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
{
using (Entities context = Entities.CreateNew(isReadOnly: true))
{
//// logics here
var query = from a in context.Activies.Valids()
where a.Activies_StartDateTime <= startTime &&
a.Activies_EndDateTime >= now &&
a.Activies_ShopId == shopId &&
a.ActiviesCondition.Any(i => i.Activies_ValidFlag
&& TypeList.Contains(i.Activies_TypeDef))
select a;
}
}

Q6.自問自答

不適用單元測試,應該整合測試作包覆

Q7. 當邏輯在 MappingProfile 該如何測試?

Logics in MappingProfile

1
2
3
4
5
6
7
8
protected override void Configure()
{
Mapper.CreateMap<PageEntity, UserPageEntity>()
.ForMember(i => i.Id, s => s.MapFrom(i => i.User_Id))
.ForMember(i => i.Title, s => s.MapFrom(i => i.User_Name))
.ForMember(i => i.PageName, s => s.MapFrom(i => i.User_Name + i.User_LastName))
.ForMember(i => i.LightBox, s => s.MapFrom(i => i.User_Sex == "male" ? true : false));
}

Q7. 自問自答

要作測試,檢查欄位 Mapping 是否正確,
但實務上若重用性不高,寫 MappingProfile 不如直接在代碼內轉換.
可以少寫 MappingProfile 的測試.

待解答…

(fin)

Please enable JavaScript to view the LikeCoin. :P