Reputation: 14148
I need to write unit test in c# to generate fake canadian SIN number for our application. After searching the internet, here is what I found. I don't even know how to get started. Specially multiplying each top number with the number below is confusing me because its not a straight multiplication. Appreciate the help in advance. Thanks.
Here is the algorithm I got from the google search.
Algorithm _
Social Insurance Numbers are validated via a simple checksum process.
Let's use this fictitious SIN to demonstrate:
046 454 286 < Try substituting your SIN
046 454 286 \ Multiply each top number
121 212 121 / by the number below it..
-----------
086 858 276 < and get this.
^
Notice here that 8*2=16, add 1 and
6 together and get 7. If you get a
2 digit # add the digits together.
Add all of these digits together.
0+8+6+8+5+8+2+7+6=50
/\
If the SIN is valid this # will
be evenly divisible by 10. This
is a 'valid' SIN.
Upvotes: 3
Views: 11079
Reputation: 1833
There may be a way to algorithmically generate a valid number, but as a quick fix, you could generate a random series of 9 integers, then validate it. Repeat until you have a valid series. Here is a complete implementation:
class SinGenerator
{
Random r = new Random();
/// <summary>
/// Generates a valid 9-digit SIN.
/// </summary>
public int[] GetValidSin()
{
int[] s = GenerateSin();
while (!SinIsValid(s))
s = GenerateSin();
return s;
}
/// <summary>
/// Generates a potential SIN. Not guaranteed to be valid.
/// </summary>
private int[] GenerateSin()
{
int[] s = new int[9];
for (int i = 0; i < 9; i++)
s[i] = r.Next(0, 10);
return s;
}
/// <summary>
/// Validates a 9-digit SIN.
/// </summary>
/// <param name="sin"></param>
private bool SinIsValid(int[] sin)
{
if (sin.Length != 9)
throw new ArgumentException();
int checkSum = 0;
for (int i = 0; i < sin.Length; i++)
{
int m = (i % 2) + 1;
int v = sin[i] * m;
if (v > 10)
checkSum += 1 + (v - 10);
else
checkSum += v;
}
return checkSum % 10 == 0;
}
}
Upvotes: 4
Reputation: 416049
This doesn't really belong as an answer, but it's important and there's not enough space for it in the comments, so here goes...
This sounds an awful lot like the purpose of your real code is to validate user-entered SINs, and you want to randomly generate SINs in your test suite for testing your validation code. That's wrong.
Unit tests should not generate random data. This doesn't mean you can't use data at all; it's perfectly fine to generate a set of 10,000 good SINs and 10,000 bad SINs once, save that data, and then write a test against your validation method that you get the right result for all of them. The important rule is this:
Whether a test passes or fails should not depend on what data a randomizer happens to generate on a specific run.
The main thing is a failing test must continue to fail in a repeatable way until you fix the code that caused the failure. You can't guarantee this if the test data is random.
Later on, you may still find a bug where your validator fails for a specfic SIN. The correct response at this point is permanently adding that SIN to your test data. Then fix the code. In this way, you are also protected from regressions.
You might still want to use this just to generate your initial test data, and that's fine, but I'd argue it's much easier to find a web site out there that can already give you a data set. Here's a quick Google result:
http://www.testingmentor.com/tools/tool_pages/ssnsin.htm
And, just for fun, here's what my validation method would look like (untested, typed directly in reply window):
bool IsValidSIN(string SIN)
{
//normalize
SIN = SIN.Trim();
//account for common input formats that use - . or space as separators
SIN = SIN.Replace(" ", "").Replace("-","").Replace(".","");
//basic validations
if (string.IsNullOrEmpty(SIN)) return false;
if (SIN.Length != 9) return false;
if (!SIN.All(c => char.IsDigit(c) ) return false;
//checksum - we already know here that all characters are digits
int multiplier = 0;
int sum = 0;
foreach (int d in SIN.Select(c => (int)c))
{
sum += (d * ((multiplier % 2) + 1)).ToString().Select(c => (int)c).Sum();
multiplier ++;
}
return sum % 10 == 0;
}
Upvotes: 7
Reputation: 21491
var nas = "046 454 286".Where(x => !Char.IsWhiteSpace(x)).ToList();
var check = "121 212 121".Where(x => !Char.IsWhiteSpace(x)).ToList();
var result = "";
for(int i = 0; i < nas.Count(); ++i){
int tmp = (int)(nas[i]) * (int)(check[i]);
var val = tmp < 10 ? tmp : tmp - 9;
result += val;
}
var sum = result.Aggregate(0, (acc, item) => acc + (int)(item));
if(sum % 10 == 0) Console.WriteLine("valid!"); else Console.WriteLine("invalid!");
Upvotes: 0
Reputation: 10862
You could create a list of 9 integers/digits, generate the first 8 digits with a random number between 0 and 9 and then set the last digit to a value so that the 9 digits addeded are divisable by 10.
This is the way to calculate the digita 9th after all the other 8 digits have been generated randmonly
d9 = 10 - (d1+d2+d3+d4+d5+d6+d7+d8)%10
Upvotes: 0
Reputation: 726839
I think the reason they put 7
in place of 16
is that they are short on space: if they put 16
instead of 7
, the other digits would no longer line up.
I think this is a better illustration of what is going on:
046 454 286 \ Multiply each top number
121 212 121 / by the number below it..
-----------
000 000 010 < and get this (tens are on the top)
086 858 266 < ... ones are on the bottom.
Since in the end you add up all the digits, having a single 7
vs. 16
does not make a difference on the final result.
To generate lots of valid SINs you could generate random nine-digit numbers, compute their "check sum", find the remainder of that check sum when divided by ten, and adjust one or more of the digits in positions 1, 3, 5, 7, or 9 to get the correct check sum.
Upvotes: 1